Ignore:
Timestamp:
Apr 17, 2019, 12:58:41 AM (5 years ago)
Author:
chronos
Message:
  • Modified: Propagate project build mode options to used packages.
  • Added: Check memory leaks using heaptrc.
  • Modified: Update BGRABitmap package.
Location:
GraphicTest
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • GraphicTest

    • Property svn:ignore
      •  

        old new  
        88GraphicTest.lps
        99GraphicTest.dbg
         10heaptrclog.trc
  • GraphicTest/Packages/bgrabitmap/bgragifformat.pas

    r494 r521  
    7272    AspectRatio: single;
    7373    BackgroundColor: TColor;
     74    LoopCount: Word;
    7475    Images: array of TGifSubImage;
    7576  end;
     
    8990  GIFExtensionIntroducer = $21;
    9091  GIFBlockTerminator     = $00;
     92  GIFFileTerminator      = $3B;
    9193
    9294  GIFGraphicControlExtension_TransparentFlag = $01;  //transparent color index is provided
     
    104106  GIFCodeTableSize = 4096;
    105107
     108  NetscapeApplicationIdentifier = 'NETSCAPE2.0';
     109  NetscapeSubBlockIdLoopCount = 1;
     110  NetscapeSubBlockIdBuffering = 2;
     111
    106112function CeilLn2(AValue: Integer): integer;
    107113function BGRAToPackedRgbTriple(color: TBGRAPixel): TPackedRGBTriple;
    108114function PackedRgbTribleToBGRA(rgb: TPackedRGBTriple): TBGRAPixel;
    109 function GIFLoadFromStream(stream: TStream): TGIFData;
     115function GIFLoadFromStream(stream: TStream; MaxImageCount: integer = maxLongint): TGIFData;
    110116procedure GIFSaveToStream(AData: TGifData; Stream: TStream; AQuantizerFactory: TBGRAColorQuantizerAny;
    111117          ADitheringAlgorithm: TDitheringAlgorithm);
     
    117123//Adapted from the work of Udo Schmal, http://www.gocher.me/FPWriteGIF
    118124procedure GIFEncodeLZW(AStream: TStream; AImageData: PByte;
    119           AImageWidth, AImageHeight: integer; ABitDepth: integer);
     125          AImageWidth, AImageHeight: integer; ABitDepth: byte);
    120126
    121127implementation
     
    224230      if (bytinbuf = 0) then
    225231      begin
    226         AStream.Read(bytinbuf, 1);
     232        if AStream.Read(bytinbuf, 1) <> 1 then
     233          raise exception.Create('Unexpected end of stream');
     234
    227235        if (bytinbuf = 0) then
     236        begin
    228237          endofsrc := True;
     238          result := endcode;
     239          exit;
     240        end;
    229241        AStream.Read(bytbuf, bytinbuf);
    230242        bytbufidx := 0;
     
    238250    bitbuf := bitbuf shr codelen;
    239251    Dec(bitsinbuf, codelen);
     252    //write(inttostr(result)+'@'+inttostr(codelen)+' ');
    240253  end;
    241254
     
    278291      if interlaced then
    279292      begin
    280         while (ycnt >= yd) and (pass < 5) do
    281         begin
     293        while ycnt >= yd do
     294        begin
     295          if pass >= 5 then exit;
     296
    282297          Inc(pass);
    283298          ycnt  := GIFInterlacedStart[pass];
    284299          ystep := GIFInterlacedStep[pass];
    285300        end;
    286       end;
     301      end else exit;
    287302    end;
    288303
     
    346361  InitStringTable;
    347362  curcode := getnextcode;
     363  //Write('Reading ');
    348364  while (curcode <> endcode) and (pass < 5) and not endofsrc do
    349365  begin
     
    370386      begin
    371387        if (curcode > stridx) then
     388        begin
     389          //write('!Invalid! ');
    372390          break;
     391        end;
    373392        AddStr2Tab(Code2Str(oldcode), firstchar(Code2Str(oldcode)));
    374393        WriteStr(Code2Str(stridx - 1));
     
    379398  end;
    380399  DoneStringTable;
     400  //Writeln;
    381401  if not endofsrc then
    382402  begin
    383403    bytinbuf:= 0;
    384     AStream.Read(bytinbuf, 1);
     404    AStream.ReadBuffer(bytinbuf, 1);
    385405    if bytinbuf <> 0 then
    386406      raise exception.Create('Invalid GIF format: expecting block terminator');
     
    391411//Adapted from the work of Udo Schmal, http://www.gocher.me/FPWriteGIF
    392412procedure GIFEncodeLZW(AStream: TStream; AImageData: PByte;
    393           AImageWidth, AImageHeight: integer; ABitDepth: integer);
     413          AImageWidth, AImageHeight: integer; ABitDepth: byte);
     414
     415var  //input position
     416  PInput, PInputEnd: PByte;
     417
     418  // get the next pixel from the bitmap
     419  function ReadValue: byte;
     420  begin
     421    result := PInput^;
     422    Inc(PInput);
     423  end;
     424
     425var // GIF buffer can be up to 255 bytes long
     426  OutputBufferSize: NativeInt;
     427  OutputBuffer: packed array[0..255] of byte;
     428
     429  procedure FlushByteOutput;
     430  begin
     431    if OutputBufferSize > 0 then
     432    begin
     433      OutputBuffer[0] := OutputBufferSize;
     434      AStream.WriteBuffer(OutputBuffer, OutputBufferSize+1);
     435      OutputBufferSize := 0;
     436    end;
     437  end;
     438
     439  procedure OutputByte(AValue: byte);
     440  begin
     441    if OutputBufferSize = 255 then FlushByteOutput;
     442    inc(OutputBufferSize);
     443    OutputBuffer[OutputBufferSize] := AValue;
     444  end;
     445
     446type TCode = Word;
     447
    394448var
    395    LZWSize: byte;
    396    OutputBufferSize: NativeInt;
    397    OutputBuffer: packed array[0..255] of byte;
    398 
    399    rPrefix: array[0..GIFCodeTableSize-1] of integer; // string prefixes
    400    rSuffix: array[0..GIFCodeTableSize-1] of integer; // string suffixes
    401    rCodeStack: array[0..GIFCodeTableSize-1] of byte; // encoded pixels
    402    rSP: integer; // pointer into CodeStack
    403    rClearCode: integer; // reset decode params
    404    rEndCode: integer; // last code in input stream
    405    rCurSize: integer; // current code size
    406    rBitString: integer; // steady stream of bits to be decoded
    407    rBits: integer; // number of valid bits in BitString
    408    rMaxVal: boolean; // max code value found?
    409    rCurX: integer; // position of next pixel
    410    rCurY: integer; // position of next pixel
    411    rCurScan: PByte;
    412    rFirstSlot: integer; // for encoding an image
    413    rNextSlot: integer; // for encoding
    414    rRowsLeft: integer; // rows left to do
    415    rLast: integer; // last byte read in
    416    rUnget: boolean; // read a new byte, or use zLast?
    417 
    418    procedure FlushOutput;
     449  BitBuffer       : DWord; // steady stream of bit output
     450  BitBufferLen    : Byte;  // number of bits in buffer
     451  CurCodeSize     : byte;  // current code size
     452
     453  // save the code in the output data stream
     454  procedure WriteCode(Code: TCode);
     455  begin
     456    //Write(IntToStr(Code)+'@'+IntToStr(CurCodeSize)+' ');
     457
     458    // append code to bit buffer
     459    BitBuffer := BitBuffer or (Code shl BitBufferLen);
     460    BitBufferLen := BitBufferLen + CurCodeSize;
     461    // output whole bytes
     462    while BitBufferLen >= 8 do
     463    begin
     464      OutputByte(BitBuffer and $ff);
     465      BitBuffer := BitBuffer shr 8;
     466      BitBufferLen -= 8;
     467    end;
     468  end;
     469
     470  procedure CloseBitOutput;
     471  begin
     472    // write out the rest of the bit string
     473    // and add padding bits if necessary
     474    while BitBufferLen > 0 do
     475    begin
     476      OutputByte(BitBuffer and $ff);
     477      BitBuffer := BitBuffer shr 8;
     478      if BitBufferLen >= 8 then
     479        BitBufferLen -= 8
     480      else
     481        BitBufferLen := 0;
     482    end;
     483  end;
     484
     485type
     486  PCodeTableEntry = ^TCodeTableEntry;
     487  TCodeTableEntry = packed record
     488               Prefix: TCode;
     489               LongerFirst, LongerLast: TCode;
     490               Suffix, Padding: Byte;
     491               NextWithPrefix: TCode;
     492             end;
     493
     494var
     495  ClearCode     : TCode;   // reset decode params
     496  EndStreamCode : TCode;   // last code in input stream
     497  FirstCodeSlot : TCode;   // first slot when table is empty
     498  NextCodeSlot  : TCode;   // next slot to be used
     499
     500  PEntry: PCodeTableEntry;
     501  CodeTable: array of TCodeTableEntry;
     502  CurrentCode   : TCode; // code representing current string
     503
     504  procedure DoClearCode;
     505  var
     506    i: Word;
     507  begin
     508    for i := 0 to (1 shl ABitDepth)-1 do
     509    with CodeTable[i] do
     510    begin
     511      LongerFirst:= 0;
     512      LongerLast:= 0;
     513    end;
     514
     515    WriteCode(ClearCode);
     516    CurCodeSize := ABitDepth + 1;
     517    NextCodeSlot := FirstCodeSlot;
     518  end;
     519
     520var
     521  CurValue: Byte;
     522  i: TCode;
     523  found: boolean; // decoded string in prefix table?
     524begin
     525   if ABitDepth > 8 then
     526     raise exception.Create('Maximum bit depth is 8');
     527
     528   //output
     529   AStream.WriteByte(ABitDepth);
     530   ClearCode := 1 shl ABitDepth;
     531   EndStreamCode := ClearCode + 1;
     532   FirstCodeSlot := ClearCode + 2;
     533   CurCodeSize := ABitDepth + 1;
     534
     535   OutputBufferSize := 0;
     536   BitBuffer := 0;
     537   BitBufferLen := 0;
     538
     539   //input
     540   PInput := AImageData;
     541   PInputEnd := AImageData + PtrInt(AImageWidth)*AImageHeight;
     542
     543   setlength(CodeTable, GIFCodeTableSize);
     544   DoClearCode;
     545   //write('Writing ');
     546
     547   while PInput < PInputEnd do
    419548   begin
    420      if OutputBufferSize > 0 then
     549     CurrentCode := ReadValue;
     550     if CurrentCode >= ClearCode then
     551       raise exception.Create('Internal error');
     552
     553     //try to match the longest string
     554     while PInput < PInputEnd do
    421555     begin
    422        OutputBuffer[0] := OutputBufferSize;
    423        AStream.WriteBuffer(OutputBuffer, OutputBufferSize+1);
    424        OutputBufferSize := 0;
    425      end;
    426    end;
    427 
    428    procedure OutputByte(AValue: byte);
    429    begin
    430      if OutputBufferSize = 255 then FlushOutput;
    431      inc(OutputBufferSize);
    432      OutputBuffer[OutputBufferSize] := AValue;
    433    end;
    434 
    435    procedure LZWReset;
    436    var i: integer;
    437    begin
    438      for i := 0 to (GIFCodeTableSize - 1) do
    439      begin
    440        rPrefix[i] := 0;
    441        rSuffix[i] := 0;
    442      end;
    443      rCurSize := LZWSize + 1;
    444      rClearCode := (1 shl LZWSize);
    445      rEndCode := rClearCode + 1;
    446      rFirstSlot := (1 shl (rCurSize - 1)) + 2;
    447      rNextSlot := rFirstSlot;
    448      rMaxVal := false;
    449    end;
    450 
    451    // save a code value on the code stack
    452    procedure LZWSaveCode(Code: integer);
    453    begin
    454      rCodeStack[rSP] := Code;
    455      inc(rSP);
    456    end;
    457 
    458    // save the code in the output data stream
    459    procedure LZWPutCode(code: integer);
    460    var
    461      n: integer;
    462      b: byte;
    463    begin
    464      // write out finished bytes
    465      // a literal "8" for 8 bits per byte
    466      while (rBits >= 8) do
    467      begin
    468        b := (rBitString and $ff);
    469        rBitString := (rBitString shr 8);
    470        rBits := rBits - 8;
    471        OutputByte(b);
    472      end;
    473      // make sure no junk bits left above the first byte
    474      rBitString := (rBitString and $ff);
    475      // and save out-going code
    476      n := (code shl rBits);
    477      rBitString := (rBitString or n);
    478      rBits := rBits + rCurSize;
    479    end;
    480 
    481    // get the next pixel from the bitmap, and return it as an index into the colormap
    482    function LZWReadBitmap: integer;
    483    begin
    484      if rUnget then
    485      begin
    486        result := rLast;
    487        rUnget := false;
    488      end
    489      else
    490      begin
    491        if rCurScan = nil then
    492          rCurScan := AImageData + rCurY*AImageWidth;
    493        result := (rCurScan+rCurX)^;
    494        inc(rCurX); // inc X position
    495        if (rCurX >= AImageWidth) then // bumping Y ?
     556       CurValue := ReadValue;
     557
     558       found := false;
     559
     560       i := CodeTable[CurrentCode].LongerFirst;
     561       while i <> 0 do
    496562       begin
    497          rCurX := 0;
    498          inc(rCurY);
    499          rCurScan := nil;
    500          dec(rRowsLeft);
     563         PEntry := @CodeTable[i];
     564         if PEntry^.Suffix = CurValue then
     565         begin
     566           found := true;
     567           CurrentCode := i;
     568           break;
     569         end;
     570         i := PEntry^.NextWithPrefix;
     571       end;
     572
     573       if not found then
     574       begin
     575         PEntry := @CodeTable[CurrentCode];
     576         if PEntry^.LongerFirst = 0 then
     577         begin
     578           //store the first and last code being longer
     579           PEntry^.LongerFirst := NextCodeSlot;
     580           PEntry^.LongerLast := NextCodeSlot;
     581         end else
     582         begin
     583           //link next entry having the same prefix
     584           CodeTable[PEntry^.LongerLast].NextWithPrefix:= NextCodeSlot;
     585           PEntry^.LongerLast := NextCodeSlot;
     586         end;
     587
     588         // add new encode table entry
     589         PEntry := @CodeTable[NextCodeSlot];
     590         PEntry^.Prefix := CurrentCode;
     591         PEntry^.Suffix := CurValue;
     592         PEntry^.LongerFirst := 0;
     593         PEntry^.LongerLast := 0;
     594         PEntry^.NextWithPrefix := 0;
     595         inc(NextCodeSlot);
     596
     597         Dec(PInput);
     598         break;
    501599       end;
    502600     end;
    503      rLast := result;
     601
     602     // write the code of the longest entry found
     603     WriteCode(CurrentCode);
     604
     605     if NextCodeSlot >= GIFCodeTableSize then
     606       DoClearCode
     607     else if NextCodeSlot > 1 shl CurCodeSize then
     608       inc(CurCodeSize);
    504609   end;
    505610
    506 var
    507    i,n,
    508    cc: integer; // current code to translate
    509    oc: integer; // last code encoded
    510    found: boolean; // decoded string in prefix table?
    511    pixel: byte; // lowest code to search for
    512    ldx: integer; // last index found
    513    fdx: integer; // current index found
    514    b: byte;
    515 begin
    516    LZWSize := ABitDepth;
    517    AStream.WriteBuffer(LZWSize, 1);
    518    OutputBufferSize := 0;
    519 
    520    // init data block
    521    fillchar(rCodeStack, sizeof(rCodeStack), 0);
    522    rBitString := 0;
    523    rBits := 0;
    524    rCurX := 0;
    525    rCurY := 0;
    526    rCurScan := nil;
    527    rLast := 0;
    528    rUnget:= false;
    529 
    530    LZWReset;
    531    // all within the data record
    532    // always save the clear code first ...
    533    LZWPutCode(rClearCode);
    534    // and first pixel
    535    oc := LZWReadBitmap;
    536    LZWPutCode(oc);
    537    // nothing found yet (but then, we haven't searched)
    538    ldx := 0;
    539    fdx := 0;
    540    // and the rest of the pixels
    541    rRowsLeft := AImageHeight;
    542    while (rRowsLeft > 0) do
    543    begin
    544      rSP := 0; // empty the stack of old data
    545      n := LZWReadBitmap; // next pixel from the bitmap
    546      LZWSaveCode(n);
    547      cc := rCodeStack[0]; // beginning of the string
    548      // add new encode table entry
    549      rPrefix[rNextSlot] := oc;
    550      rSuffix[rNextSlot] := cc;
    551      inc(rNextSlot);
    552      if (rNextSlot >= GIFCodeTableSize) then
    553        rMaxVal := true
    554      else if (rNextSlot > (1 shl rCurSize)) then
    555        inc(rCurSize);
    556      // find the running string of matching codes
    557      ldx := cc;
    558      found := true;
    559      while (found and (rRowsLeft > 0)) do
    560      begin
    561        n := LZWReadBitmap;
    562        LZWSaveCode(n);
    563        cc := rCodeStack[0];
    564        if (ldx < rFirstSlot) then
    565          i := rFirstSlot
    566        else
    567          i := ldx + 1;
    568        pixel := rCodeStack[rSP - 1];
    569        found := false;
    570        while ((not found) and (i < rNextSlot)) do
    571        begin
    572          found := ((rPrefix[i] = ldx) and (rSuffix[i] = pixel));
    573          inc(i);
    574        end;
    575        if (found) then
    576        begin
    577          ldx := i - 1;
    578          fdx := i - 1;
    579        end;
    580      end;
    581      // if not found, save this index, and get the same code again
    582      if (not found) then
    583      begin
    584        rUnget := true;
    585        rLast := rCodeStack[rSP-1];
    586        dec(rSP);
    587        cc := ldx;
    588      end
    589      else
    590        cc := fdx;
    591      // whatever we got, write it out as current table entry
    592      LZWPutCode(cc);
    593      if (rMaxVal and (rRowsLeft > 0)) then
    594      begin
    595        LZWPutCode(rClearCode);
    596        LZWReset;
    597        cc := LZWReadBitmap;
    598        LZWPutCode(cc);
    599      end;
    600      oc := cc;
    601    end;
    602    LZWPutCode(rEndCode);
    603    // write out the rest of the bit string
    604    while (rBits > 0) do
    605    begin
    606      b := (rBitString and $ff);
    607      rBitString := (rBitString shr 8);
    608      rBits := rBits - 8;
    609      OutputByte(b);
    610    end;
    611    FlushOutput;
    612    b := 0;
    613    AStream.Write(b, 1);
     611   WriteCode(EndStreamCode);
     612   CloseBitOutput;
     613   FlushByteOutput;
     614
     615   AStream.WriteByte(0); //GIF block terminator
     616   //Writeln;
    614617end;
    615618
    616 function GIFLoadFromStream(stream: TStream): TGIFData;
     619function GIFLoadFromStream(stream: TStream; MaxImageCount: integer = maxLongint): TGIFData;
    617620
    618621  procedure DumpData;
     
    625628      stream.position := stream.position + Count;
    626629    until (Count = 0) or (stream.position >= stream.size);
     630  end;
     631
     632  function ReadString: string;
     633  var Count: byte;
     634  begin
     635    Count := 0;
     636    stream.Read(Count, 1);
     637    setlength(result, Count);
     638    if Count > 0 then
     639      stream.ReadBuffer(result[1], length(result));
    627640  end;
    628641
     
    715728    GIFExtensionBlock: TGIFExtensionBlock;
    716729    GIFGraphicControlExtension: TGIFGraphicControlExtension;
    717     mincount, Count:   byte;
     730    mincount, Count, SubBlockId:   byte;
     731    app: String;
    718732
    719733  begin
    720734    stream.ReadBuffer({%H-}GIFExtensionBlock, sizeof(GIFExtensionBlock));
    721735    case GIFExtensionBlock.FunctionCode of
    722       $F9:
     736      $F9: //graphic control extension
    723737      begin
    724738        Count := 0;
     
    745759        DumpData;
    746760      end;
     761      $ff: //application extension
     762      begin
     763        app := ReadString;
     764        if app <> '' then
     765        begin
     766          if app = NetscapeApplicationIdentifier then
     767          begin
     768            repeat
     769              Count := 0;
     770              stream.Read(Count,1);
     771              if Count = 0 then break;
     772              stream.ReadBuffer({%H-}SubBlockId,1);
     773              Dec(Count);
     774              if (SubBlockId = NetscapeSubBlockIdLoopCount) and (Count >= 2) then
     775              begin
     776                stream.ReadBuffer(result.LoopCount, 2);
     777                dec(Count,2);
     778                result.LoopCount := LEtoN(result.LoopCount);
     779                if result.LoopCount > 0 then inc(result.LoopCount);
     780              end;
     781              stream.Position:= stream.Position+Count;
     782            until false;
     783          end else
     784            DumpData;
     785        end;
     786      end
    747787      else
    748788      begin
     
    758798  result.Images := nil;
    759799  result.AspectRatio := 1;
     800  result.LoopCount := 1;
    760801  if stream = nil then exit;
    761802
     
    790831      case GIFBlockID of
    791832        ';': ;
    792         ',': LoadImage;
     833        ',': begin
     834               if NbImages >= MaxImageCount then break;
     835               LoadImage;
     836             end;
    793837        '!': ReadExtension;
    794838        else
     
    10091053          for x := 0 to Image.Width -1 do
    10101054          begin
    1011             pdest^ := APalette.IndexOfColor(psource^);
     1055            if psource^.alpha < 128 then
     1056              pdest^ := APalette.IndexOfColor(BGRAPixelTransparent)
     1057            else
     1058              pdest^ := APalette.IndexOfColor(BGRA(psource^.red,psource^.green,psource^.blue,255));
    10121059            inc(psource);
    10131060            inc(pdest);
     
    10871134    for i := 0 to ImageCount-1 do
    10881135      WriteImage(i);
     1136  end;
     1137
     1138  procedure WriteLoopExtension;
     1139  var
     1140    app: shortstring;
     1141    w: Word;
     1142  begin
     1143    if AData.LoopCount = 1 then exit;
     1144
     1145    Stream.WriteByte(GIFExtensionIntroducer);
     1146    Stream.WriteByte($ff);
     1147    app := NetscapeApplicationIdentifier;
     1148    Stream.WriteBuffer(app[0], length(app)+1);
     1149
     1150    Stream.WriteByte(3);
     1151    Stream.WriteByte(NetscapeSubBlockIdLoopCount);
     1152    if AData.LoopCount = 0 then
     1153      w := 0
     1154    else
     1155      w := AData.LoopCount-1;
     1156    w := NtoLE(w);
     1157    Stream.WriteWord(w);
     1158
     1159    Stream.WriteByte(0);
    10891160  end;
    10901161
     
    11061177    WriteGlobalPalette;
    11071178
     1179    WriteLoopExtension;
     1180
    11081181    WriteImages;
    1109     Stream.WriteByte($3B); //end of file
     1182    Stream.WriteByte(GIFFileTerminator); //end of file
    11101183
    11111184  finally
Note: See TracChangeset for help on using the changeset viewer.