JPEG: support custom CMYK conversions and reading custom APPn headers

This commit is contained in:
Ondrej Pokorny 2025-03-28 10:17:33 +01:00 committed by Michael Van Canneyt
parent 84a852bc13
commit 0d988f2c96
3 changed files with 53 additions and 24 deletions

View File

@ -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, CMYKToRGB(SampRow^[x*4+0], SampRow^[x*4+1], SampRow^[x*4+2], SampRow^[x*4+3]));
//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;
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;

View File

@ -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;

View File

@ -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