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

Last change on this file since 8132 was 8132, checked in by Don-vip, 11 years ago

fix #11162 - update to metadata-extractor 2.7.2

  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 14.7 KB
Line 
1/*
2 * Copyright 2002-2015 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 * https://drewnoakes.com/code/exif/
19 * https://github.com/drewnoakes/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 https://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 /**
101 * The position in the file of raster data.
102 */
103 public static final int TAG_STRIP_OFFSETS = 0x0111;
104 public static final int TAG_ORIENTATION = 0x0112;
105 /**
106 * Each pixel is composed of this many samples.
107 */
108 public static final int TAG_SAMPLES_PER_PIXEL = 0x0115;
109 /**
110 * The raster is codified by a single block of data holding this many rows.
111 */
112 public static final int TAG_ROWS_PER_STRIP = 0x116;
113 /**
114 * The size of the raster data in bytes.
115 */
116 public static final int TAG_STRIP_BYTE_COUNTS = 0x0117;
117 /**
118 * When image format is no compression YCbCr, this value shows byte aligns of
119 * YCbCr data. If value is '1', Y/Cb/Cr value is chunky format, contiguous for
120 * each subsampling pixel. If value is '2', Y/Cb/Cr value is separated and
121 * stored to Y plane/Cb plane/Cr plane format.
122 */
123 public static final int TAG_X_RESOLUTION = 0x011A;
124 public static final int TAG_Y_RESOLUTION = 0x011B;
125 public static final int TAG_PLANAR_CONFIGURATION = 0x011C;
126 public static final int TAG_RESOLUTION_UNIT = 0x0128;
127 /**
128 * The offset to thumbnail image bytes.
129 */
130 public static final int TAG_THUMBNAIL_OFFSET = 0x0201;
131 /**
132 * The size of the thumbnail image data in bytes.
133 */
134 public static final int TAG_THUMBNAIL_LENGTH = 0x0202;
135 public static final int TAG_YCBCR_COEFFICIENTS = 0x0211;
136 public static final int TAG_YCBCR_SUBSAMPLING = 0x0212;
137 public static final int TAG_YCBCR_POSITIONING = 0x0213;
138 public static final int TAG_REFERENCE_BLACK_WHITE = 0x0214;
139
140 @NotNull
141 protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
142
143 static {
144 _tagNameMap.put(TAG_THUMBNAIL_IMAGE_WIDTH, "Thumbnail Image Width");
145 _tagNameMap.put(TAG_THUMBNAIL_IMAGE_HEIGHT, "Thumbnail Image Height");
146 _tagNameMap.put(TAG_BITS_PER_SAMPLE, "Bits Per Sample");
147 _tagNameMap.put(TAG_THUMBNAIL_COMPRESSION, "Thumbnail Compression");
148 _tagNameMap.put(TAG_PHOTOMETRIC_INTERPRETATION, "Photometric Interpretation");
149 _tagNameMap.put(TAG_STRIP_OFFSETS, "Strip Offsets");
150 _tagNameMap.put(TAG_ORIENTATION, "Orientation");
151 _tagNameMap.put(TAG_SAMPLES_PER_PIXEL, "Samples Per Pixel");
152 _tagNameMap.put(TAG_ROWS_PER_STRIP, "Rows Per Strip");
153 _tagNameMap.put(TAG_STRIP_BYTE_COUNTS, "Strip Byte Counts");
154 _tagNameMap.put(TAG_X_RESOLUTION, "X Resolution");
155 _tagNameMap.put(TAG_Y_RESOLUTION, "Y Resolution");
156 _tagNameMap.put(TAG_PLANAR_CONFIGURATION, "Planar Configuration");
157 _tagNameMap.put(TAG_RESOLUTION_UNIT, "Resolution Unit");
158 _tagNameMap.put(TAG_THUMBNAIL_OFFSET, "Thumbnail Offset");
159 _tagNameMap.put(TAG_THUMBNAIL_LENGTH, "Thumbnail Length");
160 _tagNameMap.put(TAG_YCBCR_COEFFICIENTS, "YCbCr Coefficients");
161 _tagNameMap.put(TAG_YCBCR_SUBSAMPLING, "YCbCr Sub-Sampling");
162 _tagNameMap.put(TAG_YCBCR_POSITIONING, "YCbCr Positioning");
163 _tagNameMap.put(TAG_REFERENCE_BLACK_WHITE, "Reference Black/White");
164 }
165
166 @Nullable
167 private byte[] _thumbnailData;
168
169 public ExifThumbnailDirectory()
170 {
171 this.setDescriptor(new ExifThumbnailDescriptor(this));
172 }
173
174 @Override
175 @NotNull
176 public String getName()
177 {
178 return "Exif Thumbnail";
179 }
180
181 @Override
182 @NotNull
183 protected HashMap<Integer, String> getTagNameMap()
184 {
185 return _tagNameMap;
186 }
187
188 public boolean hasThumbnailData()
189 {
190 return _thumbnailData != null;
191 }
192
193 @Nullable
194 public byte[] getThumbnailData()
195 {
196 return _thumbnailData;
197 }
198
199 public void setThumbnailData(@Nullable byte[] data)
200 {
201 _thumbnailData = data;
202 }
203
204 public void writeThumbnail(@NotNull String filename) throws MetadataException, IOException
205 {
206 byte[] data = _thumbnailData;
207
208 if (data == null)
209 throw new MetadataException("No thumbnail data exists.");
210
211 FileOutputStream stream = null;
212 try {
213 stream = new FileOutputStream(filename);
214 stream.write(data);
215 } finally {
216 if (stream != null)
217 stream.close();
218 }
219 }
220
221/*
222 // This thumbnail extraction code is not complete, and is included to assist anyone who feels like looking into
223 // it. Please share any progress with the original author, and hence the community. Thanks.
224
225 public Image getThumbnailImage() throws MetadataException
226 {
227 if (!hasThumbnailData())
228 return null;
229
230 int compression = 0;
231 try {
232 compression = this.getInt(ExifSubIFDDirectory.TAG_COMPRESSION);
233 } catch (Throwable e) {
234 this.addError("Unable to determine thumbnail type " + e.getMessage());
235 }
236
237 final byte[] thumbnailBytes = getThumbnailData();
238
239 if (compression == ExifSubIFDDirectory.COMPRESSION_JPEG)
240 {
241 // JPEG Thumbnail
242 // operate directly on thumbnailBytes
243 return decodeBytesAsImage(thumbnailBytes);
244 }
245 else if (compression == ExifSubIFDDirectory.COMPRESSION_NONE)
246 {
247 // uncompressed thumbnail (raw RGB data)
248 if (!this.containsTag(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION))
249 return null;
250
251 try
252 {
253 // If the image is RGB format, then convert it to a bitmap
254 final int photometricInterpretation = this.getInt(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
255 if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_RGB)
256 {
257 // RGB
258 Image image = createImageFromRawRgb(thumbnailBytes);
259 return image;
260 }
261 else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_YCBCR)
262 {
263 // YCbCr
264 Image image = createImageFromRawYCbCr(thumbnailBytes);
265 return image;
266 }
267 else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_MONOCHROME)
268 {
269 // Monochrome
270 return null;
271 }
272 } catch (Throwable e) {
273 this.addError("Unable to extract thumbnail: " + e.getMessage());
274 }
275 }
276 return null;
277 }
278
279 /**
280 * Handle the YCbCr thumbnail encoding used by Ricoh RDC4200/4300, Fuji DS-7/300 and DX-5/7/9 cameras.
281 *
282 * At DX-5/7/9, YCbCrSubsampling(0x0212) has values of '2,1', PlanarConfiguration(0x011c) has a value '1'. So the
283 * data align of this image is below.
284 *
285 * 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). . . .
286 *
287 * The numbers in parenthesis are pixel coordinates. DX series' YCbCrCoefficients(0x0211) has values '0.299/0.587/0.114',
288 * ReferenceBlackWhite(0x0214) has values '0,255,128,255,128,255'. Therefore to convert from Y/Cb/Cr to RGB is;
289 *
290 * B(0,0)=(Cb-128)*(2-0.114*2)+Y(0,0)
291 * R(0,0)=(Cr-128)*(2-0.299*2)+Y(0,0)
292 * G(0,0)=(Y(0,0)-0.114*B(0,0)-0.299*R(0,0))/0.587
293 *
294 * 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).
295 * Repeat this conversion by value of ImageWidth(0x0100) and ImageLength(0x0101).
296 *
297 * @param thumbnailBytes
298 * @return
299 * @throws com.drew.metadata.MetadataException
300 * /
301 private Image createImageFromRawYCbCr(byte[] thumbnailBytes) throws MetadataException
302 {
303 /*
304 Y = 0.257R + 0.504G + 0.098B + 16
305 Cb = -0.148R - 0.291G + 0.439B + 128
306 Cr = 0.439R - 0.368G - 0.071B + 128
307
308 G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
309 R = 1.164(Y-16) + 1.596(Cr-128)
310 B = 1.164(Y-16) + 2.018(Cb-128)
311
312 R, G and B range from 0 to 255.
313 Y ranges from 16 to 235.
314 Cb and Cr range from 16 to 240.
315
316 http://www.faqs.org/faqs/graphics/colorspace-faq/
317 * /
318
319 int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
320 final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
321 final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
322// final int headerLength = 54;
323// byte[] result = new byte[length + headerLength];
324// // Add a windows BMP header described:
325// // http://www.onicos.com/staff/iz/formats/bmp.html
326// result[0] = 'B';
327// result[1] = 'M'; // File Type identifier
328// result[3] = (byte)(result.length / 256);
329// result[2] = (byte)result.length;
330// result[10] = (byte)headerLength;
331// result[14] = 40; // MS Windows BMP header
332// result[18] = (byte)imageWidth;
333// result[22] = (byte)imageHeight;
334// result[26] = 1; // 1 Plane
335// result[28] = 24; // Colour depth
336// result[34] = (byte)length;
337// result[35] = (byte)(length / 256);
338
339 final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
340
341 // order is YCbCr and image is upside down, bitmaps are BGR
342//// for (int i = headerLength, dataOffset = length; i<result.length; i += 3, dataOffset -= 3)
343// {
344// final int y = thumbnailBytes[dataOffset - 2] & 0xFF;
345// final int cb = thumbnailBytes[dataOffset - 1] & 0xFF;
346// final int cr = thumbnailBytes[dataOffset] & 0xFF;
347// if (y<16 || y>235 || cb<16 || cb>240 || cr<16 || cr>240)
348// "".toString();
349//
350// int g = (int)(1.164*(y-16) - 0.391*(cb-128) - 0.813*(cr-128));
351// int r = (int)(1.164*(y-16) + 1.596*(cr-128));
352// int b = (int)(1.164*(y-16) + 2.018*(cb-128));
353//
354//// result[i] = (byte)b;
355//// result[i + 1] = (byte)g;
356//// result[i + 2] = (byte)r;
357//
358// // TODO compose the image here
359// image.setRGB(1, 2, 3);
360// }
361
362 return image;
363 }
364
365 /**
366 * Creates a thumbnail image in (Windows) BMP format from raw RGB data.
367 * @param thumbnailBytes
368 * @return
369 * @throws com.drew.metadata.MetadataException
370 * /
371 private Image createImageFromRawRgb(byte[] thumbnailBytes) throws MetadataException
372 {
373 final int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
374 final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
375 final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
376// final int headerLength = 54;
377// final byte[] result = new byte[length + headerLength];
378// // Add a windows BMP header described:
379// // http://www.onicos.com/staff/iz/formats/bmp.html
380// result[0] = 'B';
381// result[1] = 'M'; // File Type identifier
382// result[3] = (byte)(result.length / 256);
383// result[2] = (byte)result.length;
384// result[10] = (byte)headerLength;
385// result[14] = 40; // MS Windows BMP header
386// result[18] = (byte)imageWidth;
387// result[22] = (byte)imageHeight;
388// result[26] = 1; // 1 Plane
389// result[28] = 24; // Colour depth
390// result[34] = (byte)length;
391// result[35] = (byte)(length / 256);
392
393 final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
394
395 // order is RGB and image is upside down, bitmaps are BGR
396// for (int i = headerLength, dataOffset = length; i<result.length; i += 3, dataOffset -= 3)
397// {
398// byte b = thumbnailBytes[dataOffset - 2];
399// byte g = thumbnailBytes[dataOffset - 1];
400// byte r = thumbnailBytes[dataOffset];
401//
402// // TODO compose the image here
403// image.setRGB(1, 2, 3);
404// }
405
406 return image;
407 }
408*/
409}
Note: See TracBrowser for help on using the repository browser.