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

Last change on this file since 12187 was 10862, checked in by Don-vip, 8 years ago

update to metadata-extractor 2.9.1

  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 10.7 KB
RevLine 
[6127]1/*
[10862]2 * Copyright 2002-2016 Drew Noakes
[6127]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 *
[8132]18 * https://drewnoakes.com/code/exif/
19 * https://github.com/drewnoakes/metadata-extractor
[6127]20 */
21
22package com.drew.metadata.exif;
23
[10862]24import java.io.FileOutputStream;
25import java.io.IOException;
26import java.util.HashMap;
27
[6127]28import com.drew.lang.annotations.NotNull;
29import com.drew.lang.annotations.Nullable;
30import com.drew.metadata.MetadataException;
31
32/**
33 * One of several Exif directories. Otherwise known as IFD1, this directory holds information about an embedded thumbnail image.
34 *
[8132]35 * @author Drew Noakes https://drewnoakes.com
[6127]36 */
[8243]37public class ExifThumbnailDirectory extends ExifDirectoryBase
[6127]38{
39 /**
[8243]40 * The offset to thumbnail image bytes.
[6127]41 */
[8243]42 public static final int TAG_THUMBNAIL_OFFSET = 0x0201;
43 /**
44 * The size of the thumbnail image data in bytes.
45 */
46 public static final int TAG_THUMBNAIL_LENGTH = 0x0202;
[6127]47
48 @NotNull
49 protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
50
[8243]51 static
52 {
53 addExifTagNames(_tagNameMap);
54
[6127]55 _tagNameMap.put(TAG_THUMBNAIL_OFFSET, "Thumbnail Offset");
56 _tagNameMap.put(TAG_THUMBNAIL_LENGTH, "Thumbnail Length");
57 }
58
59 @Nullable
60 private byte[] _thumbnailData;
61
62 public ExifThumbnailDirectory()
63 {
64 this.setDescriptor(new ExifThumbnailDescriptor(this));
65 }
66
[8132]67 @Override
[6127]68 @NotNull
69 public String getName()
70 {
71 return "Exif Thumbnail";
72 }
73
[8132]74 @Override
[6127]75 @NotNull
76 protected HashMap<Integer, String> getTagNameMap()
77 {
78 return _tagNameMap;
79 }
80
81 public boolean hasThumbnailData()
82 {
83 return _thumbnailData != null;
84 }
85
86 @Nullable
87 public byte[] getThumbnailData()
88 {
89 return _thumbnailData;
90 }
91
92 public void setThumbnailData(@Nullable byte[] data)
93 {
94 _thumbnailData = data;
95 }
96
97 public void writeThumbnail(@NotNull String filename) throws MetadataException, IOException
98 {
99 byte[] data = _thumbnailData;
100
[8132]101 if (data == null)
[6127]102 throw new MetadataException("No thumbnail data exists.");
103
104 FileOutputStream stream = null;
105 try {
106 stream = new FileOutputStream(filename);
107 stream.write(data);
108 } finally {
[8132]109 if (stream != null)
[6127]110 stream.close();
111 }
112 }
113
114/*
115 // This thumbnail extraction code is not complete, and is included to assist anyone who feels like looking into
116 // it. Please share any progress with the original author, and hence the community. Thanks.
117
118 public Image getThumbnailImage() throws MetadataException
119 {
120 if (!hasThumbnailData())
121 return null;
122
123 int compression = 0;
124 try {
125 compression = this.getInt(ExifSubIFDDirectory.TAG_COMPRESSION);
126 } catch (Throwable e) {
127 this.addError("Unable to determine thumbnail type " + e.getMessage());
128 }
129
130 final byte[] thumbnailBytes = getThumbnailData();
131
132 if (compression == ExifSubIFDDirectory.COMPRESSION_JPEG)
133 {
134 // JPEG Thumbnail
135 // operate directly on thumbnailBytes
136 return decodeBytesAsImage(thumbnailBytes);
137 }
138 else if (compression == ExifSubIFDDirectory.COMPRESSION_NONE)
139 {
140 // uncompressed thumbnail (raw RGB data)
141 if (!this.containsTag(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION))
142 return null;
143
144 try
145 {
146 // If the image is RGB format, then convert it to a bitmap
147 final int photometricInterpretation = this.getInt(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
148 if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_RGB)
149 {
150 // RGB
151 Image image = createImageFromRawRgb(thumbnailBytes);
152 return image;
153 }
154 else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_YCBCR)
155 {
156 // YCbCr
157 Image image = createImageFromRawYCbCr(thumbnailBytes);
158 return image;
159 }
160 else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_MONOCHROME)
161 {
162 // Monochrome
163 return null;
164 }
165 } catch (Throwable e) {
166 this.addError("Unable to extract thumbnail: " + e.getMessage());
167 }
168 }
169 return null;
170 }
171
172 /**
173 * Handle the YCbCr thumbnail encoding used by Ricoh RDC4200/4300, Fuji DS-7/300 and DX-5/7/9 cameras.
174 *
175 * At DX-5/7/9, YCbCrSubsampling(0x0212) has values of '2,1', PlanarConfiguration(0x011c) has a value '1'. So the
176 * data align of this image is below.
177 *
178 * 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). . . .
179 *
180 * The numbers in parenthesis are pixel coordinates. DX series' YCbCrCoefficients(0x0211) has values '0.299/0.587/0.114',
181 * ReferenceBlackWhite(0x0214) has values '0,255,128,255,128,255'. Therefore to convert from Y/Cb/Cr to RGB is;
182 *
183 * B(0,0)=(Cb-128)*(2-0.114*2)+Y(0,0)
184 * R(0,0)=(Cr-128)*(2-0.299*2)+Y(0,0)
185 * G(0,0)=(Y(0,0)-0.114*B(0,0)-0.299*R(0,0))/0.587
186 *
187 * 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).
188 * Repeat this conversion by value of ImageWidth(0x0100) and ImageLength(0x0101).
189 *
190 * @param thumbnailBytes
191 * @return
192 * @throws com.drew.metadata.MetadataException
193 * /
194 private Image createImageFromRawYCbCr(byte[] thumbnailBytes) throws MetadataException
195 {
196 /*
197 Y = 0.257R + 0.504G + 0.098B + 16
198 Cb = -0.148R - 0.291G + 0.439B + 128
199 Cr = 0.439R - 0.368G - 0.071B + 128
200
201 G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
202 R = 1.164(Y-16) + 1.596(Cr-128)
203 B = 1.164(Y-16) + 2.018(Cb-128)
204
205 R, G and B range from 0 to 255.
206 Y ranges from 16 to 235.
207 Cb and Cr range from 16 to 240.
208
209 http://www.faqs.org/faqs/graphics/colorspace-faq/
210 * /
211
212 int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
213 final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
214 final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
215// final int headerLength = 54;
216// byte[] result = new byte[length + headerLength];
217// // Add a windows BMP header described:
218// // http://www.onicos.com/staff/iz/formats/bmp.html
219// result[0] = 'B';
220// result[1] = 'M'; // File Type identifier
221// result[3] = (byte)(result.length / 256);
222// result[2] = (byte)result.length;
223// result[10] = (byte)headerLength;
224// result[14] = 40; // MS Windows BMP header
225// result[18] = (byte)imageWidth;
226// result[22] = (byte)imageHeight;
227// result[26] = 1; // 1 Plane
228// result[28] = 24; // Colour depth
229// result[34] = (byte)length;
230// result[35] = (byte)(length / 256);
231
232 final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
233
234 // order is YCbCr and image is upside down, bitmaps are BGR
235//// for (int i = headerLength, dataOffset = length; i<result.length; i += 3, dataOffset -= 3)
236// {
237// final int y = thumbnailBytes[dataOffset - 2] & 0xFF;
238// final int cb = thumbnailBytes[dataOffset - 1] & 0xFF;
239// final int cr = thumbnailBytes[dataOffset] & 0xFF;
240// if (y<16 || y>235 || cb<16 || cb>240 || cr<16 || cr>240)
241// "".toString();
242//
243// int g = (int)(1.164*(y-16) - 0.391*(cb-128) - 0.813*(cr-128));
244// int r = (int)(1.164*(y-16) + 1.596*(cr-128));
245// int b = (int)(1.164*(y-16) + 2.018*(cb-128));
246//
247//// result[i] = (byte)b;
248//// result[i + 1] = (byte)g;
249//// result[i + 2] = (byte)r;
250//
251// // TODO compose the image here
252// image.setRGB(1, 2, 3);
253// }
254
255 return image;
256 }
257
258 /**
259 * Creates a thumbnail image in (Windows) BMP format from raw RGB data.
260 * @param thumbnailBytes
261 * @return
262 * @throws com.drew.metadata.MetadataException
263 * /
264 private Image createImageFromRawRgb(byte[] thumbnailBytes) throws MetadataException
265 {
266 final int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
267 final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
268 final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
269// final int headerLength = 54;
270// final byte[] result = new byte[length + headerLength];
271// // Add a windows BMP header described:
272// // http://www.onicos.com/staff/iz/formats/bmp.html
273// result[0] = 'B';
274// result[1] = 'M'; // File Type identifier
275// result[3] = (byte)(result.length / 256);
276// result[2] = (byte)result.length;
277// result[10] = (byte)headerLength;
278// result[14] = 40; // MS Windows BMP header
279// result[18] = (byte)imageWidth;
280// result[22] = (byte)imageHeight;
281// result[26] = 1; // 1 Plane
282// result[28] = 24; // Colour depth
283// result[34] = (byte)length;
284// result[35] = (byte)(length / 256);
285
286 final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
287
288 // order is RGB and image is upside down, bitmaps are BGR
289// for (int i = headerLength, dataOffset = length; i<result.length; i += 3, dataOffset -= 3)
290// {
291// byte b = thumbnailBytes[dataOffset - 2];
292// byte g = thumbnailBytes[dataOffset - 1];
293// byte r = thumbnailBytes[dataOffset];
294//
295// // TODO compose the image here
296// image.setRGB(1, 2, 3);
297// }
298
299 return image;
300 }
301*/
302}
Note: See TracBrowser for help on using the repository browser.