From d261c0057101df6a3e2719d7c401c9b30a02b94d Mon Sep 17 00:00:00 2001 From: Ondrej Pokorny Date: Wed, 26 Oct 2022 13:36:50 +0200 Subject: [PATCH] fcl-image/pasjpeg: handle Exif orientation flag automatically (cherry picked from commit 05c45486e8db45b8025cef2047c14c9f47fd6b66) --- packages/fcl-image/src/fpreadjpeg.pas | 94 +++++++- packages/pasjpeg/src/jdmarker.pas | 209 ++++++++++++++++++ packages/pasjpeg/src/jpeglib.pas | 3 + tests/test/packages/fcl-image/dots-5.jpg | Bin 0 -> 10839 bytes tests/test/packages/fcl-image/dots-8.jpg | Bin 0 -> 10839 bytes tests/test/packages/fcl-image/dots.rc | 2 + .../fcl-image/timage_jpegorientation.pp | 51 +++++ 7 files changed, 351 insertions(+), 8 deletions(-) create mode 100644 tests/test/packages/fcl-image/dots-5.jpg create mode 100644 tests/test/packages/fcl-image/dots-8.jpg create mode 100644 tests/test/packages/fcl-image/dots.rc create mode 100644 tests/test/packages/fcl-image/timage_jpegorientation.pp diff --git a/packages/fcl-image/src/fpreadjpeg.pas b/packages/fcl-image/src/fpreadjpeg.pas index 782ae65b7b..6ed1015d82 100644 --- a/packages/fcl-image/src/fpreadjpeg.pas +++ b/packages/fcl-image/src/fpreadjpeg.pas @@ -24,7 +24,7 @@ unit FPReadJPEG; interface uses - Classes, SysUtils, FPImage, JPEGLib, JdAPImin, JDataSrc, JdAPIstd, JmoreCfg; + Classes, SysUtils, Types, FPImage, JPEGLib, JdAPImin, JDataSrc, JdAPIstd, JmoreCfg; type { TFPReaderJPEG } @@ -79,6 +79,12 @@ type implementation +type + TExifOrientation = ( // all angles are clockwise + eoUnknown, eoNormal, eoMirrorHor, eoRotate180, eoMirrorVert, + eoMirrorHorRot270, eoRotate90, eoMirrorHorRot90, eoRotate270 + ); + procedure ReadCompleteStreamToStream(SrcStream, DestStream: TStream; StartSize: integer); var @@ -166,6 +172,61 @@ end; procedure TFPReaderJPEG.InternalRead(Str: TStream; Img: TFPCustomImage); var MemStream: TMemoryStream; + Orientation: TExifOrientation; + + function TranslatePixel(const Px: TPoint): TPoint; + begin + case Orientation of + eoUnknown, eoNormal: Result := Px; + eoMirrorHor: + begin + Result.X := FInfo.output_width-1-Px.X; + Result.Y := Px.Y; + end; + eoRotate180: + begin + Result.X := FInfo.output_width-1-Px.X; + Result.Y := FInfo.output_height-1-Px.Y; + end; + eoMirrorVert: + begin + Result.X := Px.X; + Result.Y := FInfo.output_height-1-Px.Y; + end; + eoMirrorHorRot270: + begin + Result.X := Px.Y; + Result.Y := Px.X; + end; + eoRotate90: + begin + Result.X := FInfo.output_height-1-Px.Y; + Result.Y := Px.X; + end; + eoMirrorHorRot90: + begin + Result.X := FInfo.output_height-1-Px.Y; + Result.Y := FInfo.output_width-1-Px.X; + end; + eoRotate270: + begin + Result.X := Px.Y; + Result.Y := FInfo.output_width-1-Px.X; + end; + end; + end; + + function TranslateSize(const Sz: TSize): TSize; + begin + case Orientation of + eoUnknown, eoNormal, eoMirrorHor, eoMirrorVert, eoRotate180: Result := Sz; + eoMirrorHorRot270, eoRotate90, eoMirrorHorRot90, eoRotate270: + begin + Result.Width := Sz.Height; + Result.Height := Sz.Width; + end; + end; + end; procedure SetSource; begin @@ -174,10 +235,19 @@ var end; procedure ReadHeader; + var + S: TSize; begin jpeg_read_header(@FInfo, TRUE); - FWidth := FInfo.image_width; - FHeight := FInfo.image_height; + + if FInfo.saw_EXIF_marker and (FInfo.orientation >= Ord(Low(TExifOrientation))) and (FInfo.orientation <= Ord(High(TExifOrientation))) then + Orientation := TExifOrientation(FInfo.orientation) + else + Orientation := Low(TExifOrientation); + S := TranslateSize(TSize.Create(FInfo.image_width, FInfo.image_height)); + FWidth := S.Width; + FHeight := S.Height; + FGrayscale := FInfo.jpeg_color_space = JCS_GRAYSCALE; FProgressiveEncoding := jpeg_has_multiple_scans(@FInfo); end; @@ -257,6 +327,14 @@ var Result.alpha:=alphaOpaque; end; procedure ReadPixels; + procedure SetPixel(x, y: integer; const C: TFPColor); + var + P: TPoint; + begin + P := TPoint.Create(x,y); + P := TranslatePixel(P); + Img.Colors[P.x, P.y] := C; + end; var Continue: Boolean; SampArray: JSAMPARRAY; @@ -287,7 +365,7 @@ var Color.Green:=SampRow^[x*4+1]; Color.Blue:=SampRow^[x*4+2]; Color.alpha:=SampRow^[x*4+3]; - Img.Colors[x,y]:=CorrectCMYK(Color); + SetPixel(x, y, CorrectCMYK(Color)); end else if (FInfo.jpeg_color_space = JCS_YCCK) then @@ -296,7 +374,7 @@ var Color.Green:=SampRow^[x*4+1]; Color.Blue:=SampRow^[x*4+2]; Color.alpha:=SampRow^[x*4+3]; - Img.Colors[x,y]:=CorrectYCCK(Color); + SetPixel(x, y, CorrectYCCK(Color)); end else if fgrayscale then begin @@ -305,7 +383,7 @@ var Color.Red:=c; Color.Green:=c; Color.Blue:=c; - Img.Colors[x,y]:=Color; + SetPixel(x, y, Color); end; end else begin @@ -313,7 +391,7 @@ var Color.Red:=SampRow^[x*3+0] shl 8; Color.Green:=SampRow^[x*3+1] shl 8; Color.Blue:=SampRow^[x*3+2] shl 8; - Img.Colors[x,y]:=Color; + SetPixel(x, y, Color); end; end; inc(y); @@ -328,7 +406,7 @@ var jpeg_start_decompress(@FInfo); - Img.SetSize(FInfo.output_width,FInfo.output_height); + Img.SetSize(FWidth,FHeight); GetMem(SampArray,SizeOf(JSAMPROW)); GetMem(SampRow,FInfo.output_width*FInfo.output_components); diff --git a/packages/pasjpeg/src/jdmarker.pas b/packages/pasjpeg/src/jdmarker.pas index 72f36b7b86..0f5a70ec9c 100644 --- a/packages/pasjpeg/src/jdmarker.pas +++ b/packages/pasjpeg/src/jdmarker.pas @@ -90,12 +90,25 @@ const { JPEG marker codes } M_ERROR = $100; + EXIF_TAG_PRIMARY = UINT32(1); + EXIF_TAGPARENT_PRIMARY = UINT32(EXIF_TAG_PRIMARY shl 16); // $00010000; + type JPEG_MARKER = uint; { JPEG marker codes } { Private state } type + jpeg_exif_ifd_record = packed record + tag_id: UINT16; + data_type: UINT16; + data_count: UINT32; + data_value: UINT32; + end; + + { Routine signature for application-supplied exif processing method. } + jpeg_exif_parser_method = function(cinfo : j_decompress_ptr; ifdRec: jpeg_exif_ifd_record; bigEndian: boolean; data: array of JOCTET; parent_tag_id: UINT32): boolean; + my_marker_ptr = ^my_marker_reader; my_marker_reader = record pub : jpeg_marker_reader; { public fields } @@ -104,6 +117,8 @@ type process_COM : jpeg_marker_parser_method; process_APPn : array[0..16-1] of jpeg_marker_parser_method; + handle_exif_tag : jpeg_exif_parser_method; + { Limit on marker data length to save for each marker type } length_limit_COM : uint; length_limit_APPn : array[0..16-1] of uint; @@ -1487,6 +1502,7 @@ end; { get_dri } const APP0_DATA_LEN = 14; { Length of interesting data in APP0 } + APP1_HEADER_LEN = 14; { Length of data header in APP1 } APP14_DATA_LEN = 12; { Length of interesting data in APP14 } APPN_DATA_LEN = 14; { Must be the largest of the above!! } @@ -1582,6 +1598,195 @@ begin end; +{LOCAL} +function handle_exif_marker(cinfo : j_decompress_ptr; ifdRec: jpeg_exif_ifd_record; bigEndian: boolean; data: array of JOCTET; parent_tag_id: UINT32): Boolean; + function FixEndian16(Value: UINT16): UINT16; + begin + if BigEndian then + Result := BEtoN(Value) + else + Result := LEtoN(Value); + end; +const + EXIF_TAG_ORIENTATION = EXIF_TAGPARENT_PRIMARY or $0112; +var + i: Integer; + orientation: UINT16; +begin + case (ifdRec.tag_id or parent_tag_id) of + EXIF_TAG_ORIENTATION: + begin + if Length(data)=SizeOf(UINT16) then + begin + move(data[0], orientation, Length(data)); + cinfo^.orientation := FixEndian16(orientation); + end else + Exit(False); + end; + end; + + Result := True; +end; + +{LOCAL} +function examine_app1 (cinfo : j_decompress_ptr; + var header : array of JOCTET; + headerlen : uint; + var remaining : INT32; + datasrc : jpeg_source_mgr_ptr; + var next_input_byte : JOCTETptr; + var bytes_in_buffer : size_t): Boolean; + +{ Read Exif marker. + headerlen is # of bytes at header[], remaining is length of rest of marker header. +} +var + BigEndian: Boolean; + Offset: UINT32; +const + TagElementSize: array[1..13] of Integer = (1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4); + + function FixEndian16(Value: UINT16): UINT16; + begin + if BigEndian then + Result := BEtoN(Value) + else + Result := LEtoN(Value); + end; + function FixEndian32(Value: UINT32): UINT32; + begin + if BigEndian then + Result := BEtoN(Value) + else + Result := LEtoN(Value); + end; + function Read(const Buffer: Pointer; numtoread: uint): Boolean; + var + i: UINT32; + begin + Result := False; + if numtoread=0 then + Exit; + for i := 0 to numtoread-1 do + begin + { if the offset is less than headerlen, there were more bytes read into the header than necessary } + { this can happen when APPN_DATA_LEN>APP1_HEADER_LEN (e.g. due to a future source code change } + { first read the bytes from header } + if Offset= APP1_HEADER_LEN) and + (GETJOCTET(header[0]) = Ord('E')) and + (GETJOCTET(header[1]) = Ord('x')) and + (GETJOCTET(header[2]) = Ord('i')) and + (GETJOCTET(header[3]) = Ord('f')) and + (GETJOCTET(header[4]) = 0) and + (GETJOCTET(header[5]) = 0) then + begin + // Tiff header + if (GETJOCTET(header[6]) = Ord('M')) and + (GETJOCTET(header[7]) = Ord('M')) + then + BigEndian := True + else + if (GETJOCTET(header[6]) = Ord('I')) and + (GETJOCTET(header[7]) = Ord('I')) + then + BigEndian := False + else + Exit; // invalid + + Signature := FixEndian16(GETJOCTET(header[8]) or (GETJOCTET(header[9]) shl 8)); + if Signature<>42 then + Exit; + Offset := FixEndian32(GETJOCTET(header[10]) or (GETJOCTET(header[11]) shl 8) or (GETJOCTET(header[12]) shl (8*2)) or (GETJOCTET(header[13]) shl (8*3))); + Inc(Offset, 6); // get over Exif header + { Found JFIF APP0 marker: save info } + cinfo^.saw_EXIF_marker := TRUE; + + { skip offset bytes } + if Offset>headerlen then + begin + datasrc^.next_input_byte := next_input_byte; + datasrc^.bytes_in_buffer := bytes_in_buffer; + cinfo^.src^.skip_input_data(cinfo, long(Offset-headerlen)); + next_input_byte := datasrc^.next_input_byte; + bytes_in_buffer := datasrc^.bytes_in_buffer; + end; + + // read data + if not Read16(numRecords) then + Exit(False); + + for i:=1 to numRecords do + begin + if not Read(@ifdRec, SizeOf(jpeg_exif_ifd_record)) then + Exit; + if (ifdRec.tag_id = 0) and (ifdRec.data_type = 0) and (ifdRec.data_count = 0) and (ifdRec.data_value = 0) then // nothing to read + Continue; + if (ifdRec.tag_id = 0) and (ifdRec.data_type = 0) then // Unexpected end of directory (4 zero bytes), so breaking here. + Break; + + ifdRec.tag_id := FixEndian16(ifdRec.tag_id); + ifdRec.data_type := FixEndian16(ifdRec.data_type); + + ifdRec.data_count := FixEndian32(ifdRec.data_count); + byteCount := Integer(ifdRec.data_count) * TagElementSize[ifdRec.data_type]; + if byteCount>0 then + begin + SetLength(data, bytecount); + if byteCount <= 4 then + begin + Move(ifdRec.data_value, data[0], byteCount) + end else + begin + //ToDo read at position ifdRec.data_value + continue; // for now ignore the tag + end; + my_marker_ptr(cinfo^.marker)^.handle_exif_tag(cinfo, ifdRec, BigEndian, data, EXIF_TAGPARENT_PRIMARY); + end; + end; + end; +end; + + {LOCAL} procedure examine_app14 (cinfo : j_decompress_ptr; var data : array of JOCTET; @@ -1727,6 +1932,8 @@ begin case (cinfo^.unread_marker) of M_APP0: examine_app0(cinfo, b, numtoread, length); + M_APP1: + examine_app1(cinfo, b, numtoread, length, datasrc, next_input_byte, bytes_in_buffer); M_APP14: examine_app14(cinfo, b, numtoread, length); else @@ -2567,7 +2774,9 @@ begin marker^.length_limit_APPn[i] := 0; end; marker^.process_APPn[0] := get_interesting_appn; + marker^.process_APPn[1] := get_interesting_appn; marker^.process_APPn[14] := get_interesting_appn; + marker^.handle_exif_tag := handle_exif_marker; { Reset marker processing state } reset_marker_reader(cinfo); end; { jinit_marker_reader } diff --git a/packages/pasjpeg/src/jpeglib.pas b/packages/pasjpeg/src/jpeglib.pas index 2ee4f17c8f..7eebafafc5 100644 --- a/packages/pasjpeg/src/jpeglib.pas +++ b/packages/pasjpeg/src/jpeglib.pas @@ -1200,6 +1200,9 @@ type saw_Adobe_marker : boolean; { TRUE iff an Adobe APP14 marker was found } Adobe_transform : UINT8; { Color transform code from Adobe marker } + saw_EXIF_marker : boolean; { TRUE if an Exif APP1 marker was found } + orientation : UINT16; { Exif orientation value } + CCIR601_sampling : boolean; { TRUE=first samples are cosited } { Aside from the specific data retained from APPn markers known to the diff --git a/tests/test/packages/fcl-image/dots-5.jpg b/tests/test/packages/fcl-image/dots-5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4d6429def00df1c3710f583365d5b5eddc488bba GIT binary patch literal 10839 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 M9`AY}thZkHAKc-gx&QzG literal 0 HcmV?d00001 diff --git a/tests/test/packages/fcl-image/dots-8.jpg b/tests/test/packages/fcl-image/dots-8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a952b4183ddefc2914d62523f7921af5ed52938b GIT binary patch literal 10839 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 L-t|CGZ@KV4TbQA} literal 0 HcmV?d00001 diff --git a/tests/test/packages/fcl-image/dots.rc b/tests/test/packages/fcl-image/dots.rc new file mode 100644 index 0000000000..af809b11ae --- /dev/null +++ b/tests/test/packages/fcl-image/dots.rc @@ -0,0 +1,2 @@ +dots_5 RCDATA "dots-5.jpg" +dots_8 RCDATA "dots-8.jpg" diff --git a/tests/test/packages/fcl-image/timage_jpegorientation.pp b/tests/test/packages/fcl-image/timage_jpegorientation.pp new file mode 100644 index 0000000000..ec7a88c122 --- /dev/null +++ b/tests/test/packages/fcl-image/timage_jpegorientation.pp @@ -0,0 +1,51 @@ +program timage_jpegorientation; + +{$mode objfpc}{$H+} +{$R dots.rc} + +uses + FPReadJPEG, FPImage, Classes, resource; + +var + Bmp: TFPCompactImgRGBA8Bit; + S: TResourceStream; + Reader: TFPReaderJPEG; + + function CheckColor(x, y: integer; r, g, b: Word): Boolean; + begin + Result := (Byte(Bmp.Colors[x, y].Red)=r) and (Byte(Bmp.Colors[x, y].Green)=g) and (Byte(Bmp.Colors[x, y].Blue)=b); + if not Result then + Writeln(Byte(Bmp.Colors[x, y].Red), ':', Byte(Bmp.Colors[x, y].Green), ':', Byte(Bmp.Colors[x, y].Blue)); + end; +begin + Bmp := TFPCompactImgRGBA8Bit.Create(0, 0); + Reader := TFPReaderJPEG.Create; + + S := TResourceStream.Create(HINSTANCE, 'dots_5', {$ifdef FPC_OS_UNICODE}PWideChar{$else}PChar{$endif}(RT_RCDATA)); + Bmp.LoadFromStream(S, Reader); + if not CheckColor(0, 0, 0, 0, 254) then + Halt(1); + if not CheckColor(1, 0, 0, 255, 0) then + Halt(2); + if not CheckColor(0, 1, 255, 255, 0) then + Halt(3); + if not CheckColor(1, 1, 254, 0, 0) then + Halt(4); + S.Free; + + S := TResourceStream.Create(HINSTANCE, 'dots_8', {$ifdef FPC_OS_UNICODE}PWideChar{$else}PChar{$endif}(RT_RCDATA)); + Bmp.LoadFromStream(S, Reader); + if not CheckColor(0, 0, 255, 255, 0) then + Halt(5); + if not CheckColor(1, 0, 254, 0, 0) then + Halt(6); + if not CheckColor(0, 1, 0, 0, 254) then + Halt(7); + if not CheckColor(1, 1, 0, 255, 0) then + Halt(8); + S.Free; + + Bmp.Free; + Reader.Free; +end. +