It all works

This commit is contained in:
Erlend Hamnaberg
2009-11-08 19:52:30 +01:00
parent b8faa6e36f
commit 0786949c1c
319 changed files with 0 additions and 0 deletions
@@ -0,0 +1,259 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.thumbsdb;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.ole2.CompoundDocument;
import com.twelvemonkeys.lang.StringUtil;
import java.io.InputStream;
import java.io.IOException;
import java.io.DataInput;
import java.util.Iterator;
import java.util.Date;
/**
* Represents a {@code Catalog} structure, typically found in a {@link com.twelvemonkeys.io.ole2.CompoundDocument}.
*
* @see <a href="http://www.petedavis.net/MySite/DynPageView.aspx?pageid=31">PeteDavis.NET</a>
*
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
* @author last modified by $Author: haku$
* @version $Id: Catalog.java,v 1.0 01.feb.2007 17:19:59 haku Exp$
*/
// TODO: Consider moving this one to io.ole2
public final class Catalog implements Iterable<Catalog.CatalogItem> {
private final CatalogHeader mHeader;
private final CatalogItem[] mItems;
Catalog(final CatalogHeader pHeader, final CatalogItem[] pItems) {
mHeader = pHeader;
mItems = pItems;
}
/**
* Reads the {@code Catalog} entry from the given input stream.
*
* @param pInput the input stream
* @return a new {@code Catalog}
*
* @throws java.io.IOException if an I/O exception occurs during read
*/
public static Catalog read(final InputStream pInput) throws IOException {
DataInput dataInput = new LittleEndianDataInputStream(pInput);
return read(dataInput);
}
/**
* Reads the {@code Catalog} entry from the given input stream.
* <p/>
* The data is assumed to be in little endian byte order.
*
* @param pDataInput the input stream
* @return a new {@code Catalog}
*
* @throws java.io.IOException if an I/O exception occurs during read
*/
public static Catalog read(final DataInput pDataInput) throws IOException {
CatalogHeader header = CatalogHeader.read(pDataInput);
CatalogItem[] items = new CatalogItem[header.getThumbnailCount()];
for (int i = 0; i < header.getThumbnailCount(); i++) {
CatalogItem item = CatalogItem.read(pDataInput);
//System.out.println("item: " + item);
items[item.getItemId() - 1] = item;
}
return new Catalog(header, items);
}
public final int getThumbnailCount() {
return mHeader.mThumbCount;
}
public final int getMaxThumbnailWidth() {
return mHeader.mThumbWidth;
}
public final int getMaxThumbnailHeight() {
return mHeader.mThumbHeight;
}
final CatalogItem getItem(final int pIndex) {
return mItems[pIndex];
}
final CatalogItem getItem(final String pName) {
return mItems[getIndex(pName)];
}
final int getItemId(final int pIndex) {
return mItems[pIndex].getItemId();
}
public final int getIndex(final String pName) {
for (int i = 0; i < mItems.length; i++) {
CatalogItem item = mItems[i];
if (item.getName().equals(pName)) {
return i;
}
}
return -1;
}
public final String getStreamName(final int pIndex) {
return StringUtil.reverse(String.valueOf(getItemId(pIndex)));
}
public final String getName(String pStreamName) {
return getName(Integer.parseInt(StringUtil.reverse(pStreamName)));
}
final String getName(int pItemId) {
return mItems[pItemId - 1].getName();
}
@Override
public String toString() {
return String.format("%s[%s]", getClass().getSimpleName(), mHeader);
}
public Iterator<CatalogItem> iterator() {
return new Iterator<CatalogItem>() {
int mCurrentIdx;
public boolean hasNext() {
return mCurrentIdx < mItems.length;
}
public CatalogItem next() {
return mItems[mCurrentIdx++];
}
public void remove() {
throw new UnsupportedOperationException("Remove not supported");
}
};
}
private static class CatalogHeader {
short mReserved1;
short mReserved2;
int mThumbCount;
int mThumbWidth;
int mThumbHeight;
CatalogHeader() {
}
public static CatalogHeader read(final DataInput pDataInput) throws IOException {
CatalogHeader header = new CatalogHeader();
header.mReserved1 = pDataInput.readShort();
header.mReserved2 = pDataInput.readShort();
header.mThumbCount = pDataInput.readInt();
header.mThumbWidth = pDataInput.readInt();
header.mThumbHeight = pDataInput.readInt();
return header;
}
public int getThumbnailCount() {
return mThumbCount;
}
public int getThumbHeight() {
return mThumbHeight;
}
public int getThumbWidth() {
return mThumbWidth;
}
@Override
public String toString() {
return String.format(
"%s: %s %s thumbs: %d maxWidth: %d maxHeight: %d",
getClass().getSimpleName(), mReserved1, mReserved2, mThumbCount, mThumbWidth, mThumbHeight
);
}
}
public static final class CatalogItem {
int mReserved1;
int mItemId; // Reversed stream name
String mFilename;
short mReserved2;
private long mLastModified;
private static CatalogItem read(final DataInput pDataInput) throws IOException {
CatalogItem item = new CatalogItem();
item.mReserved1 = pDataInput.readInt();
item.mItemId = pDataInput.readInt();
item.mLastModified = CompoundDocument.toJavaTimeInMillis(pDataInput.readLong());
char[] chars = new char[256];
char ch;
int len = 0;
while ((ch = pDataInput.readChar()) != 0) {
chars[len++] = ch;
}
String name = new String(chars, 0, len);
item.mFilename = StringUtil.getLastElement(name, "\\");
item.mReserved2 = pDataInput.readShort();
return item;
}
public String getName() {
return mFilename;
}
public int getItemId() {
return mItemId;
}
public long lastModified() {
return mLastModified;
}
@Override
public String toString() {
return String.format(
"%s: %d itemId: %d lastModified: %s fileName: %s %s",
getClass().getSimpleName(), mReserved1, mItemId, new Date(mLastModified), mFilename, mReserved2
);
}
}
}
@@ -0,0 +1,425 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.thumbsdb;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.io.ole2.CompoundDocument;
import com.twelvemonkeys.io.ole2.Entry;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Iterator;
import java.util.SortedSet;
/**
* ThumbsDBImageReader
*
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
* @author last modified by $Author: haku$
* @version $Id: ThumbsDBImageReader.java,v 1.0 22.jan.2007 18:49:38 haku Exp$
* @see com.twelvemonkeys.io.ole2.CompoundDocument
* @see <a href="http://en.wikipedia.org/wiki/Thumbs.db>Wikipedia: Thumbs.db</a>
*/
public class ThumbsDBImageReader extends ImageReaderBase {
private static final int THUMBNAIL_OFFSET = 12;
private Entry mRoot;
private Catalog mCatalog;
private BufferedImage[] mThumbnails;
private final ImageReader mReader;
private int mCurrentImage = -1;
private boolean mLoadEagerly;
public ThumbsDBImageReader() {
this(new ThumbsDBImageReaderSpi());
}
protected ThumbsDBImageReader(final ThumbsDBImageReaderSpi pProvider) {
super(pProvider);
mReader = createJPEGReader(pProvider);
initReaderListeners();
}
protected void resetMembers() {
mRoot = null;
mCatalog = null;
mThumbnails = null;
}
private static ImageReader createJPEGReader(final ThumbsDBImageReaderSpi pProvider) {
return pProvider.createJPEGReader();
}
public void dispose() {
mReader.dispose();
super.dispose();
}
public boolean isLoadEagerly() {
return mLoadEagerly;
}
/**
* Instructs the reader wether it should read and cache alle thumbnails
* in sequence, during the first read operation.
* <p/>
* This is useful mainly if you need to read all the thumbnails, and you
* need them in random order, as it requires less repositioning in the
* underlying stream.
*
* @param pLoadEagerly {@code true} if the reader should read all thumbs on first read
*/
public void setLoadEagerly(final boolean pLoadEagerly) {
mLoadEagerly = pLoadEagerly;
}
/**
* Reads the image data from the given input stream, and returns it as a
* {@code BufferedImage}.
*
* @param pIndex the index of the image to read
* @param pParam additional parameters used while decoding, may be
* {@code null}, in which case defaults will be used
* @return a {@code BufferedImage}
* @throws IndexOutOfBoundsException if {@code pIndex} is out of bounds
* @throws IllegalStateException if the input source has not been set
* @throws java.io.IOException if an error occurs during reading
*/
@Override
public BufferedImage read(final int pIndex, final ImageReadParam pParam) throws IOException {
init();
checkBounds(pIndex);
// Quick look-up
BufferedImage image = null;
if (pIndex < mThumbnails.length) {
image = mThumbnails[pIndex];
}
if (image == null) {
// Read the image, it's a JFIF stream, inside the OLE 2 CompoundDocument
init(pIndex);
image = mReader.read(0, pParam);
mReader.reset();
if (pParam == null) {
mThumbnails[pIndex] = image; // TODO: Caching is not kosher, as images are mutable!!
}
}
else {
// Keep progress listeners happy
processImageStarted(pIndex);
processImageProgress(100);
processImageComplete();
}
// Fake destination support
if (pParam != null && (pParam.getDestination() != null && pParam.getDestination() != image ||
pParam.getDestinationType() != null && pParam.getDestinationType().getBufferedImageType() != image.getType())) {
BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), getWidth(pIndex), getHeight(pIndex));
Graphics2D g = destination.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.drawImage(image, 0, 0, null);
}
finally {
g.dispose();
}
image = destination;
}
return image;
}
/**
* Reads the image data from the given input stream, and returns it as a
* {@code BufferedImage}.
*
* @param pName the name of the image to read
* @param pParam additional parameters used while decoding, may be
* {@code null}, in which case defaults will be used
* @return a {@code BufferedImage}
* @throws java.io.FileNotFoundException if the given file name is not found in the
* "Catalog" entry of the {@code CompoundDocument}
* @throws IllegalStateException if the input source has not been set
* @throws java.io.IOException if an error occurs during reading
*/
public BufferedImage read(final String pName, final ImageReadParam pParam) throws IOException {
initCatalog();
int index = mCatalog.getIndex(pName);
if (index < 0) {
throw new FileNotFoundException("Name not found in \"Catalog\" entry: " + pName);
}
return read(index, pParam);
}
public void abort() {
super.abort();
mReader.abort();
}
@Override
public void setInput(Object pInput, boolean pSeekForwardOnly, boolean pIgnoreMetadata) {
super.setInput(pInput, pSeekForwardOnly, pIgnoreMetadata);
if (mImageInput != null) {
mImageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
}
}
private void init(final int pIndex) throws IOException {
if (mCurrentImage == -1 || pIndex != mCurrentImage || mReader.getInput() == null) {
init();
checkBounds(pIndex);
mCurrentImage = pIndex;
initReader(pIndex);
}
}
private void initReader(final int pIndex) throws IOException {
String name = mCatalog.getStreamName(pIndex);
Entry entry = mRoot.getChildEntry(name);
// TODO: It might be possible to speed this up, with less wrapping...
// Use in-memory input stream for max speed, images are small
ImageInputStream input = new MemoryCacheImageInputStream(entry.getInputStream());
input.skipBytes(THUMBNAIL_OFFSET);
mReader.setInput(input);
}
private void initReaderListeners() {
mReader.addIIOReadProgressListener(new ProgressListenerBase() {
@Override
public void imageComplete(ImageReader pSource) {
processImageComplete();
}
@Override
public void imageStarted(ImageReader pSource, int pImageIndex) {
processImageStarted(mCurrentImage);
}
@Override
public void imageProgress(ImageReader pSource, float pPercentageDone) {
processImageProgress(pPercentageDone);
}
@Override
public void readAborted(ImageReader pSource) {
processReadAborted();
}
});
// TODO: Update listeners
// TODO: Warning listeners
}
private void init() throws IOException {
assertInput();
if (mRoot == null) {
mRoot = new CompoundDocument(mImageInput).getRootEntry();
SortedSet children = mRoot.getChildEntries();
mThumbnails = new BufferedImage[children.size() - 1];
initCatalog();
// NOTE: This is usually slower, unless you need all images
// TODO: Use as many threads as there are CPU cores? :-)
if (mLoadEagerly) {
for (int i = 0; i < mThumbnails.length; i++) {
initReader(i);
ImageReader reader = mReader;
// TODO: If stream was detached, we could probably create a
// new reader, then fire this off in a separate thread...
mThumbnails[i] = reader.read(0, null);
}
}
}
}
private void initCatalog() throws IOException {
if (mCatalog == null) {
Entry catalog = mRoot.getChildEntry("Catalog");
if (catalog.length() <= 16L) {
// TODO: Throw exception? Return empty catalog?
}
mCatalog = Catalog.read(catalog.getInputStream());
}
}
public int getNumImages(boolean pAllowSearch) throws IOException {
if (pAllowSearch) {
init();
}
return mCatalog != null ? mCatalog.getThumbnailCount() : super.getNumImages(false);
}
public int getWidth(int pIndex) throws IOException {
init(pIndex);
BufferedImage image = mThumbnails[pIndex];
return image != null ? image.getWidth() : mReader.getWidth(0);
}
public int getHeight(int pIndex) throws IOException {
init(pIndex);
BufferedImage image = mThumbnails[pIndex];
return image != null ? image.getHeight() : mReader.getHeight(0);
}
public Iterator<ImageTypeSpecifier> getImageTypes(int pIndex) throws IOException {
init(pIndex);
initReader(pIndex);
return mReader.getImageTypes(0);
}
public boolean isPresent(final String pFileName) {
try {
init();
}
catch (IOException e) {
resetMembers();
return false;
}
// TODO: Rethink this...
// Seems to be up to Windows and the installed programs what formats
// are supported...
// Some thumbs are just icons, and it might be better to use ImageIO to create thumbs for these... :-/
// At least this seems fine for now
String extension = FileUtil.getExtension(pFileName);
if (StringUtil.isEmpty(extension)) {
return false;
}
extension = extension.toLowerCase();
return !"psd".equals(extension) && !"svg".equals(extension) && mCatalog != null && mCatalog.getIndex(pFileName) != -1;
}
/// Test code below
public static void main(String[] pArgs) throws IOException {
ThumbsDBImageReader reader = new ThumbsDBImageReader();
reader.setInput(ImageIO.createImageInputStream(new File(pArgs[0])));
// reader.setLoadEagerly(true);
if (pArgs.length > 1) {
long start = System.currentTimeMillis();
reader.init();
for (Catalog.CatalogItem item : reader.mCatalog) {
reader.read(item.getName(), null);
}
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start) + " ms");
}
else {
JFrame frame = createWindow(pArgs[0]);
JPanel panel = new JPanel();
panel.setBackground(Color.WHITE);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
long start = System.currentTimeMillis();
reader.init();
for (Catalog.CatalogItem item : reader.mCatalog) {
addImage(panel, reader, reader.mCatalog.getIndex(item.getName()), item.getName());
}
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start) + " ms");
frame.getContentPane().add(new JScrollPane(panel));
frame.pack();
frame.setVisible(true);
}
}
private static JFrame createWindow(final String pTitle) {
JFrame frame = new JFrame(pTitle);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
System.exit(0);
}
});
return frame;
}
private static void addImage(Container pParent, ImageReader pReader, int pImageNo, String pName) throws IOException {
final JLabel label = new JLabel();
final BufferedImage image = pReader.read(pImageNo);
label.setIcon(new Icon() {
private static final int SIZE = 110;
public void paintIcon(Component c, Graphics g, int x, int y) {
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.DARK_GRAY);
g.fillRoundRect(x, y, SIZE, SIZE, 10, 10);
g.drawImage(image, (SIZE - image.getWidth()) / 2 + x, (SIZE - image.getHeight()) / 2 + y, null);
}
public int getIconWidth() {
return SIZE;
}
public int getIconHeight() {
return SIZE;
}
});
label.setText("" + image.getWidth() + "x" + image.getHeight() + ": " + pName);
label.setToolTipText(image.toString());
pParent.add(label);
}
}
@@ -0,0 +1,179 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.thumbsdb;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.io.ole2.CompoundDocument;
import javax.imageio.ImageReader;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Locale;
/**
* ThumbsDBImageReaderSpi
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: ThumbsDBImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$
*/
public class ThumbsDBImageReaderSpi extends ImageReaderSpi {
private ImageReaderSpi mJPEGProvider;
/**
* Creates a {@code ThumbsDBImageReaderSpi}.
*/
public ThumbsDBImageReaderSpi() {
this(IIOUtil.getProviderInfo(ThumbsDBImageReaderSpi.class));
}
private ThumbsDBImageReaderSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(),
pProviderInfo.getVersion(),
new String[]{"thumbs", "THUMBS", "Thumbs DB"},
new String[]{"db"},
new String[]{"image/x-thumbs-db", "application/octet-stream"}, // TODO: Check IANA et al...
ThumbsDBImageReader.class.getName(),
STANDARD_INPUT_TYPE,
null,
true, null, null, null, null,
true, null, null, null, null
);
}
public boolean canDecodeInput(Object source) throws IOException {
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
}
public boolean canDecode(ImageInputStream pInput) throws IOException {
maybeInitJPEGProvider();
// If this is a OLE 2 CompoundDocument, we could try...
// TODO: How do we know it's thumbs.db format (structure), without reading quite a lot?
return mJPEGProvider != null && CompoundDocument.canRead(pInput);
}
public ImageReader createReaderInstance(Object extension) throws IOException {
return new ThumbsDBImageReader(this);
}
private void maybeInitJPEGProvider() {
// NOTE: Can't do this from constructor, as ImageIO itself is not initialized yet,
// and the lookup below will produce a NPE..
// TODO: A better approach...
// - Could have a list with known working JPEG decoders?
// - System property?
// - Class path lookup of properties file with reader?
// This way we could deregister immediately
if (mJPEGProvider == null) {
ImageReaderSpi provider = null;
try {
Iterator<ImageReaderSpi> providers = getJPEGProviders();
while (providers.hasNext()) {
provider = providers.next();
// Prefer the one we know
if ("Sun Microsystems, Inc.".equals(provider.getVendorName())) {
break;
}
}
}
catch (Exception ignore) {
// It's pretty safe to assume there's always a JPEG reader out there
// In any case, we deregister the provider if there isn't one
IIORegistry.getDefaultInstance().deregisterServiceProvider(this, ImageReaderSpi.class);
}
mJPEGProvider = provider;
}
}
private Iterator<ImageReaderSpi> getJPEGProviders() {
return IIORegistry.getDefaultInstance().getServiceProviders(
ImageReaderSpi.class,
new ServiceRegistry.Filter() {
public boolean filter(Object provider) {
if (provider instanceof ImageReaderSpi) {
ImageReaderSpi spi = (ImageReaderSpi) provider;
for (String format : spi.getFormatNames()) {
if ("JPEG".equals(format)) {
return true;
}
}
}
return false;
}
}, true
);
}
/**
* Returns a new {@code ImageReader} that can read JPEG images.
*
* @return a new {@code ImageReader} that can read JPEG images.
* @throws IllegalStateException if no JPEG provider was found
* @throws Error if the reader can't be instantiated
*/
ImageReader createJPEGReader() {
maybeInitJPEGProvider();
if (mJPEGProvider == null) {
throw new IllegalStateException("No suitable JPEG reader provider found");
}
try {
return mJPEGProvider.createReaderInstance();
}
catch (IOException e) {
// NOTE: The default Sun version never throws IOException here
throw new Error("Could not create JPEG reader: " + e.getMessage(), e);
}
}
public String getDescription(Locale locale) {
return "Microsoft Windows Thumbs DB (Thumbs.db) image reader";
}
// @Override
// public void onRegistration(ServiceRegistry registry, Class<?> category) {
// System.out.println("ThumbsDBImageReaderSpi.onRegistration");
// maybeInitJPEGProvider();
// if (mJPEGProvider == null) {
// System.out.println("Deregistering");
// registry.deregisterServiceProvider(this, ImageReaderSpi.class);
// }
// }
}
@@ -0,0 +1 @@
com.twelvemonkeys.imageio.plugins.thumbsdb.ThumbsDBImageReaderSpi