source: josm/trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java@ 6127

Last change on this file since 6127 was 6127, checked in by bastiK, 11 years ago

applied #8895 - Upgrade metadata-extractor to v. 2.6.4 (patch by ebourg)

  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 14.6 KB
Line 
1/*
2 * Copyright 2002-2012 Drew Noakes
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * More information about this project is available at:
17 *
18 * http://drewnoakes.com/code/exif/
19 * http://code.google.com/p/metadata-extractor/
20 */
21
22package com.drew.metadata.exif;
23
24import com.drew.lang.annotations.NotNull;
25import com.drew.lang.annotations.Nullable;
26import com.drew.metadata.Directory;
27import com.drew.metadata.MetadataException;
28
29import java.io.FileOutputStream;
30import java.io.IOException;
31import java.util.HashMap;
32
33/**
34 * One of several Exif directories. Otherwise known as IFD1, this directory holds information about an embedded thumbnail image.
35 *
36 * @author Drew Noakes http://drewnoakes.com
37 */
38public class ExifThumbnailDirectory extends Directory
39{
40 public static final int TAG_THUMBNAIL_IMAGE_WIDTH = 0x0100;
41 public static final int TAG_THUMBNAIL_IMAGE_HEIGHT = 0x0101;
42
43 /**
44 * When image format is no compression, this value shows the number of bits
45 * per component for each pixel. Usually this value is '8,8,8'.
46 */
47 public static final int TAG_BITS_PER_SAMPLE = 0x0102;
48
49 /**
50 * Shows compression method for Thumbnail.
51 * 1 = Uncompressed
52 * 2 = CCITT 1D
53 * 3 = T4/Group 3 Fax
54 * 4 = T6/Group 4 Fax
55 * 5 = LZW
56 * 6 = JPEG (old-style)
57 * 7 = JPEG
58 * 8 = Adobe Deflate
59 * 9 = JBIG B&W
60 * 10 = JBIG Color
61 * 32766 = Next
62 * 32771 = CCIRLEW
63 * 32773 = PackBits
64 * 32809 = Thunderscan
65 * 32895 = IT8CTPAD
66 * 32896 = IT8LW
67 * 32897 = IT8MP
68 * 32898 = IT8BL
69 * 32908 = PixarFilm
70 * 32909 = PixarLog
71 * 32946 = Deflate
72 * 32947 = DCS
73 * 34661 = JBIG
74 * 34676 = SGILog
75 * 34677 = SGILog24
76 * 34712 = JPEG 2000
77 * 34713 = Nikon NEF Compressed
78 */
79 public static final int TAG_THUMBNAIL_COMPRESSION = 0x0103;
80
81 /**
82 * Shows the color space of the image data components.
83 * 0 = WhiteIsZero
84 * 1 = BlackIsZero
85 * 2 = RGB
86 * 3 = RGB Palette
87 * 4 = Transparency Mask
88 * 5 = CMYK
89 * 6 = YCbCr
90 * 8 = CIELab
91 * 9 = ICCLab
92 * 10 = ITULab
93 * 32803 = Color Filter Array
94 * 32844 = Pixar LogL
95 * 32845 = Pixar LogLuv
96 * 34892 = Linear Raw
97 */
98 public static final int TAG_PHOTOMETRIC_INTERPRETATION = 0x0106;
99
100 /** The position in the file of raster data. */
101 public static final int TAG_STRIP_OFFSETS = 0x0111;
102 public static final int TAG_ORIENTATION = 0x0112;
103 /** Each pixel is composed of this many samples. */
104 public static final int TAG_SAMPLES_PER_PIXEL = 0x0115;
105 /** The raster is codified by a single block of data holding this many rows. */
106 public static final int TAG_ROWS_PER_STRIP = 0x116;
107 /** The size of the raster data in bytes. */
108 public static final int TAG_STRIP_BYTE_COUNTS = 0x0117;
109 /**
110 * When image format is no compression YCbCr, this value shows byte aligns of
111 * YCbCr data. If value is '1', Y/Cb/Cr value is chunky format, contiguous for
112 * each subsampling pixel. If value is '2', Y/Cb/Cr value is separated and
113 * stored to Y plane/Cb plane/Cr plane format.
114 */
115 public static final int TAG_X_RESOLUTION = 0x011A;
116 public static final int TAG_Y_RESOLUTION = 0x011B;
117 public static final int TAG_PLANAR_CONFIGURATION = 0x011C;
118 public static final int TAG_RESOLUTION_UNIT = 0x0128;
119 /** The offset to thumbnail image bytes. */
120 public static final int TAG_THUMBNAIL_OFFSET = 0x0201;
121 /** The size of the thumbnail image data in bytes. */
122 public static final int TAG_THUMBNAIL_LENGTH = 0x0202;
123 public static final int TAG_YCBCR_COEFFICIENTS = 0x0211;
124 public static final int TAG_YCBCR_SUBSAMPLING = 0x0212;
125 public static final int TAG_YCBCR_POSITIONING = 0x0213;
126 public static final int TAG_REFERENCE_BLACK_WHITE = 0x0214;
127
128 @NotNull
129 protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
130
131 static
132 {
133 _tagNameMap.put(TAG_THUMBNAIL_IMAGE_WIDTH, "Thumbnail Image Width");
134 _tagNameMap.put(TAG_THUMBNAIL_IMAGE_HEIGHT, "Thumbnail Image Height");
135 _tagNameMap.put(TAG_BITS_PER_SAMPLE, "Bits Per Sample");
136 _tagNameMap.put(TAG_THUMBNAIL_COMPRESSION, "Thumbnail Compression");
137 _tagNameMap.put(TAG_PHOTOMETRIC_INTERPRETATION, "Photometric Interpretation");
138 _tagNameMap.put(TAG_STRIP_OFFSETS, "Strip Offsets");
139 _tagNameMap.put(TAG_ORIENTATION, "Orientation");
140 _tagNameMap.put(TAG_SAMPLES_PER_PIXEL, "Samples Per Pixel");
141 _tagNameMap.put(TAG_ROWS_PER_STRIP, "Rows Per Strip");
142 _tagNameMap.put(TAG_STRIP_BYTE_COUNTS, "Strip Byte Counts");
143 _tagNameMap.put(TAG_X_RESOLUTION, "X Resolution");
144 _tagNameMap.put(TAG_Y_RESOLUTION, "Y Resolution");
145 _tagNameMap.put(TAG_PLANAR_CONFIGURATION, "Planar Configuration");
146 _tagNameMap.put(TAG_RESOLUTION_UNIT, "Resolution Unit");
147 _tagNameMap.put(TAG_THUMBNAIL_OFFSET, "Thumbnail Offset");
148 _tagNameMap.put(TAG_THUMBNAIL_LENGTH, "Thumbnail Length");
149 _tagNameMap.put(TAG_YCBCR_COEFFICIENTS, "YCbCr Coefficients");
150 _tagNameMap.put(TAG_YCBCR_SUBSAMPLING, "YCbCr Sub-Sampling");
151 _tagNameMap.put(TAG_YCBCR_POSITIONING, "YCbCr Positioning");
152 _tagNameMap.put(TAG_REFERENCE_BLACK_WHITE, "Reference Black/White");
153 }
154
155 @Nullable
156 private byte[] _thumbnailData;
157
158 public ExifThumbnailDirectory()
159 {
160 this.setDescriptor(new ExifThumbnailDescriptor(this));
161 }
162
163 @NotNull
164 public String getName()
165 {
166 return "Exif Thumbnail";
167 }
168
169 @NotNull
170 protected HashMap<Integer, String> getTagNameMap()
171 {
172 return _tagNameMap;
173 }
174
175 public boolean hasThumbnailData()
176 {
177 return _thumbnailData != null;
178 }
179
180 @Nullable
181 public byte[] getThumbnailData()
182 {
183 return _thumbnailData;
184 }
185
186 public void setThumbnailData(@Nullable byte[] data)
187 {
188 _thumbnailData = data;
189 }
190
191 public void writeThumbnail(@NotNull String filename) throws MetadataException, IOException
192 {
193 byte[] data = _thumbnailData;
194
195 if (data==null)
196 throw new MetadataException("No thumbnail data exists.");
197
198 FileOutputStream stream = null;
199 try {
200 stream = new FileOutputStream(filename);
201 stream.write(data);
202 } finally {
203 if (stream!=null)
204 stream.close();
205 }
206 }
207
208/*
209 // This thumbnail extraction code is not complete, and is included to assist anyone who feels like looking into
210 // it. Please share any progress with the original author, and hence the community. Thanks.
211
212 public Image getThumbnailImage() throws MetadataException
213 {
214 if (!hasThumbnailData())
215 return null;
216
217 int compression = 0;
218 try {
219 compression = this.getInt(ExifSubIFDDirectory.TAG_COMPRESSION);
220 } catch (Throwable e) {
221 this.addError("Unable to determine thumbnail type " + e.getMessage());
222 }
223
224 final byte[] thumbnailBytes = getThumbnailData();
225
226 if (compression == ExifSubIFDDirectory.COMPRESSION_JPEG)
227 {
228 // JPEG Thumbnail
229 // operate directly on thumbnailBytes
230 return decodeBytesAsImage(thumbnailBytes);
231 }
232 else if (compression == ExifSubIFDDirectory.COMPRESSION_NONE)
233 {
234 // uncompressed thumbnail (raw RGB data)
235 if (!this.containsTag(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION))
236 return null;
237
238 try
239 {
240 // If the image is RGB format, then convert it to a bitmap
241 final int photometricInterpretation = this.getInt(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
242 if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_RGB)
243 {
244 // RGB
245 Image image = createImageFromRawRgb(thumbnailBytes);
246 return image;
247 }
248 else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_YCBCR)
249 {
250 // YCbCr
251 Image image = createImageFromRawYCbCr(thumbnailBytes);
252 return image;
253 }
254 else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_MONOCHROME)
255 {
256 // Monochrome
257 return null;
258 }
259 } catch (Throwable e) {
260 this.addError("Unable to extract thumbnail: " + e.getMessage());
261 }
262 }
263 return null;
264 }
265
266 /**
267 * Handle the YCbCr thumbnail encoding used by Ricoh RDC4200/4300, Fuji DS-7/300 and DX-5/7/9 cameras.
268 *
269 * At DX-5/7/9, YCbCrSubsampling(0x0212) has values of '2,1', PlanarConfiguration(0x011c) has a value '1'. So the
270 * data align of this image is below.
271 *
272 * 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). . . .
273 *
274 * The numbers in parenthesis are pixel coordinates. DX series' YCbCrCoefficients(0x0211) has values '0.299/0.587/0.114',
275 * ReferenceBlackWhite(0x0214) has values '0,255,128,255,128,255'. Therefore to convert from Y/Cb/Cr to RGB is;
276 *
277 * B(0,0)=(Cb-128)*(2-0.114*2)+Y(0,0)
278 * R(0,0)=(Cr-128)*(2-0.299*2)+Y(0,0)
279 * G(0,0)=(Y(0,0)-0.114*B(0,0)-0.299*R(0,0))/0.587
280 *
281 * 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).
282 * Repeat this conversion by value of ImageWidth(0x0100) and ImageLength(0x0101).
283 *
284 * @param thumbnailBytes
285 * @return
286 * @throws com.drew.metadata.MetadataException
287 * /
288 private Image createImageFromRawYCbCr(byte[] thumbnailBytes) throws MetadataException
289 {
290 /*
291 Y = 0.257R + 0.504G + 0.098B + 16
292 Cb = -0.148R - 0.291G + 0.439B + 128
293 Cr = 0.439R - 0.368G - 0.071B + 128
294
295 G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
296 R = 1.164(Y-16) + 1.596(Cr-128)
297 B = 1.164(Y-16) + 2.018(Cb-128)
298
299 R, G and B range from 0 to 255.
300 Y ranges from 16 to 235.
301 Cb and Cr range from 16 to 240.
302
303 http://www.faqs.org/faqs/graphics/colorspace-faq/
304 * /
305
306 int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
307 final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
308 final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
309// final int headerLength = 54;
310// byte[] result = new byte[length + headerLength];
311// // Add a windows BMP header described:
312// // http://www.onicos.com/staff/iz/formats/bmp.html
313// result[0] = 'B';
314// result[1] = 'M'; // File Type identifier
315// result[3] = (byte)(result.length / 256);
316// result[2] = (byte)result.length;
317// result[10] = (byte)headerLength;
318// result[14] = 40; // MS Windows BMP header
319// result[18] = (byte)imageWidth;
320// result[22] = (byte)imageHeight;
321// result[26] = 1; // 1 Plane
322// result[28] = 24; // Colour depth
323// result[34] = (byte)length;
324// result[35] = (byte)(length / 256);
325
326 final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
327
328 // order is YCbCr and image is upside down, bitmaps are BGR
329//// for (int i = headerLength, dataOffset = length; i<result.length; i += 3, dataOffset -= 3)
330// {
331// final int y = thumbnailBytes[dataOffset - 2] & 0xFF;
332// final int cb = thumbnailBytes[dataOffset - 1] & 0xFF;
333// final int cr = thumbnailBytes[dataOffset] & 0xFF;
334// if (y<16 || y>235 || cb<16 || cb>240 || cr<16 || cr>240)
335// "".toString();
336//
337// int g = (int)(1.164*(y-16) - 0.391*(cb-128) - 0.813*(cr-128));
338// int r = (int)(1.164*(y-16) + 1.596*(cr-128));
339// int b = (int)(1.164*(y-16) + 2.018*(cb-128));
340//
341//// result[i] = (byte)b;
342//// result[i + 1] = (byte)g;
343//// result[i + 2] = (byte)r;
344//
345// // TODO compose the image here
346// image.setRGB(1, 2, 3);
347// }
348
349 return image;
350 }
351
352 /**
353 * Creates a thumbnail image in (Windows) BMP format from raw RGB data.
354 * @param thumbnailBytes
355 * @return
356 * @throws com.drew.metadata.MetadataException
357 * /
358 private Image createImageFromRawRgb(byte[] thumbnailBytes) throws MetadataException
359 {
360 final int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
361 final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
362 final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
363// final int headerLength = 54;
364// final byte[] result = new byte[length + headerLength];
365// // Add a windows BMP header described:
366// // http://www.onicos.com/staff/iz/formats/bmp.html
367// result[0] = 'B';
368// result[1] = 'M'; // File Type identifier
369// result[3] = (byte)(result.length / 256);
370// result[2] = (byte)result.length;
371// result[10] = (byte)headerLength;
372// result[14] = 40; // MS Windows BMP header
373// result[18] = (byte)imageWidth;
374// result[22] = (byte)imageHeight;
375// result[26] = 1; // 1 Plane
376// result[28] = 24; // Colour depth
377// result[34] = (byte)length;
378// result[35] = (byte)(length / 256);
379
380 final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
381
382 // order is RGB and image is upside down, bitmaps are BGR
383// for (int i = headerLength, dataOffset = length; i<result.length; i += 3, dataOffset -= 3)
384// {
385// byte b = thumbnailBytes[dataOffset - 2];
386// byte g = thumbnailBytes[dataOffset - 1];
387// byte r = thumbnailBytes[dataOffset];
388//
389// // TODO compose the image here
390// image.setRGB(1, 2, 3);
391// }
392
393 return image;
394 }
395*/
396}
Note: See TracBrowser for help on using the repository browser.