All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twelvemonkeys.imageio.plugins.thumbsdb.ThumbsDBImageReader Maven / Gradle / Ivy

/*
 * 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 of the copyright holder 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 HOLDER 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 Harald Kuhr
 * @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 Wikipedia: Thumbs.db
 */
public final class ThumbsDBImageReader extends ImageReaderBase {
    private static final int THUMBNAIL_OFFSET = 12;
    private Entry root;
    private Catalog catalog;

    private BufferedImage[] thumbnails;
    private final ImageReader reader;
    private int currentImage = -1;

    private boolean loadEagerly;

    public ThumbsDBImageReader() {
        this(new ThumbsDBImageReaderSpi());
    }

    protected ThumbsDBImageReader(final ThumbsDBImageReaderSpi pProvider) {
        super(pProvider);
        reader = createJPEGReader(pProvider);
        initReaderListeners();
    }

    protected void resetMembers() {
        root = null;
        catalog = null;
        thumbnails = null;
    }

    private static ImageReader createJPEGReader(final ThumbsDBImageReaderSpi pProvider) {
        return pProvider.createJPEGReader();
    }

    public void dispose() {
        reader.dispose();
        super.dispose();
    }

    public boolean isLoadEagerly() {
        return loadEagerly;
    }

    /**
     * Instructs the reader wether it should read and cache alle thumbnails
     * in sequence, during the first read operation.
     * 

* 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) { loadEagerly = 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 < thumbnails.length) { image = thumbnails[pIndex]; } if (image == null) { // Read the image, it's a JFIF stream, inside the OLE 2 CompoundDocument init(pIndex); image = reader.read(0, pParam); reader.reset(); if (pParam == null) { thumbnails[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 = catalog.getIndex(pName); if (index < 0) { throw new FileNotFoundException("Name not found in \"Catalog\" entry: " + pName); } return read(index, pParam); } public void abort() { super.abort(); reader.abort(); } @Override public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { super.setInput(input, seekForwardOnly, ignoreMetadata); if (imageInput != null) { imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); } } private void init(final int pIndex) throws IOException { if (currentImage == -1 || pIndex != currentImage || reader.getInput() == null) { init(); checkBounds(pIndex); currentImage = pIndex; initReader(pIndex); } } private void initReader(final int pIndex) throws IOException { init(); String name = catalog.getStreamName(pIndex); Entry entry = root.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); reader.setInput(input); } private void initReaderListeners() { reader.addIIOReadProgressListener(new ProgressListenerBase() { @Override public void imageComplete(ImageReader pSource) { processImageComplete(); } @Override public void imageStarted(ImageReader pSource, int pImageIndex) { processImageStarted(currentImage); } @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 (root == null) { root = new CompoundDocument(imageInput).getRootEntry(); SortedSet children = root.getChildEntries(); thumbnails = 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 (loadEagerly) { for (int i = 0; i < thumbnails.length; i++) { initReader(i); ImageReader reader = this.reader; // TODO: If stream was detached, we could probably create a // new reader, then fire this off in a separate thread... thumbnails[i] = reader.read(0, null); } } } } private void initCatalog() throws IOException { if (catalog == null) { Entry catalog = root.getChildEntry("Catalog"); if (catalog.length() <= 16L) { // TODO: Throw exception? Return empty catalog? } this.catalog = Catalog.read(catalog.getInputStream()); } } public int getNumImages(boolean allowSearch) throws IOException { if (allowSearch) { init(); } return catalog != null ? catalog.getThumbnailCount() : super.getNumImages(false); } public int getWidth(int pIndex) throws IOException { init(pIndex); BufferedImage image = thumbnails[pIndex]; return image != null ? image.getWidth() : reader.getWidth(0); } public int getHeight(int pIndex) throws IOException { init(pIndex); BufferedImage image = thumbnails[pIndex]; return image != null ? image.getHeight() : reader.getHeight(0); } public Iterator getImageTypes(int pIndex) throws IOException { init(pIndex); initReader(pIndex); return reader.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) && catalog != null && catalog.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.catalog) { 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.catalog) { addImage(panel, reader, reader.catalog.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); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy