Browse Source

JPEG: support custom CMYK conversions and reading custom APPn headers

Ondrej Pokorny 4 months ago
parent
commit
0d988f2c96

+ 32 - 21
packages/fcl-image/src/fpreadjpeg.pas

@@ -79,6 +79,7 @@ type
     FProgressiveEncoding: boolean;
     FProgressiveEncoding: boolean;
     FError: jpeg_error_mgr;
     FError: jpeg_error_mgr;
     FProgressMgr: TFPJPEGProgressManager;
     FProgressMgr: TFPJPEGProgressManager;
+    FExtensions: jpeg_extensions;
     FInfo: jpeg_decompress_struct;
     FInfo: jpeg_decompress_struct;
     FScale: TJPEGScale;
     FScale: TJPEGScale;
     FPerformance: TJPEGReadPerformance;
     FPerformance: TJPEGReadPerformance;
@@ -87,6 +88,10 @@ type
     procedure SetPerformance(const AValue: TJPEGReadPerformance);
     procedure SetPerformance(const AValue: TJPEGReadPerformance);
     procedure SetSmoothing(const AValue: boolean);
     procedure SetSmoothing(const AValue: boolean);
   protected
   protected
+    function CMYKToRGB(const C, M, Y, K: Byte): TFPColor; virtual;
+    procedure ReadExtAPPn(Marker: int; var Header: array of JOCTET; HeaderLen: uint;
+      var Remaining: INT32; ReadData: jpeg_ext_appn_readdata); virtual;
+
     procedure ReadHeader(Str: TStream; Img: TFPCustomImage); virtual;
     procedure ReadHeader(Str: TStream; Img: TFPCustomImage); virtual;
     procedure ReadPixels(Str: TStream; Img: TFPCustomImage); virtual;
     procedure ReadPixels(Str: TStream; Img: TFPCustomImage); virtual;
     procedure InternalRead(Str: TStream; Img: TFPCustomImage); override;
     procedure InternalRead(Str: TStream; Img: TFPCustomImage); override;
@@ -202,6 +207,14 @@ begin
   // ToDo
   // ToDo
 end;
 end;
 
 
+procedure ReadExtAPPnCallback(cinfo : j_decompress_ptr; marker : int; var header : array of JOCTET; headerlen : uint;
+  var remaining : int32; readdata: jpeg_ext_appn_readdata);
+begin
+  if (cinfo=nil) or (cinfo^.client_data=nil) then exit;
+
+  TFPReaderJPEG(cinfo^.client_data).ReadExtAPPn(marker, header, headerlen, remaining, readdata);
+end;
+
 { TFPReaderJPEG }
 { TFPReaderJPEG }
 
 
 procedure TFPReaderJPEG.SetSmoothing(const AValue: boolean);
 procedure TFPReaderJPEG.SetSmoothing(const AValue: boolean);
@@ -210,6 +223,11 @@ begin
   FSmoothing:=AValue;
   FSmoothing:=AValue;
 end;
 end;
 
 
+procedure TFPReaderJPEG.ReadExtAPPn(Marker: int; var Header: array of JOCTET; HeaderLen: uint; var Remaining: INT32; ReadData: jpeg_ext_appn_readdata);
+begin
+  // override to read extended APPn data
+end;
+
 procedure TFPReaderJPEG.SetPerformance(const AValue: TJPEGReadPerformance);
 procedure TFPReaderJPEG.SetPerformance(const AValue: TJPEGReadPerformance);
 begin
 begin
   if FPerformance=AValue then exit;
   if FPerformance=AValue then exit;
@@ -347,21 +365,6 @@ var
     Img.Colors[P.x, P.y] := C;
     Img.Colors[P.x, P.y] := C;
   end;
   end;
 
 
-  function CorrectCMYK(const C: TFPColor): TFPColor;
-  var
-    MinColor: word;
-  begin
-    // accuracy not 100%
-    if C.red<C.green then MinColor:=C.red
-    else MinColor:= C.green;
-    if C.blue<MinColor then MinColor:= C.blue;
-    if MinColor+ C.alpha>$FF then MinColor:=$FF-C.alpha;
-    Result.red:=(C.red-MinColor) shl 8;
-    Result.green:=(C.green-MinColor) shl 8;
-    Result.blue:=(C.blue-MinColor) shl 8;
-    Result.alpha:=alphaOpaque;
-  end;
-
   procedure OutputScanLines();
   procedure OutputScanLines();
   var
   var
     x: integer;
     x: integer;
@@ -428,12 +431,7 @@ var
         end;
         end;
       JCS_CMYK, JCS_YCCK:
       JCS_CMYK, JCS_YCCK:
         for x:=0 to FInfo.output_width-1 do
         for x:=0 to FInfo.output_width-1 do
-        begin
-          //SetPixel(x, y, CorrectCMYK(TFPColor.New(SampRow^[x*4+0], SampRow^[x*4+1], SampRow^[x*4+2], SampRow^[x*4+3])));
-
-          cmyk :=TStdCMYK.New(SampRow^[x*4+0], SampRow^[x*4+1], SampRow^[x*4+2], SampRow^[x*4+3]);
-          SetPixel(x, y, cmyk.ToExpandedPixel.ToFPColor(false));
-        end;
+          SetPixel(x, y, CMYKToRGB(SampRow^[x*4+0], SampRow^[x*4+1], SampRow^[x*4+2], SampRow^[x*4+3]));
       else
       else
         for x:=0 to FInfo.output_width-1 do begin
         for x:=0 to FInfo.output_width-1 do begin
           Color.Red:=SampRow^[x*3+0] shl 8;
           Color.Red:=SampRow^[x*3+0] shl 8;
@@ -593,6 +591,11 @@ begin
         MemStream.Position:=0;
         MemStream.Position:=0;
         jpeg_stdio_src(@FInfo, @MemStream);
         jpeg_stdio_src(@FInfo, @MemStream);
 
 
+        FInfo.extensions := @FExtensions;
+        FExtensions.read_ext_appn := @ReadExtAPPnCallback;
+
+        FInfo.client_data := Self;
+
         ReadHeader(MemStream, Img);
         ReadHeader(MemStream, Img);
         ReadPixels(MemStream, Img);
         ReadPixels(MemStream, Img);
       finally
       finally
@@ -646,6 +649,14 @@ begin
   inherited Create;
   inherited Create;
 end;
 end;
 
 
+function TFPReaderJPEG.CMYKToRGB(const C, M, Y, K: Byte): TFPColor;
+begin
+  Result.Red := ((C*K) div 255) shl 8;
+  Result.Green := ((M*K) div 255) shl 8;
+  Result.Blue := ((Y*K) div 255) shl 8;
+  Result.Alpha := alphaOpaque;
+end;
+
 destructor TFPReaderJPEG.Destroy;
 destructor TFPReaderJPEG.Destroy;
 begin
 begin
   inherited Destroy;
   inherited Destroy;

+ 10 - 3
packages/pasjpeg/src/jdmarker.pas

@@ -17,6 +17,7 @@ Unit JdMarker;
 interface
 interface
 
 
 {$I jconfig.inc}
 {$I jconfig.inc}
+{$modeswitch nestedprocvars}
 
 
 {$IFDEF FPC_DOTTEDUNITS}
 {$IFDEF FPC_DOTTEDUNITS}
 uses
 uses
@@ -1665,13 +1666,13 @@ begin
 end;
 end;
 
 
 {LOCAL}
 {LOCAL}
-function examine_app1 (cinfo : j_decompress_ptr;
+procedure examine_app1 (cinfo : j_decompress_ptr;
                         var header : array of JOCTET;
                         var header : array of JOCTET;
                         headerlen : uint;
                         headerlen : uint;
                         var remaining : INT32;
                         var remaining : INT32;
                         datasrc : jpeg_source_mgr_ptr;
                         datasrc : jpeg_source_mgr_ptr;
                         var next_input_byte : JOCTETptr;
                         var next_input_byte : JOCTETptr;
-                        var bytes_in_buffer : size_t): Boolean;
+                        var bytes_in_buffer : size_t);
 
 
 { Read Exif marker.
 { Read Exif marker.
   headerlen is # of bytes at header[], remaining is length of rest of marker header.
   headerlen is # of bytes at header[], remaining is length of rest of marker header.
@@ -1790,7 +1791,7 @@ begin
 
 
     // read data
     // read data
     if not Read16(numRecords) then
     if not Read16(numRecords) then
-      Exit(False);
+      Exit;
 
 
     for i:=1 to numRecords do
     for i:=1 to numRecords do
     begin
     begin
@@ -1820,6 +1821,12 @@ begin
         my_marker_ptr(cinfo^.marker)^.handle_exif_tag(cinfo, ifdRec, BigEndian, data, EXIF_TAGPARENT_PRIMARY);
         my_marker_ptr(cinfo^.marker)^.handle_exif_tag(cinfo, ifdRec, BigEndian, data, EXIF_TAGPARENT_PRIMARY);
       end;
       end;
     end;
     end;
+  end else
+  if Assigned(cinfo.extensions) and Assigned(cinfo.extensions^.read_ext_appn) and (headerlen>0) then
+  begin
+    BigEndian := False;
+    Offset := APP1_HEADER_LEN;
+    cinfo.extensions^.read_ext_appn(cinfo, M_APP1, header, headerlen, remaining, Read);
   end;
   end;
 end;
 end;
 
 

+ 11 - 0
packages/pasjpeg/src/jpeglib.pas

@@ -12,6 +12,7 @@ Unit JPEGLib;
 interface
 interface
 
 
 {$I jconfig.inc}
 {$I jconfig.inc}
+{$modeswitch nestedprocvars}
 
 
 { First we include the configuration files that record how this
 { First we include the configuration files that record how this
   installation of the JPEG library is set up.  jconfig.h can be
   installation of the JPEG library is set up.  jconfig.h can be
@@ -719,6 +720,15 @@ type
     new_color_map : procedure(cinfo : j_decompress_ptr);
     new_color_map : procedure(cinfo : j_decompress_ptr);
   end;
   end;
 
 
+{ JPEG extensions }
+  jpeg_ext_appn_readdata = function (const Buffer: Pointer; numtoread: uint): Boolean is nested;
+  jpeg_extensions_ptr = ^jpeg_extensions;
+  jpeg_extensions = record
+    // read extended (unknown) APPn data
+    read_ext_appn : procedure(cinfo : j_decompress_ptr; marker : int; var header : array of JOCTET; headerlen : uint;
+      var remaining : int32; readdata: jpeg_ext_appn_readdata);
+  end;
+
   {int8array = Array[0..8-1] of int;}
   {int8array = Array[0..8-1] of int;}
   int8array = Array[0..8-1] of longint; { for TP FormatStr }
   int8array = Array[0..8-1] of longint; { for TP FormatStr }
   TFormatCallback = procedure  (cinfo : j_common_ptr; var buffer : shortstring);
   TFormatCallback = procedure  (cinfo : j_common_ptr; var buffer : shortstring);
@@ -1280,6 +1290,7 @@ type
     upsample : jpeg_upsampler_ptr;
     upsample : jpeg_upsampler_ptr;
     cconvert : jpeg_color_deconverter_ptr;
     cconvert : jpeg_color_deconverter_ptr;
     cquantize : jpeg_color_quantizer_ptr;
     cquantize : jpeg_color_quantizer_ptr;
+    extensions : jpeg_extensions_ptr;
   end;
   end;
 
 
 { Decompression startup: read start of JPEG datastream to see what's there
 { Decompression startup: read start of JPEG datastream to see what's there