El 05/02/2013 23:49, silvioprog escribió:

You have puted a PNG image into a PDF? That's what I'm trying to do, and
it needs of the uncompressed data too (or compressed as gzip).

Hello,

The attached code do more or less the same as the fpdf.php one. It can be written in a very different way but I tried to be more modular to acomodate in a future the indexed palettes and transparencies over palettes, and also allow an easy data to real RGB (after filtering) conversion.


--

unit upng4pdftest;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, zstream;

type

  RPNGChunkInfo=packed record
    ChunkSize: DWORD;
    ChunkID: array [0..3] of char;
    ChunkData: PBYTE;
    ChunkCRC: DWORD;
  end;

  RPNGIHDR=packed record
    Width: DWORD;
    Height: DWORD;
    BitDepth: BYTE;
    ColorType: BYTE;
    CompressionMethod: BYTE;
    FilterMethod: BYTE;
    InterlaceMode: BYTE;
  end;
  PPMGIHDR=^RPNGIHDR;

  RPNGPLTE=record
    PaletteEntries: BYTE;
    PaletteRGB: array [0..((256*3)-1)] of BYTE;
  end;

  RPNGtoPDFData=record
    IHDR: RPNGIHDR;
    PLTE: RPNGPLTE;
    IDATCompressed: PBYTE;
    IDATCompressedSize: DWORD;
    IDATUnCompressed: PBYTE;
    IDATUncompressedSize: DWORD;
    AlphaRGBColor: DWORD;
    AlphaPalette: array [0..255] of BYTE;
    IDATRGB: PBYTE;
    IDATRGBSize: DWORD;
    IDATHasTransparency: Boolean;
    IDATTransparency: PBYTE;
    IDATTransparencySize: DWORD;
    PDFColorSpace: string;
    ErrorString: string;
  end;

function GetPNGInformation(const aFileName: string): RPNGtoPDFData;
function GetPNGInformation(const aStream: TStream): RPNGtoPDFData;
procedure PNGTest();

implementation

function ReadChunk(const aStream: TStream; out aChunk: RPNGChunkInfo): integer;
begin
  Result:=aStream.Read(aChunk.ChunkSize,4);
  if Result<>4 then exit(0);
  aChunk.ChunkSize:=BEtoN(aChunk.ChunkSize);
  Result:=Result+aStream.Read(aChunk.ChunkID,4);
  if Result<>8 then exit(0);
  GetMem(aChunk.ChunkData,aChunk.ChunkSize);
  Result:=Result+aStream.Read(aChunk.ChunkData^,aChunk.ChunkSize);
  if DWORD(Result)<>8+aChunk.ChunkSize then exit(0);
  Result:=Result+aStream.Read(aChunk.ChunkCRC,4);
  if DWORD(Result)<>8+4+aChunk.ChunkSize then exit(0);
end;

function ExpandIDAT(const aIDAT: PBYTE; const aSize: DWORD; out aIDATExpanded: 
PBYTE; out aExpandedSize: DWORD): Boolean;
var
  ExpandStream: Tdecompressionstream;
  InputStream: TMemoryStream;
  OutputStream: TMemoryStream;
begin
  aIDATExpanded:=nil;
  aExpandedSize:=0;
  InputStream:=TMemoryStream.Create;
  InputStream.Write(aIDAT^,aSize);
  InputStream.Position:=0;

  OutputStream:=TMemoryStream.Create;
  try
    ExpandStream:=nil;
    ExpandStream:=Tdecompressionstream.create(InputStream);
    try
      OutputStream.CopyFrom(ExpandStream,0);
      GetMem(aIDATExpanded,OutputStream.Size);
      OutputStream.Position:=0;
      OutputStream.Read(aIDATExpanded^,OutputStream.Size);
      aExpandedSize:=OutputStream.Size;
      result:=true;
    except
      Result:=false;
      if Assigned(aIDATExpanded) then begin
        FreeMem(aIDATExpanded);
        aIDATExpanded:=nil;
      end;
      aExpandedSize:=0;
    end;
  finally
    InputStream.Free;
    OutputStream.Free;
    ExpandStream.Free;
  end;
end;

function GetPNGInformation(const aFileName: string): RPNGtoPDFData;
var
  FileStream: TFileStream;
begin
  Result.ErrorString:=''; //No initialization warning
  FillByte(Result,sizeof(Result),0);
  FileStream:=TFileStream.Create(aFileName,fmOpenRead or fmShareDenyWrite);
  try
    Result:=GetPNGInformation(FileStream);
  finally
    FileStream.Free;
  end;
end;

function GetPNGInformation(const aStream: TStream): RPNGtoPDFData;
const
  MatchSignature: array [0..7] of 
BYTE=(137,BYTE('P'),BYTE('N'),BYTE('G'),13,10,26,20);
var
  Signature: array [0..7] of Byte;
  Chunk: RPNGChunkInfo;
  x,y: DWORD;
  FilterMode: BYTE;
  TargetRGB: DWORD;
  TargetAlpha: DWORD;
  Source: DWORD;
begin
  FillByte(Result,sizeof(Result),0);
  Signature[0]:=0; //Avoid initialization warning
  FillByte(Signature,sizeof(Signature),0);
  aStream.Read(Signature,sizeof(Signature));
  if CompareMem(@Signature[0],@MatchSignature[0],sizeof(Signature)) then begin
    Result.ErrorString:='Not PNG signature found.';
    exit;
  end;

  if ReadChunk(aStream,Chunk)=0 then begin
    Result.ErrorString:='Error reading PNG.';
    exit;
  end;
  if Chunk.ChunkID<>'IHDR' then begin
    Result.ErrorString:='IHDR not found, incorrect PNG file.';
    exit;
  end;
  Result.IHDR:=PPMGIHDR(Chunk.ChunkData)^;
  FreeMem(Chunk.ChunkData);
  with Result do begin
    IHDR.Width:=BEtoN(IHDR.Width);
    IHDR.Height:=BEtoN(IHDR.Height);
    if IHDR.BitDepth>8 then begin
      ErrorString:='Bit depth > 8 not supported.';
      exit;
    end;
    Case IHDR.ColorType of
      0,4: PDFColorSpace:='DeviceGray';
      2,6: PDFColorSpace:='DeviceRGB';
      3:   PDFColorSpace:='Indexed';
      else begin
        ErrorString:='Color format not supported.';
        exit;
      end;
    end;
    if IHDR.CompressionMethod<>0 then begin
      ErrorString:='Compression method not supported.';
      exit;
    end;
    if IHDR.FilterMethod<>0 then begin
      ErrorString:='Filter method not supported.';
      exit;
    end;
    if IHDR.InterlaceMode<>0 then begin
      ErrorString:='Interlacing not supported.';
      exit;
    end;
  end;
  //Scan next chunks
  repeat
    if ReadChunk(aStream,Chunk)=0 then begin
      Result.ErrorString:='Error reading PNG.';
      exit;
    end;
    if Chunk.ChunkID='PLTE' then begin
      //Palette
      Result.PLTE.PaletteEntries:=Chunk.ChunkSize div 3;
      move(Chunk.ChunkData^,Result.PLTE.PaletteRGB,Chunk.ChunkSize);

    end else if Chunk.ChunkID='tRNS' then begin
      Result.IDATHasTransparency:=true;
      case Result.IHDR.ColorType of
        0:
          begin
            //Only one gray value
            Result.AlphaRGBColor:=Chunk.ChunkData^;
          end;
        2:
          begin
            //Only one color RBG value as transparent
            Result.AlphaRGBColor:=Chunk.ChunkData^ shl 16 or 
(Chunk.ChunkData+1)^ shl 8 or (Chunk.ChunkData+2)^;
          end;
        3:
          begin
            move(Chunk.ChunkData^,Result.AlphaPalette,Chunk.ChunkSize);
          end;
        else
          begin
            Result.ErrorString:='Unknown color type and transparency 
combination.';
            exit;
          end;
      end;

    end else if Chunk.ChunkID='IDAT' then begin
      Result.IDATCompressed:=Chunk.ChunkData;
      Chunk.ChunkData:=nil; // Memory block moved to Result.IDATCompressed and 
niled to avoid freemem.
      Result.IDATCompressedSize:=Chunk.ChunkSize;
      if not 
ExpandIDAT(Result.IDATCompressed,Result.IDATCompressedSize,Result.IDATUnCompressed,Result.IDATUncompressedSize)
 then begin
        Result.ErrorString:='Something went wrong expanding IDAT.';
        exit;
      end;
    end else begin
      if BYTE(Chunk.ChunkID[0])>128 then begin
        Result.ErrorString:='Critical PNG packet found, but I do not know how 
to handle "'+Chunk.ChunkID+'"';
        exit;
      end;
    end;
    if Chunk.ChunkData<>nil then begin
      FreeMem(Chunk.ChunkData);
    end;
  until Chunk.ChunkID='IEND';

  //Process uncompressed data to extract the alpha and RGB24 channels in 
different layers.
  if Result.IDATHasTransparency then begin
    //TODO:
    //Resolve paletted and other transparencies.
  end else begin
    case Result.IHDR.ColorType of
      4: //Gray scale with alpha (not tested)
        begin
          {$DEFINE INCLUDE_FILTERMODE=1}
          //Each line is: FilterMode(1),Gray(1),Alpha(1),...,Gray(1),Alpha(1)
          Result.IDATRGBSize:=Result.IHDR.Width*Result.IHDR.Height;
          Result.IDATTransparencySize:=Result.IHDR.Width*Result.IHDR.Height;
          {$IFDEF INCLUDE_FILTERMODE}
            inc(Result.IDATRGBSize,Result.IHDR.Height*1);
            inc(Result.IDATTransparencySize,Result.IHDR.Height*1);
          {$ENDIF}
          Getmem(Result.IDATTransparency,Result.IDATTransparencySize);
          Getmem(Result.IDATRGB,Result.IDATRGBSize);
          with Result do begin
            TargetAlpha:=0;
            TargetRGB:=0;
            Source:=0;
            y:=0;
            while y<IHDR.Height do begin
              FilterMode:=(IDATUnCompressed+Source)^;
              {$IFDEF INCLUDE_FILTERMODE}
                (IDATRGB+TargetRGB)^:=(IDATUnCompressed+Source)^;
                inc(TargetRGB);
                (IDATTransparency+TargetAlpha)^:=(IDATUnCompressed+Source)^;
                inc(TargetAlpha);
              {$ENDIF}
              inc(Source);
              x:=0;
              while X<IHDR.Width do begin
                (IDATRGB+TargetRGB)^:=(IDATUnCompressed+Source)^;
                inc(TargetRGB);
                inc(Source);
                (IDATTransparency+TargetAlpha)^:=(IDATUnCompressed+Source)^;
                inc(TargetAlpha);
                inc(Source);
                inc(x);
              end;
              inc(y);
            end;
          end;
        end;
      6: //Truecolor with alpha
        begin
          {$DEFINE INCLUDE_FILTERMODE=1}
          //Each line is: FilterMode(1),RGB(3),Alpha(1),...,RGB(3),Alpha(1)
          Result.IDATRGBSize:=Result.IHDR.Width*Result.IHDR.Height*3;
          Result.IDATTransparencySize:=Result.IHDR.Width*Result.IHDR.Height;
          {$IFDEF INCLUDE_FILTERMODE}
            inc(Result.IDATRGBSize,Result.IHDR.Height*1);
            inc(Result.IDATTransparencySize,Result.IHDR.Height*1);
          {$ENDIF}
          Getmem(Result.IDATTransparency,Result.IDATTransparencySize);
          Getmem(Result.IDATRGB,Result.IDATRGBSize);
          with Result do begin
            TargetAlpha:=0;
            TargetRGB:=0;
            Source:=0;
            y:=0;
            while y<IHDR.Height do begin
              FilterMode:=(IDATUnCompressed+Source)^;
              {$IFDEF INCLUDE_FILTERMODE}
                (IDATRGB+TargetRGB)^:=(IDATUnCompressed+Source)^;
                inc(TargetRGB);
                (IDATTransparency+TargetAlpha)^:=(IDATUnCompressed+Source)^;
                inc(TargetAlpha);
              {$ENDIF}
              inc(Source);
              x:=0;
              while X<IHDR.Width do begin
                (IDATRGB+TargetRGB)^:=(IDATUnCompressed+Source)^;
                inc(TargetRGB);
                inc(Source);
                (IDATRGB+TargetRGB)^:=(IDATUnCompressed+Source)^;
                inc(TargetRGB);
                inc(Source);
                (IDATRGB+TargetRGB)^:=(IDATUnCompressed+Source)^;
                inc(TargetRGB);
                inc(Source);
                (IDATTransparency+TargetAlpha)^:=(IDATUnCompressed+Source)^;
                inc(TargetAlpha);
                inc(Source);
                inc(x);
              end;
              inc(y);
            end;
          end;
        end;
    end;
  end;
end;

procedure PNGTest();
var
  Info: RPNGtoPDFData;
  F: TFileStream;
begin
  Info:=GetPNGInformation('image.png');
  if Info.ErrorString='' then begin
    if Info.IDATRGBSize>0 then begin
      F:=TFileStream.Create('COLOR.DATA',fmCreate);
      F.Write(Info.IDATRGB^,info.IDATRGBSize);
      F.Free;
    end;
    if Info.IDATTransparencySize>0 then begin
      F:=TFileStream.Create('ALPHA.DATA',fmCreate);
      F.Write(Info.IDATTransparency^,info.IDATTransparencySize);
      F.Free;
    end;
  end;
  FreeMem(info.IDATRGB);
  FreeMem(info.IDATTransparency);
  FreeMem(info.IDATUnCompressed);
  FreeMem(info.IDATCompressed);
end;

end.

--
_______________________________________________
Lazarus mailing list
[email protected]
http://lists.lazarus.freepascal.org/mailman/listinfo/lazarus

Reply via email to