[Lazarus] Jpeg Exif reader was: TImage shows loaded image rotated by 90?
LacaK
lacak at zoznam.sk
Thu Sep 29 12:28:19 CEST 2016
Attached improved code.
(as far as there is a lot of Exif tags, not all are parsed and placed in
FExif record, but all are in FIFD array)
-Laco.
>> I have created small Exif reader for my own needs.
>> I have looked also in FCL TFPReaderJpeg (which uses pasjpeg) if there
>> is
>> no support, but I do not see.
>> If there would be interest for extending functionality of this reader
>> to
>> support reading of Exif information I can prepare patch.
>>
>> Here is my simple implementation if somebody is interested:
>>
-------------- next part --------------
unit JpegExif;
{$IFDEF FPC}
{$mode objfpc}
{$ENDIF}
{$H+}
interface
uses
Classes, SysUtils;
type
{ TJpegExifReader }
TJpegExifReader = class
private
type
TIFDEntry = record
TagNo: Word;
DataType: Word;
Count: DWord;
Value: DWord; // This tag records the offset from the start of the TIFF header to the position where the value itself is recorded.
// In cases where the value fits in 4 bytes, the value itself is recorded. If the value is smaller than 4 bytes, the value is stored in the 4-byte area starting from the left, i.e., from the lower end
end;
TExifData = record
Make,
Model,
Software,
DateTime,
Artist: string;
Orientation: smallint;
XResolution,
YResolution: double;
ResolutionUnit: smallint;
ExposureTime: double;
FNumber: double;
ColorSpace: Word;
ImageUniqueID: string;
end;
var
FBA: Word;
function Swap(w: Word): Word; overload;
function Swap(dw: DWord): DWord; overload;
public
FIFD: array of TIFDEntry;
FExif: TExifData;
public
constructor Create(const FileName: string); overload;
constructor Create(Stream: TFileStream); overload;
function Orientation: smallint;
function XResolution: double;
function YResolution: double;
function ResolutionUnit: string;
function DateTime: TDateTime;
end;
implementation
{ TJpegExifReader }
function TJpegExifReader.Swap(w: Word): Word;
begin
if FBA = $4D4D then
// Motorola align: first byte in data is highest byte (big endian)
Result := BEtoN(w)
else
// Intel align: first byte in data is lowest byte (little endian)
Result := LEtoN(w);
end;
function TJpegExifReader.Swap(dw: DWord): DWord;
begin
if FBA = $4D4D then
Result := BEtoN(dw)
else
Result := LEtoN(dw);
end;
constructor TJpegExifReader.Create(const FileName: string);
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead+fmShareDenyWrite);
try
Create(Stream);
finally
Stream.Free;
end;
end;
constructor TJpegExifReader.Create(Stream: TFileStream);
type
TRATIONAL=record
numerator: dword;
denominator: dword;
end;
var
b: Byte;
w, Marker, Size: Word;
dw: DWord;
i,c: Integer;
S: AnsiString;
R: TRATIONAL;
D: Double;
function ReadIFD(Offset: DWord): integer;
var i,j: integer;
begin
Stream.Position := 12 + Offset;
Stream.Read(w, SizeOf(w)); // No of IFD entries
Dec(Size, 2);
Result := Swap(w);
j := Length(FIFD);
SetLength(FIFD, j+Result);
// Read IFD entries
for i:=j to j+Result-1 do begin
Stream.Read(FIFD[i], SizeOf(TIFDEntry));
Dec(Size, SizeOf(TIFDEntry));
FIFD[i].TagNo := Swap(FIFD[i].TagNo);
FIFD[i].DataType := Swap(FIFD[i].DataType);
FIFD[i].Count := Swap(FIFD[i].Count);
end;
Stream.Read(dw, SizeOf(dw)); // Offset to next IFD (0=last)
end;
begin
inherited Create;
Stream.Position := 0;
Stream.Read(w,SizeOf(w));
if w <> NtoLE($D8FF) then Exit; // 0-1: Jpeg SOI (Start of image: FFD8)
Stream.Read(Marker, SizeOf(w)); // 2-3: Application marker
Stream.Read(Size, SizeOf(Size)); // 4-5: Size of APP1 data area (high byte first)
Size := BEtoN(Size);
Dec(Size, 2);
Stream.Read(dw, SizeOf(dw)); // 6-9: 'Exif' of 'JFIF'
Dec(Size, 4);
// APP0 (Application marker: FFE0) and 'JFIF'
if (Marker = NtoLE($E0FF)) and (dw = NtoLE($4649464A)) then
begin
Stream.Read(b, 1); // 10: 0
Stream.Read(w, SizeOf(w)); // 11-12: JFIF version
Stream.Read(b, 1); // 13: Density units (1-in, 2-px)
FExif.ResolutionUnit := b+1;
Stream.Read(w, SizeOf(w)); // 14-15: XDensity
FExif.XResolution := BEtoN(w);
Stream.Read(w, SizeOf(w)); // 16-17: YDensity
FExif.YResolution := BEtoN(w);
end
// APP1 (Application marker: FFE1) and 'Exif'
else if (Marker = NtoLE($E1FF)) and (dw = NtoLE($66697845)) then
begin
Stream.Read(w, SizeOf(w));
if w <> NtoLE($0000) then Exit; // 10-11: 0000
Dec(Size, 2);
// TIFF header
Stream.Read(FBA, SizeOf(FBA)); // 12-13: byte order
if (FBA<>$4949) and (FBA<>$4D4D) then Exit; // 4949=Intel, 4D4D=Motorola
Stream.Read(w, SizeOf(w));
if Swap(w) <> $002A then Exit; // Tag Mark
Stream.Read(dw, SizeOf(dw)); // Offset to first IFD (usualy 8)
Dec(Size, 8);
// IFD0: Image file directory (main image)
ReadIFD(8);
// Parse IFD entries
i := 0;
while i < Length(FIFD) do begin
case FIFD[i].DataType of
1: w:=1; // unsigned byte
2: w:=1; // ascii string (terminated with 0)
3: w:=2; // unsigned short (2 bytes)
4: w:=4; // unsigned long (4 bytes)
5: w:=8; // unsigned rational (4+4 long)
else w:=1;
end;
c := w * FIFD[i].Count;
if c > 4 then begin
// Value contains offset from TIFF header to data
Stream.Position := 12 + Swap(FIFD[i].Value);
case FIFD[i].DataType of
2: begin
Dec(c);
SetLength(S, c);
Stream.Read(S[1], c);
end;
5: begin
Stream.Read(R, SizeOf(R));
D := Swap(R.numerator) / Swap(R.denominator);
end;
end;
end
else
case FIFD[i].DataType of
3: w := Swap(Word(FIFD[i].Value));
4: dw := Swap(FIFD[i].Value);
end;
case FIFD[i].TagNo of
// IFD0 Tags
$010F: FExif.Make := S;
$0110: FExif.Model := S;
$0112: FExif.Orientation := w;
$011A: FExif.XResolution := D;
$011B: FExif.YResolution := D;
$0128: FExif.ResolutionUnit := w;
$0131: FExif.Software := S;
$0132: FExif.DateTime := S;
$013B: FExif.Artist := S;
$8769: ReadIFD(dw); // offset to Exif SubIFD
// Exif SubIFD Tags
$829A: FExif.ExposureTime := D;
$829D: FExif.FNumber := D;
$A001: FExif.ColorSpace := w;
$A420: FExif.ImageUniqueID := S;
end;
Inc(i);
end;
end;
end;
function TJpegExifReader.Orientation: smallint;
begin
case FExif.Orientation of
3: Result := 180; // The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
6: Result := 270; // The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
8: Result := 90; // The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
else Result := 0;
end;
end;
function TJpegExifReader.XResolution: double;
begin
Result := FExif.XResolution;
end;
function TJpegExifReader.YResolution: double;
begin
Result := FExif.YResolution;
end;
function TJpegExifReader.ResolutionUnit: string;
begin
case FExif.ResolutionUnit of
2: Result := 'in';
3: Result := 'cm';
else Result := '';
end;
end;
function TJpegExifReader.DateTime: TDateTime;
begin
if Length(FExif.DateTime) = 19 then
Result := ComposeDateTime(
StrToDate(Copy(FExif.DateTime,1,10), 'yyyy:mm:dd', ':'),
StrToTime(Copy(FExif.DateTime,12,8), ':')
)
else
Result := 0;
end;
end.
More information about the Lazarus
mailing list