* Fix bug #29989, add compression and JPEG image support (by Ondrej Pokorny)

git-svn-id: trunk@33484 -
This commit is contained in:
michael 2016-04-11 19:07:27 +00:00
parent b46969cfa8
commit 591f3dc879

View File

@ -28,7 +28,7 @@ uses
SysUtils, SysUtils,
StrUtils, StrUtils,
contnrs, contnrs,
fpImage, fpImage, FPReadJPEG,
zstream, zstream,
fpparsettf; fpparsettf;
@ -45,7 +45,7 @@ type
TPDFPageLayout = (lSingle, lTwo, lContinuous); TPDFPageLayout = (lSingle, lTwo, lContinuous);
TPDFUnitOfMeasure = (uomInches, uomMillimeters, uomCentimeters, uomPixels); TPDFUnitOfMeasure = (uomInches, uomMillimeters, uomCentimeters, uomPixels);
TPDFOption = (poOutLine, poCompressText, poCompressFonts); TPDFOption = (poOutLine, poCompressText, poCompressFonts, poCompressImages, poUseRawJPEG);
TPDFOptions = set of TPDFOption; TPDFOptions = set of TPDFOption;
EPDF = Class(Exception); EPDF = Class(Exception);
@ -667,12 +667,14 @@ type
Property Pages[AIndex : Integer] : TPDFPage Read GetP; Default; Property Pages[AIndex : Integer] : TPDFPage Read GetP; Default;
end; end;
TPDFImageCompression = (icNone, icDeflate, icJPEG);
TPDFImageItem = Class(TCollectionItem) TPDFImageItem = Class(TCollectionItem)
private private
FImage: TFPCustomImage; FImage: TFPCustomImage;
FOwnsImage: Boolean; FOwnsImage: Boolean;
FStreamed: TBytes; FStreamed: TBytes;
FCompression: TPDFImageCompression;
FWidth,FHeight : Integer; FWidth,FHeight : Integer;
function GetHeight: Integer; function GetHeight: Integer;
function GetStreamed: TBytes; function GetStreamed: TBytes;
@ -681,7 +683,7 @@ type
procedure SetStreamed(AValue: TBytes); procedure SetStreamed(AValue: TBytes);
Public Public
Destructor Destroy; override; Destructor Destroy; override;
Procedure CreateStreamedData; Procedure CreateStreamedData(AUseCompression: Boolean);
Function WriteImageStream(AStream: TStream): int64; virtual; Function WriteImageStream(AStream: TStream): int64; virtual;
function Equals(AImage: TFPCustomImage): boolean; reintroduce; function Equals(AImage: TFPCustomImage): boolean; reintroduce;
Property Image : TFPCustomImage Read FImage Write SetImage; Property Image : TFPCustomImage Read FImage Write SetImage;
@ -692,13 +694,23 @@ type
end; end;
TPDFImages = CLass(TCollection) { TPDFImages }
private
TPDFImages = Class(TCollection)
Private
FOwner: TPDFDocument;
function GetI(AIndex : Integer): TPDFImageItem; function GetI(AIndex : Integer): TPDFImageItem;
Protected
function GetOwner: TPersistent; override;
Public Public
Constructor Create(AOwner: TPDFDocument; AItemClass : TCollectionItemClass);
Function AddImageItem : TPDFImageItem; Function AddImageItem : TPDFImageItem;
Function AddJPEGStream(Const AStream : TStream; Width,Height : Integer): Integer;
Function AddFromStream(Const AStream : TStream; Handler : TFPCustomImageReaderClass;
KeepImage : Boolean = False): Integer;
Function AddFromFile(Const AFileName : String; KeepImage : Boolean = False): Integer; Function AddFromFile(Const AFileName : String; KeepImage : Boolean = False): Integer;
Property Images[AIndex : Integer] : TPDFImageItem Read GetI; default; Property Images[AIndex : Integer] : TPDFImageItem Read GetI; default;
Property Owner: TPDFDocument read FOwner;
end; end;
@ -734,6 +746,8 @@ type
end; end;
{ TPDFDocument }
TPDFDocument = class(TComponent) TPDFDocument = class(TComponent)
private private
FCatalogue: integer; FCatalogue: integer;
@ -763,6 +777,13 @@ type
procedure SetInfos(AValue: TPDFInfos); procedure SetInfos(AValue: TPDFInfos);
procedure SetLineStyles(AValue: TPDFLineStyleDefs); procedure SetLineStyles(AValue: TPDFLineStyleDefs);
protected protected
// Create all kinds of things, virtual so they can be overridden to create descendents instead
function CreatePDFPages: TPDFPages; virtual;
function CreateLineStyles: TPDFLineStyleDefs; virtual;
function CreateFontDefs: TPDFFontDefs; virtual;
function CreatePDFImages: TPDFImages; virtual;
function CreatePDFInfos: TPDFInfos; virtual;
function CreateSectionList: TPDFSectionList; virtual;
// Returns next prevoutline // Returns next prevoutline
function CreateSectionOutLine(Const SectionIndex,OutLineRoot,ParentOutLine,NextSect,PrevSect : Integer): Integer; virtual; function CreateSectionOutLine(Const SectionIndex,OutLineRoot,ParentOutLine,NextSect,PrevSect : Integer): Integer; virtual;
Function CreateSectionsOutLine : Integer; virtual; Function CreateSectionsOutLine : Integer; virtual;
@ -1954,7 +1975,12 @@ end;
function TPDFImageItem.GetStreamed: TBytes; function TPDFImageItem.GetStreamed: TBytes;
begin begin
if Length(FStreamed)=0 then if Length(FStreamed)=0 then
CreateStreamedData; begin
if Collection.Owner is TPDFDocument then
CreateStreamedData(poCompressImages in TPDFDocument(Collection.Owner).Options)
else
CreateStreamedData(True);
end;
Result:=FStreamed; Result:=FStreamed;
end; end;
@ -1988,24 +2014,52 @@ begin
inherited Destroy; inherited Destroy;
end; end;
procedure TPDFImageItem.CreateStreamedData; procedure TPDFImageItem.CreateStreamedData(AUseCompression: Boolean);
Var Var
I,X,Y : Integer; X,Y : Integer;
C : TFPColor; C : TFPColor;
MS : TMemoryStream;
Str : TStream;
CWhite : TFPColor; // white color
begin begin
FillChar(CWhite, SizeOf(CWhite), $FF);
FWidth:=Image.Width; FWidth:=Image.Width;
FHeight:=Image.Height; FHeight:=Image.Height;
SetLength(FStreamed,FWidth*FHeight*3); Str := nil;
I:=0; MS := TMemoryStream.Create;
try
if AUseCompression then
begin
FCompression := icDeflate;
Str := Tcompressionstream.create(cldefault, MS)
end
else
begin
FCompression := icNone;
Str := MS;
end;
for Y:=0 to FHeight-1 do for Y:=0 to FHeight-1 do
for X:=0 to FWidth-1 do for X:=0 to FWidth-1 do
begin begin
C:=Image.Colors[x,y]; C:=Image.Colors[x,y];
FStreamed[I]:=C.Red shr 8; if C.alpha < $FFFF then // remove alpha channel - assume white background
FStreamed[I+1]:=C.Green shr 8; C := AlphaBlend(CWhite, C);
FStreamed[I+2]:=C.blue shr 8;
Inc(I,3); Str.WriteByte(C.Red shr 8);
Str.WriteByte(C.Green shr 8);
Str.WriteByte(C.blue shr 8);
end;
if Str<>MS then
Str.Free;
Str := nil;
SetLength(FStreamed, MS.Size);
MS.Position := 0;
if MS.Size>0 then
MS.ReadBuffer(FStreamed[0], MS.Size);
finally
Str.Free;
MS.Free;
end; end;
end; end;
@ -2046,28 +2100,138 @@ begin
Result:=Items[AIndex] as TPDFImageItem; Result:=Items[AIndex] as TPDFImageItem;
end; end;
function TPDFImages.GetOwner: TPersistent;
begin
Result := FOwner;
end;
function TPDFImages.AddImageItem: TPDFImageItem; function TPDFImages.AddImageItem: TPDFImageItem;
begin begin
Result:=Add as TPDFImageItem; Result:=Add as TPDFImageItem;
end; end;
function TPDFImages.AddJPEGStream(const AStream: TStream; Width, Height: Integer
): Integer;
Var
IP : TPDFImageItem;
begin
IP:=AddImageItem;
IP.FWidth := Width;
IP.FHeight := Height;
IP.FCompression := icJPEG;
SetLength(IP.FStreamed, AStream.Size-AStream.Position);
if Length(IP.FStreamed)>0 then
AStream.ReadBuffer(IP.FStreamed[0], Length(IP.FStreamed));
Result:=Count-1;
end;
constructor TPDFImages.Create(AOwner: TPDFDocument;
AItemClass: TCollectionItemClass);
begin
inherited Create(AItemClass);
FOwner := AOwner;
end;
function TPDFImages.AddFromFile(const AFileName: String; KeepImage: Boolean): Integer; function TPDFImages.AddFromFile(const AFileName: String; KeepImage: Boolean): Integer;
{$IF NOT (FPC_FULLVERSION >= 30101)}
function FindReaderFromExtension(extension: String): TFPCustomImageReaderClass;
var s : string;
r : integer;
begin
extension := lowercase (extension);
if (extension <> '') and (extension[1] = '.') then
system.delete (extension,1,1);
with ImageHandlers do
begin
r := count-1;
s := extension + ';';
while (r >= 0) do
begin
Result := ImageReader[TypeNames[r]];
if (pos(s,Extensions[TypeNames[r]]+';') <> 0) then
Exit;
dec (r);
end;
end;
Result := nil;
end;
function FindReaderFromFileName(const filename: String
): TFPCustomImageReaderClass;
begin
Result := FindReaderFromExtension(ExtractFileExt(filename));
end;
{$ENDIF}
var
FS: TFileStream;
begin
FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
try
Result := AddFromStream(FS,
{$IF (FPC_FULLVERSION >= 30101)}TFPCustomImage.{$ENDIF}FindReaderFromFileName(AFileName), KeepImage);
finally
FS.Free;
end;
end;
function TPDFImages.AddFromStream(const AStream: TStream;
Handler: TFPCustomImageReaderClass; KeepImage: Boolean): Integer;
Var Var
I : TFPMemoryImage; I : TFPMemoryImage;
IP : TPDFImageItem; IP : TPDFImageItem;
JPEG : TFPReaderJPEG;
Reader: TFPCustomImageReader;
{$IF (FPC_FULLVERSION >= 30101)}
Size : TPoint;
{$ELSE}
startPos: Int64;
{$ENDIF}
begin begin
if (poUseRawJPEG in Owner.Options) and Handler.InheritsFrom(TFPReaderJPEG) then
begin
JPEG := TFPReaderJPEG.Create;
try
{$IF (FPC_FULLVERSION >= 30101)}
Size := JPEG.ImageSize(AStream);
Result := AddJPEGStream(AStream, Size.X, Size.Y);
{$ELSE}
I:=TFPMemoryImage.Create(0,0); I:=TFPMemoryImage.Create(0,0);
I.LoadFromFile(AFileName); try
startPos := AStream.Position;
I.LoadFromStream(AStream, JPEG);
AStream.Position := startPos;
Result := AddJPEGStream(AStream, I.Width, I.Height);
finally
I.Free;
end;
{$ENDIF}
finally
JPEG.Free;
end;
end else
begin
IP:=AddImageItem; IP:=AddImageItem;
I:=TFPMemoryImage.Create(0,0);
Reader := Handler.Create;
try
I.LoadFromStream(AStream, Reader);
finally
Reader.Free;
end;
IP.Image:=I; IP.Image:=I;
if Not KeepImage then if Not KeepImage then
begin begin
IP.CreateStreamedData; Writeln('Compressing : ',poCompressImages in Owner.Options);
IP.CreateStreamedData(poCompressImages in Owner.Options);
IP.FImage:=Nil; // not through property, that would clear the image IP.FImage:=Nil; // not through property, that would clear the image
i.Free; i.Free;
end; end;
end;
Result:=Count-1; Result:=Count-1;
end; end;
@ -2689,6 +2853,10 @@ begin
D:=Document.GlobalXRefs[AObject].Dict; D:=Document.GlobalXRefs[AObject].Dict;
D.AddInteger('Length',ISize); D.AddInteger('Length',ISize);
LastElement.Write(AStream); LastElement.Write(AStream);
case Document.Images[NumImg].FCompression of
icJPEG: WriteString('/Filter /DCTDecode'+CRLF, AStream);
icDeflate: WriteString('/Filter /FlateDecode'+CRLF, AStream);
end;
WriteString('>>', AStream); WriteString('>>', AStream);
// write image stream in xobject dictionary // write image stream in xobject dictionary
Document.Images[NumImg].WriteImageStream(AStream); Document.Images[NumImg].WriteImageStream(AStream);
@ -3351,22 +3519,58 @@ begin
Raise EPDF.CreateFmt(SErrNoGlobalDict,[AName]); Raise EPDF.CreateFmt(SErrNoGlobalDict,[AName]);
end; end;
Function TPDFDocument.CreateLineStyles : TPDFLineStyleDefs;
begin
Result:=TPDFLineStyleDefs.Create(TPDFLineStyleDef);
end;
Function TPDFDocument.CreateSectionList : TPDFSectionList;
begin
Result:=TPDFSectionList.Create(TPDFSection)
end;
Function TPDFDocument.CreateFontDefs : TPDFFontDefs;
begin
TPDFFontDefs.Create(TPDFFont);
end;
Function TPDFDocument.CreatePDFInfos : TPDFInfos;
begin
Result:=TPDFInfos.Create;
end;
Function TPDFDocument.CreatePDFImages : TPDFImages;
begin
Result:=TPDFImages.Create(Self,TPDFImageItem);
end;
Function TPDFDocument.CreatePDFPages : TPDFPages;
begin
Result:=TPDFPages.Create(Self);
end;
constructor TPDFDocument.Create(AOwner : TComponent); constructor TPDFDocument.Create(AOwner : TComponent);
begin begin
inherited Create(AOwner); inherited Create(AOwner);
FLineStyleDefs:=TPDFLineStyleDefs.Create(TPDFLineStyleDef);
FSections:=TPDFSectionList.Create(TPDFSection);
FFontFiles:=TStringList.Create; FFontFiles:=TStringList.Create;
FFonts:=TPDFFontDefs.Create(TPDFFont); FLineStyleDefs:=CreateLineStyles;
FInfos:=TPDFInfos.Create; FSections:=CreateSectionList;
FImages:=TPDFImages.Create(TPDFImageItem); FFonts:=CreateFontDefs;
FPages:=TPDFPages.Create(Self); FInfos:=CreatePDFInfos;
FImages:=CreatePDFImages;
FPages:=CreatePDFPages;
FPreferences:=True; FPreferences:=True;
FPageLayout:=lSingle; FPageLayout:=lSingle;
FDefaultPaperType:=ptA4; FDefaultPaperType:=ptA4;
FDefaultOrientation:=ppoPortrait; FDefaultOrientation:=ppoPortrait;
FZoomValue:='100'; FZoomValue:='100';
FOptions := [poCompressFonts]; FOptions := [poCompressFonts, poCompressImages];
end; end;
procedure TPDFDocument.StartDocument; procedure TPDFDocument.StartDocument;