Dear all,
for my personal need, I developed a Exif Reader for octave, allowing to
read additional informations from Jpeg images.
The implementation is based on the format specification:
JEITA CP-3451, Exchangeable image file format for digital still
cameras: Exif Version 2.2
I tested it with several images from my own camera and other files
obtained from Flickr. No special extensions for octave are used, my
version of octave is 3.1.55.
Hoping that this function might be useful for other people using MATLAB
/ Octave, I would like to release it as part and under the terms of the
GPL conditions of the octave-forge image processing toolbox.
Best regards,
Roderick Köhle
---
Tribulaunstr. 8, 81739 München
Email: koe...@users.sourceforge.net
## Copyright (C) 2009 Roderick Koehle koehle(at)users.sourceforge.net
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## Octave is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING. If not, see
## <http://www.gnu.org/licenses/>.
## -*- texinfo -*-
## @deftypefn {Function File} {...@var{exif} =} readexif(@var{filename}, @var{thumbnail})
## Read EXIF information from JPEG image data.
##
## The exif tag information are returned in the @var{exif} data structure.
## Integer ratios are expressed as column vector.
## For example, a focal number of 2.8 is expressed
## as FNumber=[28; 10]. Otherwise all data are returned by the type
## as specified in the IFD structures.
##
## The filename for the thumbnail image is optional.
## If given, the thumbnail jpeg image will be stored to
## file @var{thumbnail}.
##
## Reference:
## JEITA CP-3451, Exchangeable image file format for digital still cameras:
## Exif Version 2.2
##
## @seealso{imwrite, imfinfo}
## @end deftypefn
function exif = readexif(file, thumbnail)
% Enable the debug flag to see more of the JPG sections.
debug = false;
in = fopen(file);
if (in<0)
error('File "%s" not found !', file);
end
s = fread(in, 1, 'uint16', 'ieee-be');
JPEG.SOI = 0xffd8;
JPEG.APP0 = 0xffe0;
JPEG.APP1 = 0xffe1;
JPEG.APP2 = 0xffe2;
JPEG.DQT = 0xffdb;
JPEG.DHT = 0xffc4;
JPEG.DRI = 0xffdd;
JPEG.SOF = 0xffc0;
JPEG.SOS = 0xffda;
JPEG.EOI = 0xffd9;
% Stop if no Start of Image found
if s~=JPEG.SOI
error('JPEG Format error - missing start of image tag.');
end
exif = [];
while ~feof(in)
s = fread(in, 1, 'uint16', 'ieee-be');
switch s
case JPEG.SOI
case JPEG.APP0
l = fread(in, 1, 'uint16', 'ieee-be');
if debug, printf('APP0: %i\n', l); end
fseek(in, l-2, 'cof');
case JPEG.APP1
l = fread(in, 1, 'uint16', 'ieee-be');
if debug, printf('APP1: %i\n', l); end
app1 = fread(in, l-2, 'uchar');
if nargin==1
exif = parseexif(app1);
else
exif = parseexif(app1, thumbnail);
end
% stop reading further, remove following break
% if you want to extend this parser.
break;
case JPEG.APP2
l = fread(in, 1, 'uint16', 'ieee-be');
if debug, printf('APP2: %i\n', l); end
fseek(in, l-2, 'cof');
case JPEG.DQT % define quantization table
l = fread(in, 1, 'uint16', 'ieee-be');
if debug, printf('DQT: %i\n', l); end;
fseek(in, l-2, 'cof');
case JPEG.DHT % define huffmann table
l = fread(in, 1, 'uint16', 'ieee-be');
fseek(in, l-2, 'cof');
if debug, printf('DHT: %i\n', l); end
case JPEG.DRI % define restart interoperability
l = fread(in, 1, 'uint16', 'ieee-be');
fseek(in, l-2, 'cof');
if debug, printf('DRI: %i\n', l); end
case JPEG.SOF % start of frame
l = fread(in, 1, 'uint16', 'ieee-be');
fseek(in, l-2, 'cof');
if debug, printf('SOF: %i\n', l); end
case JPEG.SOS % start of scan
l = fread(in, 1, 'uint16', 'ieee-be');
fseek(in, l-2, 'cof');
if debug, printf('SOS: %i\n', l); end
% JPEG compressed data comes here ...
break;
case JPEG.EOI % end of image
printf('EOI');
otherwise % Skip unknown tags
l = fread(in, 1, 'uint16', 'ieee-be');
if debug, printf('TAG %04X: %i\n', s, l); end
fseek(in, l-2, 'cof');
end
end
fclose(in);
end
%
% Parse EXIF APP1 section
%
% This routine will parse the APP1 section of an jpeg image.
% If a filename "thumb" is given, the tumbnail image data
% will be exported to given file.
%
% exif = parseexif(data, thumb)
%
function exif = parseexif(data, thumb)
id = char(data(1:6).');
if strncmp(id, ['Exif' 0 0], 6)
% TIFF header
byteorder = char(data(7:8).');
littleendian = strncmp(byteorder, 'II', 2);
bigendian = strncmp(byteorder, 'MM', 2);
tag42 = intn(data(9:10), bigendian);
offset = intn(data(11:14), bigendian);
if (~littleendian && ~bigendian) || tag42~=42
error('invalid TIFF header');
end
% IFD fields
exif = ifdparse(tifftags(), data, offset, bigendian);
else
exif = [];
end
% export thumbnail image
if nargin==2 && isfield(exif, 'JPEGInterchangeFormat')
i = exif.JPEGInterchangeFormat;
n = exif.JPEGInterchangeFormatLength;
jpg = data(7+i:7+i+n-1);
out = fopen(thumb, 'w');
if (out<0),
error('Cannot open file "%s" for writing thumbnail image.', thumb);
end
fwrite(out, jpg, 'uint8');
fclose(out);
end
end
function ifd = ifdparse(dict, data, offset, endian)
debug = false;
ifd = [];
while offset
ifd_fields = intn(data(7+offset+(0:1)), endian);
if debug, printf('Tag Type Count Offset\n'); end
for i=1:ifd_fields
j = 9+offset+(i-1)*12;
ifd_tag = intn(data( j:j+1), endian);
ifd_type = intn(data(j+2:j+3), endian);
ifd_count = intn(data(j+4:j+7), endian);
ifd_offset = intn(data(j+8:j+11), endian);
name = ifdtagname(dict, ifd_tag);
if debug,
printf('%04x %04x %08x %08x %s : ', ifd_tag, ifd_type, ...
ifd_count, ifd_offset, name);
end
if ifd_type>0
n = ifdsize(ifd_type);
if n*ifd_count<=4
value = data(j+8:j+8+n*ifd_count-1);
value = reshape(value, n, ifd_count);
else
a = 7+ifd_offset;
b = 7+ifd_offset+n*ifd_count-1;
if (a>0 && b>0 && a<=length(data) && b<=length(data))
value = data(7+ifd_offset:7+ifd_offset+n*ifd_count-1);
value = reshape(value, n, ifd_count);
else
value = [];
end
end
end
switch ifd_type
case 01 % unsigned char
ifd.(name) = uint8(value);
if debug,
printf('%02x ', uint8(value));
printf('\n');
end
case 02 % Ascii
ifd.(name) = char(value);
if debug, printf('%s\n', char(value)); end
case 03 % 16 bit unsigned int
ifd.(name) = uintn(value, endian);
if debug
printf('%i ', intn(value), endian);
printf('\n');
end
case 04 % 32 bit unsigned int
ifd.(name) = uintn(value, endian);
if debug, printf('%i\n', uintn(value, endian)); end
case 05 % 32 bit unsigned rational
ifd.(name) = [uintn(value(1:4,:), endian); uintn(value(5:8,:), endian)];
if debug, printf('%i/%i\n',uintn(value(1:4), endian),uintn(value(5:8)), endian); end
case 07 % unknown
ifd.(name) = uint8(value);
if debug
printf('%02x ', value);
printf('\n');
end
case 09 % 32 bit signed int
ifd.(name) = intn(value, endian);
if debug, printf('%i\n', intn(value, endian)); end
case 10 % 32 bit signed rational
ifd.(name) = [intn(value(1:4,:), endian); intn(value(5:8,:), endian)];
if debug, printf('%i/%i\n',intn(value(1:4), endian),intn(value(5:8)), endian); end
otherwise
printf('%02x ', value);
printf('\n');
end
switch ifd_tag
case 0x8769, % Exif Pointer
ifd.(name) = ifdparse(exiftags(), data, ifd_offset, endian);
case 0x8825, % GPS Pointer
ifd.(name) = ifdparse(gpstags(), data, ifd_offset, endian);
case 0xa005 % Interoperatibility Pointer
ifd.(name) = ifdparse(dict, data, ifd_offset, endian);
% case 0x927c % Makernotes
% ifd.(name) = ifdparse([], data, ifd_offset, endian);
otherwise
end
end
j = 9+offset+ifd_fields*12;
ifd_next = intn(data(j:j+3), endian);
offset = ifd_next;
end
end
%
% Return bytelength for respective IFD type
%
function n = ifdsize(ifd_type)
switch ifd_type
case {1, 2, 7}, n = 1;
case 03 , n = 2;
case {4, 9} , n = 4;
case {5, 10} , n = 8;
otherwise , n = 1;
end
end
%
% Convert little endian character vector to integer
%
function y = intn(x, bigendian)
if bigendian
y = polycol(x, int32(256));
else
y = polycol(flipud(x), int32(256));
end
end
function y = uintn(x, bigendian)
if bigendian
y = polycol(x, uint32(256));
else
y = polycol(flipud(x), uint32(256));
end
end
%
% Use own polyval that works with integers,
% it evaluates the number polygon columnwise.
%
% number = polycol(digits, base)
%
function y = polycol(c, x)
y = c(1,:);
for i=2:size(c, 1)
y = y.*x+c(i,:);
end
end
%
% Query EXIF IFD tagname
%
% Unfortunately, neither MATLAB nor Octave provide a hash functionality,
% so use structures as hash.
%
function name = ifdtagname(dict, key)
k = sprintf('K%04X', key);
if isfield(dict, k)
name = dict.(k);
else
name = sprintf('tag%04X', key);
end
end
%
% Primary image IFD tags according to Exif 2.2
%
function dict = tifftags()
t = {
% TIFF Tags according to EXIF2.2, additional baseline TIFF tags are marked by a '%'
'0FE' 'NewSubfileType' %
'0FF' 'SubfileType' %
'100' 'ImageWidth'
'101' 'ImageLength'
'102' 'BitsPerSample'
'103' 'Compression'
'106' 'PhotometricInterpretation'
'108' 'CellWidth' %
'109' 'CellLength' %
'10A' 'FillOrder' %
'10E' 'ImageDescription'
'10F' 'Make'
'110' 'Model'
'111' 'StripOffsets'
'112' 'Orientation'
'115' 'SamplesPerPixel'
'116' 'RowsPerStrip'
'117' 'StripByteCounts'
'118' 'MinSampleValue' %
'119' 'MaxSampleValue' %
'11A' 'XResolution'
'11B' 'YResolution'
'11C' 'PlanarConfiguration'
'120' 'FreeOffsets' %
'121' 'FreeByteCounts' %
'122' 'GrayResponseUnit' %
'123' 'GrayResponseCurve' %
'128' 'ResolutionUnit'
'12D' 'TransferFunction'
'131' 'Software'
'132' 'DateTime'
'13B' 'Artist'
'13C' 'HostComputer' %
'13E' 'WhitePoint'
'13F' 'PrimaryChromaticities'
'140' 'ColorMap' %
'152' 'ExtraSamples' %
'201' 'JPEGInterchangeFormat'
'202' 'JPEGInterchangeFormatLength'
'211' 'YCbCrCoefficients'
'212' 'YCbCrSubSampling'
'213' 'YCbCrPositioning'
'214' 'ReferenceBlackWhite'
'8298' 'Copyright'
'8769' 'Exif IFD Pointer'
'8825' 'GPS Info IFD Pointer'
};
dict = [];
for i=1:size(t,1)
key = sprintf('K%04X', hex2dec(t{i,1}));
value = t{i,2};
dict.(key) = strrep(value, ' ', '_');
end
end
%
% EXIF private tags
%
function dict = exiftags()
t = {
% EXIF Tags
'829A' 'ExposureTime'
'829D' 'FNumber'
'8822' 'ExposureProgram'
'8824' 'SpectralSensitivity'
'8827' 'ISOSpeedRatings'
'8828' 'OECF'
'9000' 'ExifVersion'
'9003' 'DateTimeOriginal'
'9004' 'DateTimeDigitized'
'9101' 'ComponentsConfiguration'
'9102' 'CompressedBitsPerPixel'
'9201' 'ShutterSpeedValue'
'9202' 'ApertureValue'
'9203' 'BrightnessValue'
'9204' 'ExposureBiasValue'
'9205' 'MaxApertureValue'
'9206' 'SubjectDistance'
'9207' 'MeteringMode'
'9208' 'LightSource'
'9209' 'Flash'
'920A' 'FocalLength'
'9214' 'SubjectArea'
'927C' 'MakerNote'
'9286' 'UserComment'
'9290' 'SubsecTime'
'9291' 'SubsecTimeOriginal'
'9292' 'SubsecTimeDigitized'
'A000' 'FlashpixVersion'
'A001' 'ColorSpace'
'A002' 'PixelXDimension'
'A003' 'PixelYDimension'
'A004' 'RelatedSoundFile'
'A005' 'Interoperatibility IFD Pointer'
'A20B' 'FlashEnergy'
'A20C' 'SpatialFrequencyResponse'
'A20E' 'FocalPlaneXResolution'
'A20F' 'FocalPlaneYResolution'
'A210' 'FocalPlaneResolutionUnit'
'A214' 'SubjectLocation'
'A215' 'ExposureIndex'
'A217' 'SensingMethod'
'A300' 'FileSource'
'A301' 'SceneType'
'A302' 'CFAPattern'
'A401' 'CustomRendered'
'A402' 'ExposureMode'
'A403' 'WhiteBalance'
'A404' 'DigitalZoomRatio'
'A405' 'FocalLengthIn35mmFilm'
'A406' 'SceneCaptureType'
'A407' 'GainControl'
'A408' 'Contrast'
'A409' 'Saturation'
'A40A' 'Sharpness'
'A40B' 'DeviceSettingDescription'
'A40C' 'SubjectDistanceRange'
'A420' 'ImageUniqueID'
% Interoperatibility tags
'001' 'InteroperatibilityIndex'
'002' 'InteroperatibilityVersion'
'1000' 'RelatedImageFileFormat'
'1001' 'RelatedImageWidth'
'1002' 'RelatedImageLength'
};
dict = [];
for i=1:size(t,1)
key = sprintf('K%04X', hex2dec(t{i,1}));
value = t{i,2};
dict.(key) = strrep(value, ' ', '_');
end
end
%
% EXIF GPS tags
%
function dict = gpstags()
t = {
0 'GPSVersionID'
1 'GPSLatitudeRef'
2 'GPSLatitude'
3 'GPSLongitudeRef'
4 'GPSLongitude'
5 'GPSAltitudeRef'
6 'GPSAltitude'
7 'GPSTimeStamp'
8 'GPSSatellites'
9 'GPSStatus'
10 'GPSMeasureMode'
11 'GPSDOP'
12 'GPSSpeedRef'
13 'GPSSpeed'
14 'GPSTrackRef'
15 'GPSTrack'
16 'GPSImgDirectionRef'
17 'GPSImgDirection'
18 'GPSMapDatum'
19 'GPSDestLatitudeRef'
20 'GPSDestLatitude'
21 'GPSDestLongitudeRef'
22 'GPSDestLongitude'
23 'GPSDestBearingRef'
24 'GPSDestBearing'
25 'GPSDestDistanceRef'
26 'GPSDestDistance'
};
dict = [];
for i=1:size(t,1)
key = sprintf('K%04X', t{i,1});
value = t{i,2};
dict.(key) = strrep(value, ' ', '_');
end
end
------------------------------------------------------------------------------
Come build with us! The BlackBerry® Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay
ahead of the curve. Join us from November 9-12, 2009. Register now!
http://p.sf.net/sfu/devconf
_______________________________________________
Octave-dev mailing list
Octave-dev@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/octave-dev