1 ## Copyright (C) 2009 Roderick Koehle koehle(at)users.sourceforge.net
3 ## This program is free software; you can redistribute it and/or
4 ## modify it under the terms of the GNU General Public License
5 ## as published by the Free Software Foundation; either version 2
6 ## of the License, or (at your option) any later version.
8 ## Octave is distributed in the hope that it will be useful, but
9 ## WITHOUT ANY WARRANTY; without even the implied warranty of
10 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 ## General Public License for more details.
13 ## You should have received a copy of the GNU General Public License
14 ## along with Octave; see the file COPYING. If not, see
15 ## <http://www.gnu.org/licenses/>.
18 ## @deftypefn {Function File} {@var{exif} =} readexif(@var{filename}, @var{thumbnail})
19 ## Read EXIF information from JPEG image data.
21 ## The exif tag information are returned in the @var{exif} data structure.
22 ## Integer ratios are expressed as column vector.
23 ## For example, a focal number of 2.8 is expressed
24 ## as FNumber=[28; 10]. Otherwise all data are returned by the type
25 ## as specified in the IFD structures.
27 ## The filename for the thumbnail image is optional.
28 ## If given, the thumbnail jpeg image will be stored to
29 ## file @var{thumbnail}.
32 ## JEITA CP-3451, Exchangeable image file format for digital still cameras:
35 ## @seealso{imwrite, imfinfo}
38 function exif = readexif(file, thumbnail)
40 % Enable the debug flag to see more of the JPG sections.
46 error('File "%s" not found !', file);
49 s = fread(in, 1, 'uint16', 'ieee-be');
62 % Stop if no Start of Image found
65 error('JPEG Format error - missing start of image tag.');
71 s = fread(in, 1, 'uint16', 'ieee-be');
76 l = fread(in, 1, 'uint16', 'ieee-be');
77 if debug, printf('APP0: %i\n', l); end
78 fseek(in, l-2, 'cof');
80 l = fread(in, 1, 'uint16', 'ieee-be');
81 if debug, printf('APP1: %i\n', l); end
82 app1 = fread(in, l-2, 'uchar');
84 exif = parseexif(app1);
86 exif = parseexif(app1, thumbnail);
88 % stop reading further, remove following break
89 % if you want to extend this parser.
92 l = fread(in, 1, 'uint16', 'ieee-be');
93 if debug, printf('APP2: %i\n', l); end
94 fseek(in, l-2, 'cof');
95 case JPEG.DQT % define quantization table
96 l = fread(in, 1, 'uint16', 'ieee-be');
97 if debug, printf('DQT: %i\n', l); end;
98 fseek(in, l-2, 'cof');
99 case JPEG.DHT % define huffmann table
100 l = fread(in, 1, 'uint16', 'ieee-be');
101 fseek(in, l-2, 'cof');
102 if debug, printf('DHT: %i\n', l); end
103 case JPEG.DRI % define restart interoperability
104 l = fread(in, 1, 'uint16', 'ieee-be');
105 fseek(in, l-2, 'cof');
106 if debug, printf('DRI: %i\n', l); end
107 case JPEG.SOF % start of frame
108 l = fread(in, 1, 'uint16', 'ieee-be');
109 fseek(in, l-2, 'cof');
110 if debug, printf('SOF: %i\n', l); end
111 case JPEG.SOS % start of scan
112 l = fread(in, 1, 'uint16', 'ieee-be');
113 fseek(in, l-2, 'cof');
114 if debug, printf('SOS: %i\n', l); end
116 % JPEG compressed data comes here ...
119 case JPEG.EOI % end of image
121 otherwise % Skip unknown tags
122 l = fread(in, 1, 'uint16', 'ieee-be');
123 if debug, printf('TAG %04X: %i\n', s, l); end
124 fseek(in, l-2, 'cof');
132 % Parse EXIF APP1 section
134 % This routine will parse the APP1 section of an jpeg image.
135 % If a filename "thumb" is given, the tumbnail image data
136 % will be exported to given file.
138 % exif = parseexif(data, thumb)
140 function exif = parseexif(data, thumb)
142 id = char(data(1:6).');
143 if strncmp(id, ['Exif' 0 0], 6)
147 byteorder = char(data(7:8).');
148 littleendian = strncmp(byteorder, 'II', 2);
149 bigendian = strncmp(byteorder, 'MM', 2);
151 tag42 = intn(data(9:10), bigendian);
152 offset = intn(data(11:14), bigendian);
154 if (~littleendian && ~bigendian) || tag42~=42
155 error('invalid TIFF header');
160 exif = ifdparse(tifftags(), data, offset, bigendian);
165 % export thumbnail image
167 if nargin==2 && isfield(exif, 'JPEGInterchangeFormat')
168 i = exif.JPEGInterchangeFormat;
169 n = exif.JPEGInterchangeFormatLength;
171 jpg = data(7+i:7+i+n-1);
172 out = fopen(thumb, 'w');
174 error('Cannot open file "%s" for writing thumbnail image.', thumb);
176 fwrite(out, jpg, 'uint8');
181 function ifd = ifdparse(dict, data, offset, endian)
188 ifd_fields = intn(data(7+offset+(0:1)), endian);
190 if debug, printf('Tag Type Count Offset\n'); end
192 j = 9+offset+(i-1)*12;
193 ifd_tag = intn(data( j:j+1), endian);
194 ifd_type = intn(data(j+2:j+3), endian);
195 ifd_count = intn(data(j+4:j+7), endian);
196 ifd_offset = intn(data(j+8:j+11), endian);
198 name = ifdtagname(dict, ifd_tag);
201 printf('%04x %04x %08x %08x %s : ', ifd_tag, ifd_type, ...
202 ifd_count, ifd_offset, name);
205 n = ifdsize(ifd_type);
208 value = data(j+8:j+8+n*ifd_count-1);
209 value = reshape(value, n, ifd_count);
212 b = 7+ifd_offset+n*ifd_count-1;
213 if (a>0 && b>0 && a<=length(data) && b<=length(data))
214 value = data(7+ifd_offset:7+ifd_offset+n*ifd_count-1);
215 value = reshape(value, n, ifd_count);
223 case 01 % unsigned char
224 ifd.(name) = uint8(value);
226 printf('%02x ', uint8(value));
230 ifd.(name) = char(value);
231 if debug, printf('%s\n', char(value)); end
232 case 03 % 16 bit unsigned int
233 ifd.(name) = uintn(value, endian);
235 printf('%i ', intn(value), endian);
238 case 04 % 32 bit unsigned int
239 ifd.(name) = uintn(value, endian);
240 if debug, printf('%i\n', uintn(value, endian)); end
241 case 05 % 32 bit unsigned rational
242 ifd.(name) = [uintn(value(1:4,:), endian); uintn(value(5:8,:), endian)];
243 if debug, printf('%i/%i\n',uintn(value(1:4), endian),uintn(value(5:8)), endian); end
245 ifd.(name) = uint8(value);
247 printf('%02x ', value);
250 case 09 % 32 bit signed int
251 ifd.(name) = intn(value, endian);
252 if debug, printf('%i\n', intn(value, endian)); end
253 case 10 % 32 bit signed rational
254 ifd.(name) = [intn(value(1:4,:), endian); intn(value(5:8,:), endian)];
255 if debug, printf('%i/%i\n',intn(value(1:4), endian),intn(value(5:8)), endian); end
257 printf('%02x ', value);
262 case 0x8769, % Exif Pointer
263 ifd.(name) = ifdparse(exiftags(), data, ifd_offset, endian);
264 case 0x8825, % GPS Pointer
265 ifd.(name) = ifdparse(gpstags(), data, ifd_offset, endian);
266 case 0xa005 % Interoperatibility Pointer
267 ifd.(name) = ifdparse(dict, data, ifd_offset, endian);
268 % case 0x927c % Makernotes
269 % ifd.(name) = ifdparse([], data, ifd_offset, endian);
273 j = 9+offset+ifd_fields*12;
274 ifd_next = intn(data(j:j+3), endian);
281 % Return bytelength for respective IFD type
283 function n = ifdsize(ifd_type)
285 case {1, 2, 7}, n = 1;
288 case {5, 10} , n = 8;
295 % Convert little endian character vector to integer
297 function y = intn(x, bigendian)
299 y = polycol(x, int32(256));
301 y = polycol(flipud(x), int32(256));
305 function y = uintn(x, bigendian)
307 y = polycol(x, uint32(256));
309 y = polycol(flipud(x), uint32(256));
314 % Use own polyval that works with integers,
315 % it evaluates the number polygon columnwise.
317 % number = polycol(digits, base)
319 function y = polycol(c, x)
327 % Query EXIF IFD tagname
329 % Unfortunately, neither MATLAB nor Octave provide a hash functionality,
330 % so use structures as hash.
332 function name = ifdtagname(dict, key)
333 k = sprintf('K%04X', key);
337 name = sprintf('tag%04X', key);
342 % Primary image IFD tags according to Exif 2.2
344 function dict = tifftags()
347 % TIFF Tags according to EXIF2.2, additional baseline TIFF tags are marked by a '%'
349 '0FE' 'NewSubfileType' %
350 '0FF' 'SubfileType' %
353 '102' 'BitsPerSample'
355 '106' 'PhotometricInterpretation'
359 '10E' 'ImageDescription'
364 '115' 'SamplesPerPixel'
366 '117' 'StripByteCounts'
367 '118' 'MinSampleValue' %
368 '119' 'MaxSampleValue' %
371 '11C' 'PlanarConfiguration'
372 '120' 'FreeOffsets' %
373 '121' 'FreeByteCounts' %
374 '122' 'GrayResponseUnit' %
375 '123' 'GrayResponseCurve' %
376 '128' 'ResolutionUnit'
377 '12D' 'TransferFunction'
381 '13C' 'HostComputer' %
383 '13F' 'PrimaryChromaticities'
385 '152' 'ExtraSamples' %
386 '201' 'JPEGInterchangeFormat'
387 '202' 'JPEGInterchangeFormatLength'
388 '211' 'YCbCrCoefficients'
389 '212' 'YCbCrSubSampling'
390 '213' 'YCbCrPositioning'
391 '214' 'ReferenceBlackWhite'
393 '8769' 'Exif IFD Pointer'
394 '8825' 'GPS Info IFD Pointer'
399 key = sprintf('K%04X', hex2dec(t{i,1}));
401 dict.(key) = strrep(value, ' ', '_');
408 function dict = exiftags()
413 '829A' 'ExposureTime'
415 '8822' 'ExposureProgram'
416 '8824' 'SpectralSensitivity'
417 '8827' 'ISOSpeedRatings'
420 '9003' 'DateTimeOriginal'
421 '9004' 'DateTimeDigitized'
422 '9101' 'ComponentsConfiguration'
423 '9102' 'CompressedBitsPerPixel'
424 '9201' 'ShutterSpeedValue'
425 '9202' 'ApertureValue'
426 '9203' 'BrightnessValue'
427 '9204' 'ExposureBiasValue'
428 '9205' 'MaxApertureValue'
429 '9206' 'SubjectDistance'
430 '9207' 'MeteringMode'
438 '9291' 'SubsecTimeOriginal'
439 '9292' 'SubsecTimeDigitized'
440 'A000' 'FlashpixVersion'
442 'A002' 'PixelXDimension'
443 'A003' 'PixelYDimension'
444 'A004' 'RelatedSoundFile'
445 'A005' 'Interoperatibility IFD Pointer'
447 'A20C' 'SpatialFrequencyResponse'
448 'A20E' 'FocalPlaneXResolution'
449 'A20F' 'FocalPlaneYResolution'
450 'A210' 'FocalPlaneResolutionUnit'
451 'A214' 'SubjectLocation'
452 'A215' 'ExposureIndex'
453 'A217' 'SensingMethod'
457 'A401' 'CustomRendered'
458 'A402' 'ExposureMode'
459 'A403' 'WhiteBalance'
460 'A404' 'DigitalZoomRatio'
461 'A405' 'FocalLengthIn35mmFilm'
462 'A406' 'SceneCaptureType'
467 'A40B' 'DeviceSettingDescription'
468 'A40C' 'SubjectDistanceRange'
469 'A420' 'ImageUniqueID'
471 % Interoperatibility tags
473 '001' 'InteroperatibilityIndex'
474 '002' 'InteroperatibilityVersion'
475 '1000' 'RelatedImageFileFormat'
476 '1001' 'RelatedImageWidth'
477 '1002' 'RelatedImageLength'
482 key = sprintf('K%04X', hex2dec(t{i,1}));
484 dict.(key) = strrep(value, ' ', '_');
491 function dict = gpstags()
509 16 'GPSImgDirectionRef'
512 19 'GPSDestLatitudeRef'
514 21 'GPSDestLongitudeRef'
515 22 'GPSDestLongitude'
516 23 'GPSDestBearingRef'
518 25 'GPSDestDistanceRef'
524 key = sprintf('K%04X', t{i,1});
526 dict.(key) = strrep(value, ' ', '_');