From 30e7bfb666d15ab494daa888416859da9ee893a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Van=20Canneyt?= Date: Fri, 4 Aug 2023 09:42:23 +0200 Subject: [PATCH] * Apply Merge request !468 (use JPEG out_color_space) --- packages/fcl-image/src/fpcolorspace.pas | 125 ++++++++++++++++------ packages/fcl-image/src/fpreadjpeg.pas | 133 +++++++++++++++--------- packages/fcl-image/src/fpreadtiff.pas | 7 +- 3 files changed, 181 insertions(+), 84 deletions(-) diff --git a/packages/fcl-image/src/fpcolorspace.pas b/packages/fcl-image/src/fpcolorspace.pas index 46d3646322..a60c7f1089 100644 --- a/packages/fcl-image/src/fpcolorspace.pas +++ b/packages/fcl-image/src/fpcolorspace.pas @@ -277,6 +277,10 @@ type function ToExpanded(AGammaExpansion: boolean = true): TExpandedPixel; function ToHSLAPixel(AGammaExpansion: boolean = true): THSLAPixel; function ToGSBAPixel(AGammaExpansion: boolean = true): TGSBAPixel; + function ToStdRGBA: TStdRGBA; + function ToStdHSLA: TStdHSLA; + function ToStdHSVA: TStdHSVA; + function ToStdCMYK: TStdCMYK; end; { TExpandedPixelHelper } @@ -300,11 +304,15 @@ type { TStdRGBAHelper } TStdRGBAHelper = record helper for TStdRGBA + function ToFPColor: TFPColor; function ToExpandedPixel: TExpandedPixel; function ToLinearRGBA: TLinearRGBA; function ToStdHSLA: TStdHSLA; function ToStdHSVA: TStdHSVA; function ToStdCMYK: TStdCMYK; + {** SamplePrecision = 2^(YCbCrSamplePrecision-1) } + function ToYCbCr(const AStd:TYCbCrSTD=YCbCr_JPG; ASamplePrecision:Single=0.5): TYCbCr; overload; + function ToYCbCr(LumaRed:Single=0.299; LumaGreen:Single=0.587; LumaBlue:Single=0.114): TYCbCr; overload; end; { TAdobeRGBAHelper } @@ -317,6 +325,7 @@ type { TStdHSLAHelper } TStdHSLAHelper = record helper for TStdHSLA + function ToFPColor: TFPColor; function ToStdRGBA: TStdRGBA; function ToStdHSVA: TStdHSVA; function ToExpandedPixel: TExpandedPixel; @@ -325,6 +334,7 @@ type { TStdHSVAHelper } TStdHSVAHelper = record helper for TStdHSVA + function ToFPColor: TFPColor; function ToStdRGBA: TStdRGBA; function ToStdHSLA: TStdHSLA; end; @@ -332,6 +342,7 @@ type { TStdCMYKHelper } TStdCMYKHelper = record helper for TStdCMYK + function ToFPColor(const AAlpha: Word=$ffff): TFPColor; function ToStdRGBA(AAlpha: Single = 1): TStdRGBA; function ToExpandedPixel: TExpandedPixel;overload; function ToExpandedPixel(AAlpha: word): TExpandedPixel;overload; @@ -350,9 +361,6 @@ type function ToXYZA: TXYZA; overload; function ToXYZA(const AReferenceWhite: TXYZReferenceWhite): TXYZA; overload; function ToStdRGBA: TStdRGBA; - {** SamplePrecision = 2^(YCbCrSamplePrecision-1) } - function ToYCbCr(const AStd:TYCbCrSTD=YCbCr_JPG; ASamplePrecision:Single=0.5): TYCbCr; overload; - function ToYCbCr(LumaRed:Single=0.299; LumaGreen:Single=0.587; LumaBlue:Single=0.114): TYCbCr; overload; end; { TXYZAHelper } @@ -399,8 +407,8 @@ type TYCbCrHelper = record helper for TYCbCr {** SamplePrecision = 2^(YCbCrSamplePrecision-1) } - function ToLinearRGBA(const AStd:TYCbCrSTD=YCbCr_JPG; ASamplePrecision:Single=0.5): TLinearRGBA; overload; - function ToLinearRGBA(LumaRed:Single=0.299; LumaGreen:Single=0.587; LumaBlue:Single=0.114): TLinearRGBA; overload; + function ToStdRGBA(const AStd:TYCbCrSTD=YCbCr_601; ASamplePrecision:Single=0.5): TStdRGBA; overload; + function ToStdRGBA(LumaRed:Single=0.299; LumaGreen:Single=0.587; LumaBlue:Single=0.114; ASamplePrecision:Single=0.5): TStdRGBA; overload; end; {* How to handle overflow when converting from XYZ } @@ -1396,6 +1404,30 @@ begin result.FromFPColor(self, AGammaExpansion); end; +function TFPColorHelper.ToStdRGBA: TStdRGBA; +const oneOver65535 = 1/65535; +begin + result.red := red * oneOver65535; + result.green := green * oneOver65535; + result.blue := blue * oneOver65535; + result.alpha := alpha * oneOver65535; +end; + +function TFPColorHelper.ToStdHSLA: TStdHSLA; +begin + result :=self.ToStdRGBA.ToStdHSLA; +end; + +function TFPColorHelper.ToStdHSVA: TStdHSVA; +begin + result :=self.ToStdRGBA.ToStdHSVA; +end; + +function TFPColorHelper.ToStdCMYK: TStdCMYK; +begin + result :=self.ToStdRGBA.ToStdCMYK; +end; + { TExpandedPixelHelper } function TExpandedPixelHelper.ToHSLAPixel: THSLAPixel; @@ -1470,6 +1502,14 @@ end; { TStdRGBAHelper } +function TStdRGBAHelper.ToFPColor: TFPColor; +begin + result.red := ClampInt(round(red * 65535), 0, 65535); + result.green := ClampInt(round(green * 65535), 0, 65535); + result.blue := ClampInt(round(blue * 65535), 0, 65535); + result.alpha := ClampInt(round(alpha * 65535), 0, 65535); +end; + function TStdRGBAHelper.ToExpandedPixel: TExpandedPixel; begin result.red := FPGammaExpansion(self.red); @@ -1576,6 +1616,26 @@ begin end; end; +function TStdRGBAHelper.ToYCbCr(const AStd: TYCbCrSTD; ASamplePrecision:Single=0.5): TYCbCr; +begin + with self, YCbCrSTD_Factors[AStd] do + begin + result.Y := a * red + b * green + c * blue; + result.Cb := ((blue - result.Y) / d)+ASamplePrecision; + result.Cr := ((red - result.Y) / e)+ASamplePrecision; + end; +end; + +function TStdRGBAHelper.ToYCbCr(LumaRed: Single; LumaGreen: Single; LumaBlue: Single): TYCbCr; +begin + with self do + begin + result.Y := ( LumaRed * red + LumaGreen * green + LumaBlue * blue ); + result.Cb := ( blue - result.Y ) / ( 2 - 2 * LumaBlue ); + result.Cr := ( red - result.Y ) / ( 2 - 2 * LumaRed ); + end; +end; + { TAdobeRGBAHelper } function TAdobeRGBAHelper.ToXYZA: TXYZA; @@ -1608,6 +1668,11 @@ end; { TStdHSLAHelper } +function TStdHSLAHelper.ToFPColor: TFPColor; +begin + result :=self.ToStdRGBA.ToFPColor; +end; + function TStdHSLAHelper.ToStdRGBA: TStdRGBA; var C, X, M, rp, gp, bp, sp, lp, h360: single; @@ -1687,6 +1752,11 @@ end; { TStdHSVAHelper } +function TStdHSVAHelper.ToFPColor: TFPColor; +begin + result :=self.ToStdRGBA.ToFPColor; +end; + function TStdHSVAHelper.ToStdRGBA: TStdRGBA; var C, X, M, rp, gp, bp, sp, vp: single; @@ -1775,24 +1845,30 @@ end; function TStdCMYKHelper.ToExpandedPixel: TExpandedPixel; begin - self.ToStdRGBA.ToExpandedPixel; + result :=self.ToStdRGBA.ToExpandedPixel; end; function TStdCMYKHelper.ToExpandedPixel(AAlpha: word): TExpandedPixel; begin - self.ToStdRGBA(AAlpha).ToExpandedPixel; + result :=self.ToStdRGBA(AAlpha).ToExpandedPixel; +end; + +function TStdCMYKHelper.ToFPColor(const AAlpha: Word): TFPColor; +begin + result :=self.ToStdRGBA.ToFPColor; + result.alpha := AAlpha end; { TLabAHelper } function TLabAHelper.ToExpandedPixel: TExpandedPixel; begin - self.ToXYZA.ToLinearRGBA.ToExpandedPixel; + result :=self.ToXYZA.ToLinearRGBA.ToExpandedPixel; end; function TLabAHelper.ToExpandedPixel(const AReferenceWhite: TXYZReferenceWhite): TExpandedPixel; begin - self.ToXYZA(AReferenceWhite).ToLinearRGBA(AReferenceWhite).ToExpandedPixel; + result :=self.ToXYZA(AReferenceWhite).ToLinearRGBA(AReferenceWhite).ToExpandedPixel; end; function TLabAHelper.ToXYZA: TXYZA; @@ -1854,22 +1930,25 @@ end; { TYCbCrHelper } -function TYCbCrHelper.ToLinearRGBA(const AStd: TYCbCrSTD; ASamplePrecision:Single): TLinearRGBA; +function TYCbCrHelper.ToStdRGBA(const AStd: TYCbCrSTD; ASamplePrecision:Single): TStdRGBA; begin with self, YCbCrSTD_Factors[AStd] do begin + //"analog" Y is in the range 0 to 1 ; Cb and Cr in the range -0.5 to +0.5 + //"digital" are normalized to 0..255; In 601 Standard values are between 16 and 235 for Y, 16 to 240 for Cb and Cr. + result.red := Y + e * (Cr-ASamplePrecision); result.green := Y - (a * e / b) * (Cr-ASamplePrecision) - (c * d / b) * (Cb-ASamplePrecision); result.blue := Y + d * (Cb-ASamplePrecision); end; end; -function TYCbCrHelper.ToLinearRGBA(LumaRed: Single; LumaGreen: Single; LumaBlue: Single): TLinearRGBA; +function TYCbCrHelper.ToStdRGBA(LumaRed: Single; LumaGreen: Single; LumaBlue: Single; ASamplePrecision:Single): TStdRGBA; begin with self do begin - result.red := Cr * ( 2 - 2 * LumaRed ) + Y; - result.blue := Cb * ( 2 - 2 * LumaBlue ) + Y; + result.red := (Cr-ASamplePrecision) * ( 2 - 2 * LumaRed ) + Y; + result.blue := (Cb-ASamplePrecision) * ( 2 - 2 * LumaBlue ) + Y; result.green := ( Y - LumaBlue * result.blue - LumaRed * result.red ) / LumaGreen; end; end; @@ -1933,26 +2012,6 @@ begin result.alpha := self.alpha; end; -function TLinearRGBAHelper.ToYCbCr(const AStd: TYCbCrSTD; ASamplePrecision:Single=0.5): TYCbCr; -begin - with self, YCbCrSTD_Factors[AStd] do - begin - result.Y := a * red + b * green + c * blue; - result.Cb := ((blue - result.Y) / d)+ASamplePrecision; - result.Cr := ((red - result.Y) / e)+ASamplePrecision; - end; -end; - -function TLinearRGBAHelper.ToYCbCr(LumaRed: Single; LumaGreen: Single; LumaBlue: Single): TYCbCr; -begin - with self do - begin - result.Y := ( LumaRed * red + LumaGreen * green + LumaBlue * blue ); - result.Cb := ( blue - result.Y ) / ( 2 - 2 * LumaBlue ); - result.Cr := ( red - result.Y ) / ( 2 - 2 * LumaRed ); - end; -end; - { TXYZAHelper } function TXYZAHelper.ToLinearRGBA: TLinearRGBA; diff --git a/packages/fcl-image/src/fpreadjpeg.pas b/packages/fcl-image/src/fpreadjpeg.pas index c91ce0fb91..d48835a1a5 100644 --- a/packages/fcl-image/src/fpreadjpeg.pas +++ b/packages/fcl-image/src/fpreadjpeg.pas @@ -109,6 +109,25 @@ type implementation +uses FPColorSpace; + +type + int_Color_Table = array[0..MAXJSAMPLE+1-1] of int; + int_table_ptr = ^int_Color_Table; + INT32_Color_Table = array[0..MAXJSAMPLE+1-1] of INT32; + INT32_table_ptr = ^INT32_Color_Table; + my_cconvert_ptr = ^my_color_deconverter; + my_color_deconverter = record + pub : jpeg_color_deconverter; { public fields } + + { Private state for YCC^.RGB conversion } + Cr_r_tab : int_table_ptr; { => table for Cr to R conversion } + Cb_b_tab : int_table_ptr; { => table for Cb to B conversion } + Cr_g_tab : INT32_table_ptr; { => table for Cr to G conversion } + Cb_g_tab : INT32_table_ptr; { => table for Cb to G conversion } + end; + + procedure ReadCompleteStreamToStream(SrcStream, DestStream: TStream; StartSize: integer); var @@ -353,24 +372,15 @@ var Result.alpha:=alphaOpaque; end; - function CorrectYCCK(const C: TFPColor): TFPColor; - var - MinColor: word; - begin - if C.red$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; + //ycbcr:TYCbCr; + cmyk:TStdCMYK; + yy,cb,cr :Int; + shift_temp : INT32; + cconvert : my_cconvert_ptr; + begin Color.Alpha:=alphaOpaque; y:=0; @@ -381,41 +391,68 @@ var ReturnValue:=false; break; end; - if (FInfo.jpeg_color_space = JCS_CMYK) then - for x:=0 to FInfo.output_width-1 do begin - Color.Red:=SampRow^[x*4+0]; - Color.Green:=SampRow^[x*4+1]; - Color.Blue:=SampRow^[x*4+2]; - Color.alpha:=SampRow^[x*4+3]; - SetPixel(x, y, CorrectCMYK(Color)); - end + + Case FInfo.out_color_space of + JCS_GRAYSCALE : + for x:=0 to FInfo.output_width-1 do + begin + c:= SampRow^[x] shl 8; + Color.Red:=c; + Color.Green:=c; + Color.Blue:=c; + SetPixel(x, y, Color); + end; + JCS_YCbCr : + for x:=0 to FInfo.output_width-1 do + begin + //MaxM: YCbCr is defined per CCIR 601-1 + // Y (0 to 1.0) and Cb,Cr (-0.5 to 0.5) is normalized to the range 0..MAXJSAMPLE + // We have two ways to convert them, the most accurate is to denormalize + // the values like the following commented code, or as is and set SamplePrecision to CENTERJSAMPLE + // ycbcr.Y :=SampRow^[x*3+0]/256; + // ycbcr.Cb :=(SampRow^[x*3+1]-128)/256; + // ycbcr.Cr :=(SampRow^[x*3+2]-128)/256; + //ycbcr :=TYCbCr.New(SampRow^[x*3+0], SampRow^[x*3+1], SampRow^[x*3+2]); + //SetPixel(x, y, ycbcr.ToStdRGBA(YCBCr_601, CENTERJSAMPLE).ToExpandedPixel.ToFPColor(false)); + + //Use the same Code of PasJPeg (ycc_rgb_convert function) + yy :=SampRow^[x*3+0]; + cb :=SampRow^[x*3+1]; + cr :=SampRow^[x*3+2]; + cconvert :=my_cconvert_ptr(FInfo.cconvert); + Color.Red := (FInfo.sample_range_limit^[yy + cconvert^.Cr_r_tab^[cr]]); + shift_temp := cconvert^.Cb_g_tab^[cb] + cconvert^.Cr_g_tab^[cr]; + if shift_temp < 0 then { SHIFT arithmetic RIGHT } + Color.Green := (FInfo.sample_range_limit^[yy + int((shift_temp shr 16) + or ( (not INT32(0)) shl (32-16)))]) + else + Color.Green := (FInfo.sample_range_limit^[yy + int(shift_temp shr 16)]); + + Color.Blue := (FInfo.sample_range_limit^[yy + cconvert^.Cb_b_tab^[cb]]); + + Color.Red:=Color.Red shl 8; + Color.Green:=Color.Green shl 8; + Color.Blue:=Color.Blue shl 8; + + SetPixel(x, y, Color); + 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; else - if (FInfo.jpeg_color_space = JCS_YCCK) then - for x:=0 to FInfo.output_width-1 do begin - Color.Red:=SampRow^[x*4+0]; - Color.Green:=SampRow^[x*4+1]; - Color.Blue:=SampRow^[x*4+2]; - Color.alpha:=SampRow^[x*4+3]; - SetPixel(x, y, CorrectYCCK(Color)); - end - else - if fgrayscale then begin - for x:=0 to FInfo.output_width-1 do begin - c:= SampRow^[x] shl 8; - Color.Red:=c; - Color.Green:=c; - Color.Blue:=c; - SetPixel(x, y, Color); - end; - end - else begin - for x:=0 to FInfo.output_width-1 do begin - Color.Red:=SampRow^[x*3+0] shl 8; - Color.Green:=SampRow^[x*3+1] shl 8; - Color.Blue:=SampRow^[x*3+2] shl 8; - SetPixel(x, y, Color); - end; + for x:=0 to FInfo.output_width-1 do begin + Color.Red:=SampRow^[x*3+0] shl 8; + Color.Green:=SampRow^[x*3+1] shl 8; + Color.Blue:=SampRow^[x*3+2] shl 8; + SetPixel(x, y, Color); + end; end; + inc(y); end; end; diff --git a/packages/fcl-image/src/fpreadtiff.pas b/packages/fcl-image/src/fpreadtiff.pas index f1b3290fb9..671ad947ca 100644 --- a/packages/fcl-image/src/fpreadtiff.pas +++ b/packages/fcl-image/src/fpreadtiff.pas @@ -1928,7 +1928,8 @@ var //MaxM: Test the difference // result:=CMYKToFPColor(ChannelValues[0],ChannelValues[1],ChannelValues[2],ChannelValues[3]); cmyk :=TStdCMYK.New(ChannelValues[0]/$ffff, ChannelValues[1]/$ffff, ChannelValues[2]/$ffff, ChannelValues[3]/$ffff); - result :=cmyk.ToExpandedPixel.ToFPColor(true); //MaxM: in Future we can use GammaCompression + result :=cmyk.ToExpandedPixel.ToFPColor(true); //Use of GammaCompression or direct? + //result :=cmyk.ToFPColor; end; 6: // YCBCR: CCIR 601 @@ -1936,8 +1937,8 @@ var ycbcr :=TYCbCr.New(ChannelValues[0]/$ffff, ChannelValues[1]/$ffff, ChannelValues[2]/$ffff); if IFD.YCbCr_LumaRed<>0 - then result :=ycbcr.ToLinearRGBA(IFD.YCbCr_LumaRed, IFD.YCbCr_LumaGreen, IFD.YCbCr_LumaBlue).ToExpandedPixel.ToFPColor(false) - else result :=ycbcr.ToLinearRGBA(YCbCr_601).ToExpandedPixel.ToFPColor(false); + then result :=ycbcr.ToStdRGBA(IFD.YCbCr_LumaRed, IFD.YCbCr_LumaGreen, IFD.YCbCr_LumaBlue).ToFPColor + else result :=ycbcr.ToStdRGBA(YCbCr_601).ToFPColor; end; //8: CIELAB: 1976 CIE L*a*b*