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

com.drew.metadata.exif.ExifThumbnailDirectory Maven / Gradle / Ivy

Go to download

Java library for extracting EXIF, IPTC, XMP, ICC and other metadata from image and video files.

There is a newer version: 2.19.0
Show newest version
/*
 * Copyright 2002-2015 Drew Noakes
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 *
 * More information about this project is available at:
 *
 *    https://drewnoakes.com/code/exif/
 *    https://github.com/drewnoakes/metadata-extractor
 */

package com.drew.metadata.exif;

import com.drew.lang.annotations.NotNull;
import com.drew.lang.annotations.Nullable;
import com.drew.metadata.MetadataException;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;

/**
 * One of several Exif directories.  Otherwise known as IFD1, this directory holds information about an embedded thumbnail image.
 *
 * @author Drew Noakes https://drewnoakes.com
 */
public class ExifThumbnailDirectory extends ExifDirectoryBase
{
    /**
     * The offset to thumbnail image bytes.
     */
    public static final int TAG_THUMBNAIL_OFFSET = 0x0201;
    /**
     * The size of the thumbnail image data in bytes.
     */
    public static final int TAG_THUMBNAIL_LENGTH = 0x0202;

    /**
     * Shows compression method for Thumbnail.
     * 1 = Uncompressed
     * 2 = CCITT 1D
     * 3 = T4/Group 3 Fax
     * 4 = T6/Group 4 Fax
     * 5 = LZW
     * 6 = JPEG (old-style)
     * 7 = JPEG
     * 8 = Adobe Deflate
     * 9 = JBIG B&W
     * 10 = JBIG Color
     * 32766 = Next
     * 32771 = CCIRLEW
     * 32773 = PackBits
     * 32809 = Thunderscan
     * 32895 = IT8CTPAD
     * 32896 = IT8LW
     * 32897 = IT8MP
     * 32898 = IT8BL
     * 32908 = PixarFilm
     * 32909 = PixarLog
     * 32946 = Deflate
     * 32947 = DCS
     * 34661 = JBIG
     * 34676 = SGILog
     * 34677 = SGILog24
     * 34712 = JPEG 2000
     * 34713 = Nikon NEF Compressed
     */
    public static final int TAG_THUMBNAIL_COMPRESSION = 0x0103;

    @NotNull
    protected static final HashMap _tagNameMap = new HashMap();

    static
    {
        addExifTagNames(_tagNameMap);

        _tagNameMap.put(TAG_THUMBNAIL_COMPRESSION, "Thumbnail Compression");
        _tagNameMap.put(TAG_THUMBNAIL_OFFSET, "Thumbnail Offset");
        _tagNameMap.put(TAG_THUMBNAIL_LENGTH, "Thumbnail Length");
    }

    @Nullable
    private byte[] _thumbnailData;

    public ExifThumbnailDirectory()
    {
        this.setDescriptor(new ExifThumbnailDescriptor(this));
    }

    @Override
    @NotNull
    public String getName()
    {
        return "Exif Thumbnail";
    }

    @Override
    @NotNull
    protected HashMap getTagNameMap()
    {
        return _tagNameMap;
    }

    public boolean hasThumbnailData()
    {
        return _thumbnailData != null;
    }

    @Nullable
    public byte[] getThumbnailData()
    {
        return _thumbnailData;
    }

    public void setThumbnailData(@Nullable byte[] data)
    {
        _thumbnailData = data;
    }

    public void writeThumbnail(@NotNull String filename) throws MetadataException, IOException
    {
        byte[] data = _thumbnailData;

        if (data == null)
            throw new MetadataException("No thumbnail data exists.");

        FileOutputStream stream = null;
        try {
            stream = new FileOutputStream(filename);
            stream.write(data);
        } finally {
            if (stream != null)
                stream.close();
        }
    }

/*
    // This thumbnail extraction code is not complete, and is included to assist anyone who feels like looking into
    // it.  Please share any progress with the original author, and hence the community.  Thanks.

    public Image getThumbnailImage() throws MetadataException
    {
        if (!hasThumbnailData())
            return null;

        int compression = 0;
        try {
            compression = this.getInt(ExifSubIFDDirectory.TAG_COMPRESSION);
        } catch (Throwable e) {
            this.addError("Unable to determine thumbnail type " + e.getMessage());
        }

        final byte[] thumbnailBytes = getThumbnailData();

        if (compression == ExifSubIFDDirectory.COMPRESSION_JPEG)
        {
            // JPEG Thumbnail
            // operate directly on thumbnailBytes
            return decodeBytesAsImage(thumbnailBytes);
        }
        else if (compression == ExifSubIFDDirectory.COMPRESSION_NONE)
        {
            // uncompressed thumbnail (raw RGB data)
            if (!this.containsTag(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION))
                return null;

            try
            {
                // If the image is RGB format, then convert it to a bitmap
                final int photometricInterpretation = this.getInt(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
                if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_RGB)
                {
                    // RGB
                    Image image = createImageFromRawRgb(thumbnailBytes);
                    return image;
                }
                else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_YCBCR)
                {
                    // YCbCr
                    Image image = createImageFromRawYCbCr(thumbnailBytes);
                    return image;
                }
                else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_MONOCHROME)
                {
                    // Monochrome
                    return null;
                }
            } catch (Throwable e) {
                this.addError("Unable to extract thumbnail: " + e.getMessage());
            }
        }
        return null;
    }

    /**
     * Handle the YCbCr thumbnail encoding used by Ricoh RDC4200/4300, Fuji DS-7/300 and DX-5/7/9 cameras.
     *
     * At DX-5/7/9, YCbCrSubsampling(0x0212) has values of '2,1', PlanarConfiguration(0x011c) has a value '1'. So the
     * data align of this image is below.
     *
     * Y(0,0),Y(1,0),Cb(0,0),Cr(0,0), Y(2,0),Y(3,0),Cb(2,0),Cr(3.0), Y(4,0),Y(5,0),Cb(4,0),Cr(4,0). . . .
     *
     * The numbers in parenthesis are pixel coordinates. DX series' YCbCrCoefficients(0x0211) has values '0.299/0.587/0.114',
     * ReferenceBlackWhite(0x0214) has values '0,255,128,255,128,255'. Therefore to convert from Y/Cb/Cr to RGB is;
     *
     * B(0,0)=(Cb-128)*(2-0.114*2)+Y(0,0)
     * R(0,0)=(Cr-128)*(2-0.299*2)+Y(0,0)
     * G(0,0)=(Y(0,0)-0.114*B(0,0)-0.299*R(0,0))/0.587
     *
     * Horizontal subsampling is a value '2', so you can calculate B(1,0)/R(1,0)/G(1,0) by using the Y(1,0) and Cr(0,0)/Cb(0,0).
     * Repeat this conversion by value of ImageWidth(0x0100) and ImageLength(0x0101).
     *
     * @param thumbnailBytes
     * @return
     * @throws com.drew.metadata.MetadataException
     * /
    private Image createImageFromRawYCbCr(byte[] thumbnailBytes) throws MetadataException
    {
        /*
            Y  =  0.257R + 0.504G + 0.098B + 16
            Cb = -0.148R - 0.291G + 0.439B + 128
            Cr =  0.439R - 0.368G - 0.071B + 128

            G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
            R = 1.164(Y-16) + 1.596(Cr-128)
            B = 1.164(Y-16) + 2.018(Cb-128)

            R, G and B range from 0 to 255.
            Y ranges from 16 to 235.
            Cb and Cr range from 16 to 240.

            http://www.faqs.org/faqs/graphics/colorspace-faq/
        * /

        int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
        final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
        final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
//        final int headerLength = 54;
//        byte[] result = new byte[length + headerLength];
//        // Add a windows BMP header described:
//        // http://www.onicos.com/staff/iz/formats/bmp.html
//        result[0] = 'B';
//        result[1] = 'M'; // File Type identifier
//        result[3] = (byte)(result.length / 256);
//        result[2] = (byte)result.length;
//        result[10] = (byte)headerLength;
//        result[14] = 40; // MS Windows BMP header
//        result[18] = (byte)imageWidth;
//        result[22] = (byte)imageHeight;
//        result[26] = 1;  // 1 Plane
//        result[28] = 24; // Colour depth
//        result[34] = (byte)length;
//        result[35] = (byte)(length / 256);

        final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);

        // order is YCbCr and image is upside down, bitmaps are BGR
////        for (int i = headerLength, dataOffset = length; i235 || cb<16 || cb>240 || cr<16 || cr>240)
//                "".toString();
//
//            int g = (int)(1.164*(y-16) - 0.391*(cb-128) - 0.813*(cr-128));
//            int r = (int)(1.164*(y-16) + 1.596*(cr-128));
//            int b = (int)(1.164*(y-16) + 2.018*(cb-128));
//
////            result[i] = (byte)b;
////            result[i + 1] = (byte)g;
////            result[i + 2] = (byte)r;
//
//            // TODO compose the image here
//            image.setRGB(1, 2, 3);
//        }

        return image;
    }

    /**
     * Creates a thumbnail image in (Windows) BMP format from raw RGB data.
     * @param thumbnailBytes
     * @return
     * @throws com.drew.metadata.MetadataException
     * /
    private Image createImageFromRawRgb(byte[] thumbnailBytes) throws MetadataException
    {
        final int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
        final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
        final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
//        final int headerLength = 54;
//        final byte[] result = new byte[length + headerLength];
//        // Add a windows BMP header described:
//        // http://www.onicos.com/staff/iz/formats/bmp.html
//        result[0] = 'B';
//        result[1] = 'M'; // File Type identifier
//        result[3] = (byte)(result.length / 256);
//        result[2] = (byte)result.length;
//        result[10] = (byte)headerLength;
//        result[14] = 40; // MS Windows BMP header
//        result[18] = (byte)imageWidth;
//        result[22] = (byte)imageHeight;
//        result[26] = 1;  // 1 Plane
//        result[28] = 24; // Colour depth
//        result[34] = (byte)length;
//        result[35] = (byte)(length / 256);

        final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);

        // order is RGB and image is upside down, bitmaps are BGR
//        for (int i = headerLength, dataOffset = length; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy