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;
FError: jpeg_error_mgr;
FProgressMgr: TFPJPEGProgressManager;
FExtensions: jpeg_extensions;
FInfo: jpeg_decompress_struct;
FScale: TJPEGScale;
FPerformance: TJPEGReadPerformance;
@ -87,6 +88,10 @@ type
procedure SetPerformance(const AValue: TJPEGReadPerformance);
procedure SetSmoothing(const AValue: boolean);
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 ReadPixels(Str: TStream; Img: TFPCustomImage); virtual;
procedure InternalRead(Str: TStream; Img: TFPCustomImage); override;
@ -202,6 +207,14 @@ begin
// ToDo
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 }
procedure TFPReaderJPEG.SetSmoothing(const AValue: boolean);
@ -210,6 +223,11 @@ begin
FSmoothing:=AValue;
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);
begin
if FPerformance=AValue then exit;
@ -347,21 +365,6 @@ var
Img.Colors[P.x, P.y] := C;
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();
var
x: integer;
@ -428,12 +431,7 @@ var
end;
JCS_CMYK, JCS_YCCK:
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
for x:=0 to FInfo.output_width-1 do begin
Color.Red:=SampRow^[x*3+0] shl 8;
@ -593,6 +591,11 @@ begin
MemStream.Position:=0;
jpeg_stdio_src(@FInfo, @MemStream);
FInfo.extensions := @FExtensions;
FExtensions.read_ext_appn := @ReadExtAPPnCallback;
FInfo.client_data := Self;
ReadHeader(MemStream, Img);
ReadPixels(MemStream, Img);
finally
@ -646,6 +649,14 @@ begin
inherited Create;
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;
begin
inherited Destroy;

View File

@ -17,6 +17,7 @@ Unit JdMarker;
interface
{$I jconfig.inc}
{$modeswitch nestedprocvars}
{$IFDEF FPC_DOTTEDUNITS}
uses
@ -1665,13 +1666,13 @@ begin
end;
{LOCAL}
function examine_app1 (cinfo : j_decompress_ptr;
procedure 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;
var bytes_in_buffer : size_t);
{ Read Exif marker.
headerlen is # of bytes at header[], remaining is length of rest of marker header.
@ -1790,7 +1791,7 @@ begin
// read data
if not Read16(numRecords) then
Exit(False);
Exit;
for i:=1 to numRecords do
begin
@ -1820,6 +1821,12 @@ begin
my_marker_ptr(cinfo^.marker)^.handle_exif_tag(cinfo, ifdRec, BigEndian, data, EXIF_TAGPARENT_PRIMARY);
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;

View File

@ -12,6 +12,7 @@ Unit JPEGLib;
interface
{$I jconfig.inc}
{$modeswitch nestedprocvars}
{ First we include the configuration files that record how this
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);
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 longint; { for TP FormatStr }
TFormatCallback = procedure (cinfo : j_common_ptr; var buffer : shortstring);
@ -1280,6 +1290,7 @@ type
upsample : jpeg_upsampler_ptr;
cconvert : jpeg_color_deconverter_ptr;
cquantize : jpeg_color_quantizer_ptr;
extensions : jpeg_extensions_ptr;
end;
{ Decompression startup: read start of JPEG datastream to see what's there