From 123e934a4f1778eb6cb9d959437a9ff76b5db34c Mon Sep 17 00:00:00 2001 From: jesusr Date: Fri, 14 Aug 2020 13:39:02 +0000 Subject: [PATCH] PowerPDF: use fcl-image for image processing. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7602 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/powerpdf/PdfImageLazTools.pas | 175 ++++++++++++++++ components/powerpdf/PdfImages.pas | 244 +++++++++++------------ components/powerpdf/PdfJpegImage.pas | 44 ++++ components/powerpdf/PdfTypes.pas | 1 + components/powerpdf/pack_powerpdf.lpk | 12 +- components/powerpdf/pack_powerpdf.pas | 2 +- 6 files changed, 341 insertions(+), 137 deletions(-) create mode 100644 components/powerpdf/PdfImageLazTools.pas diff --git a/components/powerpdf/PdfImageLazTools.pas b/components/powerpdf/PdfImageLazTools.pas new file mode 100644 index 000000000..11ddf929c --- /dev/null +++ b/components/powerpdf/PdfImageLazTools.pas @@ -0,0 +1,175 @@ +unit PdfImageLazTools; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + FPImage, + FPReadBMP, FPReadPNG, FPReadJPEG, // make sure we have the basic readers + Graphics, PDFTypes; + + function CreateAlphaImage(const aWidth, aHeight: Integer): TFPMemoryImage; + procedure ConvertGraphicToFPImage(AImage: TGraphic; out fpImg: TFPCustomImage); + function CreateMaskStream(AImage: TFPCustomImage): TPDfImage; + function CreateIndexedColorArray(ABitmap: TFPCustomImage): TPdfArray; + +implementation + +type + + { TRasterImageHelper } + + TRasterImageHelper = class helper for TRasterImage + public + function RequestRawStream(out rawStream: TMemoryStream): boolean; + end; + +function CreateAlphaImage(const aWidth, aHeight: Integer): TFPMemoryImage; +var + aColor: TFPColor; + x: Integer; +begin + result := TFPMemoryImage.Create(aWidth, aHeight); + result.UsePalette := true; + result.Palette.Count := 256; + for x:=0 to $FF do + begin + aColor.Red:=x; + aColor.Red:=(aColor.Red shl 8) + aColor.Red; + aColor.Green:=aColor.Red; + aColor.Blue:=aColor.Red; + result.Palette.Color[x]:=aColor; + end; +end; + +procedure ConvertGraphicToFPImage(AImage: TGraphic; out fpImg: TFPCustomImage); +var + rawImgStream: TMemoryStream = nil; + useOriginalStream: boolean = false; +begin + if (AImage is TRasterImage) then + useOriginalStream := TRasterImage(AImage).RequestRawStream(rawImgStream); + + if not useOriginalStream then begin + rawImgStream := TMemoryStream.Create; + AImage.SaveToStream(rawImgStream); + rawImgStream.Position := 0; + end; + + try + fpImg := TFPMemoryImage.Create(0, 0); + fpImg.UsePalette := false; + try + fpImg.LoadFromStream(rawImgStream); + except + fpImg.Free; + fpImg := nil; + raise + end; + finally + if not useOriginalStream then + rawImgStream.Free; + end; + +end; + +function CreateMaskStream(AImage: TFPCustomImage): TPDfImage; +var + pb: PByteArray; + y: Integer; + x: Integer; +begin + result := TPdfImage.CreateStream(nil); + with result do + try + with Attributes do + begin + AddItem('Type', TPdfName.CreateName('XObject')); + AddItem('Subtype', TPdfName.CreateName('Image')); + AddItem('Width', TPdfNumber.CreateNumber(aImage.Width)); + AddItem('Height', TPdfNumber.CreateNumber(aImage.Height)); + AddItem('BitsPerComponent', TPdfNumber.CreateNumber(8)); + AddItem('ColorSpace',TPdfName.CreateName('DeviceGray')); + if USE_ZLIB then + PdfArrayByName('Filter').AddItem(TPdfName.CreateName('FlateDecode')); + + new(pb); + for y := 0 to AImage.Height - 1 do + begin + for x := 0 to AImage.Width-1 do + pb^[x] := AImage.Pixels[x,y]; + Stream.Write(pb^, AImage.Width); + end; + dispose(pb); + end; + + finally + end; +end; + +function CreateIndexedColorArray(ABitmap: TFPCustomImage): TPdfArray; +var + i: integer; + ColorTable: TPdfBinary; + NumOfColors: integer; + S: string; + + procedure AddColor(Red,Green,Blue:byte); + begin + S := S + IntToHex(Red, 2) + + IntToHex(Green, 2) + + IntToHex(Blue, 2) + + ' '; + end; + +begin + // creating color table from palette of bitmap. + if (not ABitmap.UsePalette) then + raise EPdfInvalidImageFormat.Create('Expected indexed color image'); + + NumOfColors := ABitmap.Palette.Count; + + // get/check palette entries + if ABitmap.Palette.Count=0 then + raise EPdfInvalidImageFormat.Create('failed to get Palette..'); + + ColorTable := TPdfBinary.Create; + S := '<'; + + for i := 0 to NumOfColors - 1 do + if i'; + ColorTable.Stream.Write(PChar(S)^, Length(S)); + + result := TPdfArray.CreateArray(nil); + with result do + begin + AddItem(TPdfName.CreateName('Indexed')); + AddItem(TPdfName.CreateName('DeviceRGB')); + AddItem(TPdfNumber.CreateNumber(NumOfColors - 1)); + AddItem(ColorTable); + end; +end; + +{ TRasterImageHelper } + +function TRasterImageHelper.RequestRawStream(out rawStream: TMemoryStream + ): boolean; +begin + // make direct use of the saved original stream to avoid re-copying + // this should be ok as it is very unlikely to change in the future. + rawStream := FSharedImage.SaveStream; + result := rawStream<>nil; + if result then + rawStream.Position := 0; +end; + +end. + diff --git a/components/powerpdf/PdfImages.pas b/components/powerpdf/PdfImages.pas index 476a88c60..0d90487b0 100644 --- a/components/powerpdf/PdfImages.pas +++ b/components/powerpdf/PdfImages.pas @@ -23,30 +23,29 @@ * 2001.09.01 changed the implementation of the image. * *} -{$IFDEF LAZ_POWERPDF} -{$H+} +{$IFDEF FPC} +{$MODE OBJFPC}{$H+} {$ENDIF} + unit PdfImages; interface {$IFDEF UNIX} - {$IFDEF LAZ_POWERPDF} - {$ELSE} + {$IFNDEF FPC} {$DEFINE USE_CLX} {$ENDIF} {$ENDIF} uses - SysUtils, - {$IFNDEF USE_CLX} {$IFDEF LAZ_POWERPDF} - LCLType, LCLIntf, Graphics, FPImage, IntfGraphics, BmpComn, + SysUtils, LCLType, LCLIntf, Graphics, FPImage, IntfGraphics, GraphType, PdFImageLazTools, {$ELSE} - Windows, Graphics, + {$IFNDEF USE_CLX} + Windows, SysUtils, Graphics, + {$ELSE} + SysUtils, QGraphics, Qt, {$ENDIF} - {$ELSE} - QGraphics, Qt, {$ENDIF} Classes, PdfTypes, PdfDoc; @@ -60,14 +59,13 @@ type TPdfBitmapImage = class(TPdfImageCreator) private + {$IFNDEF LAZ_POWERPDF} function CreateIndexedColorArray(ABitmap: TBitmap): TPdfArray; - function CreateMaskStream(AImage: TFPCustomImage): TPDfImage; + {$ENDIF} public function CreateImage(AImage: TGraphic; ObjectMgr: TPdfObjectMgr=nil): TPdfImage; override; end; - EPdfInvalidImageFormat = class(Exception); - function CreatePdfImage(AImage: TGraphic; ImageClassName: string; ObjectMgr: TPdfObjectMgr=nil): TPdfImage; implementation @@ -103,6 +101,106 @@ type PColorTable = ^TColorTable; {$ENDIF} +{$IFDEF LAZ_POWERPDF} +function TPdfBitmapImage.CreateImage(AImage: TGraphic; ObjectMgr: TPdfObjectMgr=nil): TPdfImage; +var + fpImg: TFPCustomImage; + Alpha: TFPMemoryImage; + x, y, z: integer; + pb: PByteArray; + b: Byte; + aColor : TFPColor; + HasAlpha: Boolean; + MaskImage: TPdfImage; + + procedure AddItems; + begin + with Result.Attributes do + begin + AddItem('Width', TPdfNumber.CreateNumber(aImage.Width)); + AddItem('Height', TPdfNumber.CreateNumber(aImage.Height)); + AddItem('BitsPerComponent', TPdfNumber.CreateNumber(8)); + if USE_ZLIB then + PdfArrayByName('Filter').AddItem(TPdfName.CreateName('FlateDecode')); + end; + end; + +begin + + result := TPdfImage.CreateStream(nil); + with result do + try + + with Attributes do + begin + AddItem('Type', TPdfName.CreateName('XObject')); + AddItem('Subtype', TPdfName.CreateName('Image')); + end; + + // Convert a LCL TGraphic into a TFPCustomImage + ConvertGraphicToFPImage(AImage, fpImg); + + if fpImg.UsePalette and (fpImg.Palette.Count>0) then + begin + for y := 0 to fpImg.Height - 1 do + begin + new(pb); + for x := 0 to fpImg.Width-1 do + pb^[x] := fpImg.Pixels[x,y]; + Stream.Write(pb^, fpImg.Width); + dispose(pb); + end; + Attributes.AddItem('ColorSpace', CreateIndexedColorArray(fpImg)); + end else + begin + + Alpha := CreateAlphaImage(AImage.Width, AImage.Height); + hasAlpha := false; + + for y := 0 to fpImg.Height - 1 do + begin + new(pb); + for x := 0 to fpImg.Width-1 do + begin + aColor := fpImg.Colors[x,y]; + z:=1; + pb^[ 0 ] := acolor.red shr 8; + pb^[ z ] := acolor.green shr 8; + pb^[ z+1 ] := acolor.blue shr 8; + Stream.write(pb[ 0 ], 3); + + b := acolor.alpha shr 8; + Alpha.Pixels[x,y] := b; + + if acolor.Alpha<>AlphaOpaque then + HasAlpha := true; + end; + dispose(pb); + end; + + if HasAlpha then begin + MaskImage := CreateMaskStream(Alpha); + if ObjectMgr<>nil then + ObjectMgr.AddObject(MaskImage); + Attributes.AddItem('SMask', MaskImage); + end; + + Alpha.Free; + + Attributes.AddItem('ColorSpace', TPdfName.CreateName('DeviceRGB')); + end; + AddItems; + + fpImg.Free; + + except + result.free; + raise; + end; +end; + +{$ELSE} + function TPdfBitmapImage.CreateIndexedColorArray(ABitmap: TBitmap): TPdfArray; var {$IFNDEF USE_CLX} @@ -164,54 +262,12 @@ begin end; end; -function TPdfBitmapImage.CreateMaskStream(AImage: TFPCustomImage): TPDfImage; -var - pb: PByteArray; - y: Integer; - x: Integer; -begin - result := TPdfImage.CreateStream(nil); - with result do - try - with Attributes do - begin - AddItem('Type', TPdfName.CreateName('XObject')); - AddItem('Subtype', TPdfName.CreateName('Image')); - AddItem('Width', TPdfNumber.CreateNumber(aImage.Width)); - AddItem('Height', TPdfNumber.CreateNumber(aImage.Height)); - AddItem('BitsPerComponent', TPdfNumber.CreateNumber(8)); - AddItem('ColorSpace',TPdfName.CreateName('DeviceGray')); - if USE_ZLIB then - PdfArrayByName('Filter').AddItem(TPdfName.CreateName('FlateDecode')); - - new(pb); - for y := 0 to AImage.Height - 1 do - begin - for x := 0 to AImage.Width-1 do - pb^[x] := AImage.Pixels[x,y]; - Stream.Write(pb^, AImage.Width); - end; - dispose(pb); - end; - - finally - end; -end; - function TPdfBitmapImage.CreateImage(AImage: TGraphic; ObjectMgr: TPdfObjectMgr=nil): TPdfImage; var ABitmap: TBitmap; x, y: integer; pb: PByteArray; b: Byte; -{$IFDEF LAZ_POWERPDF} - aIntfImage: TLazIntfImage; - aColor : TFPColor; - Alpha : TFPMemoryImage; - maskimage : TPDFImage; - hasAlpha : boolean; -{$endif} - {$IFDEF USE_CLX} const PIXEL_COLOR_SIZE = 4; @@ -233,10 +289,6 @@ begin try Assign(AImage); -{$IFDEF FPC} - aIntfImage := TLazIntfImage.Create(0,0); - aIntfImage.LoadFromBitmap(aBitmap.Handle, aBitmap.MaskHandle); -{$ENDIF} // if bitmap image has less then 8 bit color, set PixelFormat to 8 bit. if (PixelFormat = pf1bit) or {$IFNDEF USE_CLX} @@ -254,35 +306,15 @@ begin // translate TBitmap object to pdf image format. if PixelFormat = pf8bit then begin -{$IFNDEF FPC} for y := 0 to Height - 1 do begin pb := ScanLine[y]; Stream.Write(pb^, Width); -{$ELSE} - for y := 0 to aintfimage.Height - 1 do - begin - new(pb); - - for x := 0 to aintfimage.Width-1 do - begin - aColor := aIntfImage.Colors[x,y]; - { kleurwaarden worden als 16bits waarden opgeslagen, we kappen er - dus 8 van af. - red is willekeurig genomen - } - pb^[x] := acolor.red shr 8; end; - - Stream.Write(pb^, Width); - dispose(pb); -{$ENDIF} - end; Attributes.AddItem('ColorSpace', CreateIndexedColorArray(ABitmap)); end else begin -{$ifndef fpc} for y := 0 to Height - 1 do begin pb := ScanLine[y]; @@ -297,61 +329,12 @@ begin end; Attributes.AddItem('ColorSpace', TPdfName.CreateName('DeviceRGB')); end; -{$else} - Alpha := TFPMemoryImage.Create(AImage.Width, AImage.Height); - Alpha.UsePalette := true; - Alpha.Palette.Count := 256; - for x:=0 to $FF do - begin - aColor.Red:=x; - aColor.Red:=(aColor.Red shl 8) + aColor.Red; - aColor.Green:=aColor.Red; - aColor.Blue:=aColor.Red; - Alpha.Palette.Color[x]:=aColor; end; - HasAlpha := false; - - for y := 0 to aintfimage.Height - 1 do - begin - new(pb); - for x := 0 to aintfimage.Width-1 do - begin - aColor := aIntfImage.Colors[x,y]; - pb[ 0 ] := acolor.red shr 8; - pb[ 1 ] := acolor.green shr 8; - pb[ 2 ] := acolor.blue shr 8; - Stream.write(pb[ 0 ], 3); - - b := acolor.alpha shr 8; - Alpha.Pixels[x,y] := b; - - if acolor.Alpha<>AlphaOpaque then - HasAlpha := true; - end; - dispose(pb); - Attributes.AddItem('ColorSpace', TPdfName.CreateName('DeviceRGB')); - end; - - if HasAlpha then begin - MaskImage := CreateMaskStream(Alpha); - if ObjectMgr<>nil then - ObjectMgr.AddObject(MaskImage); - Attributes.AddItem('SMask', MaskImage); - end; - - Alpha.Free; -{$endif} - end; with Attributes do begin -{$IFDEF FPC} - AddItem('Width', TPdfNumber.CreateNumber(aintfimage.Width)); - AddItem('Height', TPdfNumber.CreateNumber(aintfimage.Height)); -{$ELSE} AddItem('Width', TPdfNumber.CreateNumber(abitmap.Width)); AddItem('Height', TPdfNumber.CreateNumber(abitmap.Height)); -{$ENDIF} AddItem('BitsPerComponent', TPdfNumber.CreateNumber(8)); if USE_ZLIB then PdfArrayByName('Filter').AddItem(TPdfName.CreateName('FlateDecode')); @@ -359,15 +342,12 @@ begin finally Free; end; - -{$IFDEF FPC} - aIntfImage.Free(); -{$ENDIF} except result.Free; raise; end; end; +{$ENDIF} initialization {$IFDEF LAZ_POWERPDF} diff --git a/components/powerpdf/PdfJpegImage.pas b/components/powerpdf/PdfJpegImage.pas index 5f1897632..7d0131f35 100644 --- a/components/powerpdf/PdfJpegImage.pas +++ b/components/powerpdf/PdfJpegImage.pas @@ -27,10 +27,12 @@ interface uses SysUtils, Classes, Graphics, PdfTypes, PdfDoc, PdfImages {$IFDEF LAZ_POWERPDF} + ,FPImage, FPReadJPEG, PdfImageLazTools {$ELSE} ,JPEG {$ENDIF} ; + type { TPdfJpegImage } TPdfJpegImage = class(TPdfImageCreator) @@ -41,7 +43,48 @@ type implementation // CreateImage + +{$IFDEF LAZ_POWERPDF} function TPdfJpegImage.CreateImage(AImage: TGraphic; ObjectMgr: TPdfObjectMgr=nil): TPdfImage; +var + fpImg: TFPCustomImage; +begin + // check whether specified graphic is valid image. + if not (AImage is TJpegImage) then + raise EPdfInvalidValue.Create('only jpeg image is allowed.'); + + result := TPdfImage.CreateStream(nil); + try + ConvertGraphicToFPImage(AImage, fpImg); + + TJpegImage(AImage).SaveToStream(result.Stream); + + with result.Attributes do + begin + AddItem('Type', TPdfName.CreateName('XObject')); + AddItem('Subtype', TPdfName.CreateName('Image')); + if TJPegImage(AImage).GrayScale then + AddItem('ColorSpace', TPdfName.CreateName('DeviceGray')) + else + AddItem('ColorSpace', TPdfName.CreateName('DeviceRGB')); + AddItem('Width', TPdfNumber.CreateNumber(fpImg.Width)); + AddItem('Height', TPdfNumber.CreateNumber(fpImg.Height)); + AddItem('BitsPerComponent', TPdfNumber.CreateNumber(8)); + PdfArrayByName('Filter').AddItem(TPdfName.CreateName('DCTDecode')); + end; + + fpImg.Free; + + except + result.Free; + raise; + end; + +end; + +{$ELSE} +function TPdfJpegImage.CreateImage(AImage: TGraphic; ObjectMgr: TPdfObjectMgr=nil): TPdfImage; + begin // check whether specified graphic is valid image. if not (AImage is TJpegImage) then @@ -70,6 +113,7 @@ begin end; end; +{$ENDIF} initialization diff --git a/components/powerpdf/PdfTypes.pas b/components/powerpdf/PdfTypes.pas index a9576b1ab..02b9bf4cc 100644 --- a/components/powerpdf/PdfTypes.pas +++ b/components/powerpdf/PdfTypes.pas @@ -283,6 +283,7 @@ type EPdfInvalidValue = class(Exception); EPdfInvalidOperation = class(Exception); + EPdfInvalidImageFormat = class(Exception); {* * utility functions. diff --git a/components/powerpdf/pack_powerpdf.lpk b/components/powerpdf/pack_powerpdf.lpk index baf18fda7..02cf5a717 100644 --- a/components/powerpdf/pack_powerpdf.lpk +++ b/components/powerpdf/pack_powerpdf.lpk @@ -1,6 +1,6 @@ - + @@ -21,8 +21,8 @@ - - + + @@ -72,7 +72,12 @@ + + + + + @@ -87,7 +92,6 @@ - diff --git a/components/powerpdf/pack_powerpdf.pas b/components/powerpdf/pack_powerpdf.pas index e43250018..f0377a7a3 100644 --- a/components/powerpdf/pack_powerpdf.pas +++ b/components/powerpdf/pack_powerpdf.pas @@ -10,7 +10,7 @@ interface uses PdfTypes, PdfDoc, PdfJpCMap, PdfJPFonts, PdfGBFonts, PdfFonts, PdfImages, PReport, PdfJpegImage, PRJpegImage, PRAnnotation, PowerPdf, - LazarusPackageIntf; + PdfImageLazTools, LazarusPackageIntf; implementation