FPWriteGIF

In Free Pascal existiert die Unit FPReadGIF aber die Unit FPWriteGIF fehlt, hier nun ein erste Anfang.

Animierte GIFs werden noch nicht unterstützt!

fpwritegif.pas Pascal (25,43 kByte) 06.11.2013 00:30
unit FPWriteGIF;
{$mode objfpc}{$H+}
interface

uses Classes, SysUtils, FPImage, FPReadGif;

type TColor = -$7FFFFFFF - 1..$7FFFFFFF;

const
// GIF record separators
  kGifImageSeparator: byte = $2c;
  kGifExtensionSeparator: byte = $21;
  kGifTerminator: byte = $3b;
  kGifLabelGraphic: byte = $f9;
  kGifBlockTerminator: byte = $00;
// LZW encode table sizes
  kGifCodeTableSize = 4096;
// Raw rgb value
  clNone = TColor($1FFFFFFF);
  AlphaOpaque = $FF;
  AlphaTransparent = 0;
  MaxArr = (MaxLongint div Sizeof(integer)) - 1;

type
  APixel8 = array[0..MaxArr] of Byte;
  PAPixel8 = ^APixel8;

  TRGBQuadArray256 = array[0..256] of TFPCompactImgRGBA8BitValue;
  TOpenColorTableArray = array of TColor;
  TColorTableArray = array[0..$FF] of TColor;

  TOctreeNode = class; // Forward definition so TReducibleNodes can be declared
  TReducibleNodes = array[0..7] of TOctreeNode;
  TOctreeNode = class(TObject)
    IsLeaf: Boolean;
    PixelCount: Integer;
    RedSum, GreenSum, BlueSum: Integer;
    Next: TOctreeNode;
    Child: TReducibleNodes;
    constructor Create(const Level: Integer; var LeafCount: Integer; var ReducibleNodes: TReducibleNodes);
    destructor Destroy; override;
  end;

  TFPWriterGIF = class(TFPCustomImageWriter)
  private
    fHeader: TGifHeader;
    fDescriptor: TGifImageDescriptor; // only one image supported
    fGraphicsCtrlExt: TGifGraphicsControlExtension;
    fTransparent: Boolean;
    fBackground: TColor;
    fPixels: PAPixel8;
    fPixelList: PChar; // decoded pixel indices
    fPixelCount: longint; // number of pixels
    fColorTable: TColorTableArray;
    fColorTableSize: integer;

    procedure SaveToStream(Destination: TStream);
  protected
    procedure InternalWrite(Stream: TStream; Img: TFPCustomImage); override;
  public
    constructor Create; override;
    destructor Destroy; override;
  end;

implementation
{$REGION ' - TOctreeNode - '}
constructor TOctreeNode.Create(const Level: Integer; var LeafCount: Integer; var ReducibleNodes: TReducibleNodes);
var i: Integer;
begin
  PixelCount := 0;
  RedSum := 0;
  GreenSum := 0;
  BlueSum := 0;
  for i := Low(Child) to High(Child) do
    Child[i] := nil;
  IsLeaf := (Level = 8);
  if IsLeaf then
  begin
    Next := nil;
    Inc(LeafCount);
  end
  else
  begin
    Next := ReducibleNodes[Level];
    ReducibleNodes[Level] := Self;
  end
end;

destructor TOctreeNode.Destroy;
var i: Integer;
begin
  for i := Low(Child) to High(Child) do
    Child[i].Free
end;
{$ENDREGION}

{$REGION ' - TFPWriterGIF. - '}
constructor TFPWriterGIF.Create;
begin
  inherited Create;
end;

destructor TFPWriterGIF.Destroy;
begin
  inherited Destroy;
end;

// save the current GIF definition to a stream object
// at first, just write it to our memory stream fSOURCE
procedure TFPWriterGIF.SaveToStream(Destination: TStream);
var
  LZWStream: TMemoryStream; // temp storage for LZW
  LZWSize: integer; // LZW minimum code size

  // these LZW encode routines sqrunch a bitmap into a memory stream
  procedure LZWEncode();
  var
    rPrefix: array[0..kGifCodeTableSize-1] of integer; // string prefixes
    rSuffix: array[0..kGifCodeTableSize-1] of integer; // string suffixes
    rCodeStack: array[0..kGifCodeTableSize-1] of byte; // encoded pixels
    rSP: integer; // pointer into CodeStack
    rClearCode: integer; // reset decode params
    rEndCode: integer; // last code in input stream
    rCurSize: integer; // current code size
    rBitString: integer; // steady stream of bits to be decoded
    rBits: integer; // number of valid bits in BitString
    rMaxVal: boolean; // max code value found?
    rCurX: integer; // position of next pixel
    rCurY: integer; // position of next pixel
    rCurPass: integer; // pixel line pass 1..4
    rFirstSlot: integer; // for encoding an image
    rNextSlot: integer; // for encoding
    rCount: integer; // number of bytes read/written
    rLast: integer; // last byte read in
    rUnget: boolean; // read a new byte, or use zLast?

    procedure LZWReset;
    var i: integer;
    begin
      for i := 0 to (kGifCodeTableSize - 1) do
      begin
        rPrefix[i] := 0;
        rSuffix[i] := 0;
      end;
      rCurSize := LZWSize + 1;
      rClearCode := (1 shl LZWSize);
      rEndCode := rClearCode + 1;
      rFirstSlot := (1 shl (rCurSize - 1)) + 2;
      rNextSlot := rFirstSlot;
      rMaxVal := false;
    end;

    // save a code value on the code stack
    procedure LZWSaveCode(Code: integer);
    begin
      rCodeStack[rSP] := Code;
      inc(rSP);
    end;

    // save the code in the output data stream
    procedure LZWPutCode(code: integer);
    var
      n: integer;
      b: byte;
    begin
      // write out finished bytes
      // a literal "8" for 8 bits per byte
      while (rBits >= 8) do
      begin
        b := (rBitString and $ff);
        rBitString := (rBitString shr 8);
        rBits := rBits - 8;
        LZWStream.Write(b, 1);
      end;
      // make sure no junk bits left above the first byte
      rBitString := (rBitString and $ff);
      // and save out-going code
      n := (code shl rBits);
      rBitString := (rBitString or n);
      rBits := rBits + rCurSize;
    end;

    // get the next pixel from the bitmap, and return it as an index into the colormap
    function LZWReadBitmap: integer;
    var
      n: integer;
      j: longint;
      p: PChar;
    begin
      if (rUnget) then
      begin
        n := rLast;
        rUnget := false;
      end
      else
      begin
        inc(rCount);
        j := (rCurY * fDescriptor.Width) + rCurX;
        if ((0 <= j) and (j < fPixelCount)) then
        begin
          p := fPixelList + j;
          n := ord(p^);
        end
        else
          n := 0;
        // if first pass, make sure CurPass was initialized
        if (rCurPass = 0) then rCurPass := 1;
        inc(rCurX); // inc X position
        if (rCurX >= fDescriptor.Width) then // bumping Y ?
        begin
          rCurX := 0;
          inc(rCurY);
        end;
      end;
      rLast := n;
      result := n;
    end;

  var
    i,n,
    cc: integer; // current code to translate
    oc: integer; // last code encoded
    found: boolean; // decoded string in prefix table?
    pixel: byte; // lowest code to search for
    ldx: integer; // last index found
    fdx: integer; // current index found
    b: byte;
  begin
    // init data block
    fillchar(rCodeStack, sizeof(rCodeStack), 0);
    rBitString := 0;
    rBits := 0;
    rCurX := 0;
    rCurY := 0;
    rCurPass := 0;
    rLast := 0;
    rUnget:= false;

    LZWReset;
    // all within the data record
    // always save the clear code first ...
    LZWPutCode(rClearCode);
    // and first pixel
    oc := LZWReadBitmap;
    LZWPutCode(oc);
    // nothing found yet (but then, we haven't searched)
    ldx := 0;
    fdx := 0;
    // and the rest of the pixels
    rCount := 1;
    while (rCount <= fPixelCount) do
    begin
      rSP := 0; // empty the stack of old data
      n := LZWReadBitmap; // next pixel from the bitmap
      LZWSaveCode(n);
      cc := rCodeStack[0]; // beginning of the string
      // add new encode table entry
      rPrefix[rNextSlot] := oc;
      rSuffix[rNextSlot] := cc;
      inc(rNextSlot);
      if (rNextSlot >= kGifCodeTableSize) then
        rMaxVal := true
      else if (rNextSlot > (1 shl rCurSize)) then
        inc(rCurSize);
      // find the running string of matching codes
      ldx := cc;
      found := true;
      while (found and (rCount <= fPixelCount)) do
      begin
        n := LZWReadBitmap;
        LZWSaveCode(n);
        cc := rCodeStack[0];
        if (ldx < rFirstSlot) then
          i := rFirstSlot
        else
          i := ldx + 1;
        pixel := rCodeStack[rSP - 1];
        found := false;
        while ((not found) and (i < rNextSlot)) do
        begin
          found := ((rPrefix[i] = ldx) and (rSuffix[i] = pixel));
          inc(i);
        end;
        if (found) then
        begin
          ldx := i - 1;
          fdx := i - 1;
        end;
      end;
      // if not found, save this index, and get the same code again
      if (not found) then
      begin
        rUnget := true;
        rLast := rCodeStack[rSP-1];
        dec(rSP);
        cc := ldx;
      end
      else
        cc := fdx;
      // whatever we got, write it out as current table entry
      LZWPutCode(cc);
      if ((rMaxVal) and (rCount <= fPixelCount)) then
      begin
        LZWPutCode(rClearCode);
        LZWReset;
        cc := LZWReadBitmap;
        LZWPutCode(cc);
      end;
      oc := cc;
    end;
    LZWPutCode(rEndCode);
    // write out the rest of the bit string
    while (rBits > 0) do
    begin
      b := (rBitString and $ff);
      rBitString := (rBitString shr 8);
      rBits := rBits - 8;
      LZWStream.Write(b, 1);
    end;
  end;

var i: integer;
begin
  Destination.Position := 0;
  with fHeader do
  begin
    // write the GIF signature
    // if only one image, and no image extensions, then GIF is GIF87a,
    // else use the updated version GIF98a
    // we just added an extension block; the signature must be version 89a
    Destination.Write(Signature, 3);
    Destination.Write(Version, 3);
    // write the overall GIF screen description to the source stream
    Destination.Write(ScreenWidth, 2); // logical screen width
    Destination.Write(ScreenHeight, 2); // logical screen height
    Destination.Write(Packedbit, 1); // packed bit fields (Global Color valid, Global Color size, Sorted, Color Resolution)
    Destination.Write(BackgroundColor, 1); // background color
    Destination.Write(AspectRatio, 1); // pixel aspect ratio
    if (Packedbit and $80)>0 then //Global Color valid
      // write out color gobal table with RGB values
      for i := 0 to fColorTableSize-1 do
        Destination.Write(fColorTable[i], 3);
  end;
  // write out graphic extension for this image
  Destination.Write(kGifExtensionSeparator, 1); // write the extension separator
  Destination.Write(kGifLabelGraphic, 1); // write the extension label
  Destination.Write(fGraphicsCtrlExt.BlockSize, 1); // block size (always 4)
  Destination.Write(fGraphicsCtrlExt.Packedbit, 1); // packed bit field
  Destination.Write(fGraphicsCtrlExt.DelayTime, 2); // delay time
  Destination.Write(fGraphicsCtrlExt.ColorIndex, 1); // transparent color
  Destination.Write(fGraphicsCtrlExt.Terminator, 1); // block terminator
  // write actual image data
  Destination.Write(kGifImageSeparator, 1);
  // write the next image descriptor shortcut to the record fields
  with fDescriptor do
  begin
    // write the basic descriptor record
    Destination.Write(Left, 2); // left position
    Destination.Write(Top, 2); // top position
    Destination.Write(Width, 2); // size of image
    Destination.Write(Height, 2); // size of image
    Destination.Write(Packedbit, 1); // packed bit field
    // there is no local color table defined we use global
    LZWSize := 8; // the LZW minimum code size
    Destination.Write(LZWSize, 1);
    LZWStream := TMemoryStream.Create; // init the storage for compressed data
    try
      LZWEncode(); // encode the image and save it in LZWStream
      // write out the data stream as a series of data blocks
      LZWStream.Position := 0;
      while (LZWStream.Position < LZWStream.Size) do
      begin
        i := LZWStream.Size - LZWStream.Position;
        if (i > 255) then i := 255;
        Destination.Write(i, 1);
        Destination.CopyFrom(LZWStream, i);
      end;
    finally
      FreeAndNil(LZWStream);
    end;
    Destination.Write(kGifBlockTerminator, 1); // block terminator
  end;
  Destination.Write(kGifTerminator, 1); // done with writing
end;

procedure TFPWriterGIF.InternalWrite(Stream: TStream; Img: TFPCustomImage);
var
  CT: TOpenColorTableArray;
  Palette: TList;
  PaletteHasAllColours: Boolean;
  Mappings: array[BYTE, BYTE] of TList;
  Tree: TOctreeNode;
  LeafCount: Integer;
  ReducibleNodes: TReducibleNodes;
  LastColor: TColor;
  LastColorIndex: Byte;

  // convert TFPCustomImage TFPColor to TColor
  function FPColorToTColor(const FPColor: TFPColor): TColor;
  begin
    result := TColor(((FPColor.Red shr 8) and $ff) or (FPColor.Green and $ff00) or ((FPColor.Blue shl 8) and $ff0000));
  end;

  // try to make color table of all colors
  function MakeColorTableOfAllColors(): Boolean;
  var
    Flags: array[Byte, Byte] of TBits;
    x, y, ci: Cardinal;
    Red, Green, Blue: Byte;
    Cnt: word;
  begin
    result := false;
    // init Flags
    for y := 0 to $FF do
      for x := 0 to $FF do
        Flags[x, y] := nil;
    try
      for ci := 0 to $ff do
        CT[ci] := 0;
      Cnt := 0;
      for y := 0 to Img.Height - 1 do
        for x := 0 to Img.Width - 1 do
        begin
          Red := Byte(Img.Colors[x, y].red shr 8);
          Green := Byte(Img.Colors[x, y].green shr 8);
          Blue := Byte(Img.Colors[x, y].blue shr 8);
          if (Flags[Red, Green]) = nil then
          begin
            Flags[Red, Green] := Classes.TBits.Create;
            Flags[Red, Green].Size := 256;
          end;
          if not Flags[Red, Green].Bits[Blue] then
          begin
            CT[Cnt] := FPColorToTColor(Img.Colors[x, y]);
            if Cnt = $ff then exit;
            inc(Cnt);
            Flags[Red, Green].Bits[Blue] := true;
          end;
        end;
      result := true;
      PaletteHasAllColours := true;
    finally // free Flags
      for y := 0 to $FF do
        for x := 0 to $FF do
          if Flags[x, y] <> nil then
            FreeAndNil(Flags[x, y]);
    end;
    fColorTableSize := High(CT) + 1;
    for x := 0 to fColorTableSize - 1 do
      fColorTable[x] := CT[x];
    LastColor := clNone;
  end;

  procedure MakeColorTableofReducedColors();
    procedure AddColor(var Node: TOctreeNode; const r, g, b: Byte; const Level: Integer; var ReducibleNodes: TReducibleNodes);
    const mask: array[0..7] of Byte = ($80, $40, $20, $10, $08, $04, $02, $01);
    var Index, Shift: Integer;
    begin
      if Node = nil then
        Node := TOctreeNode.Create(Level, LeafCount, ReducibleNodes);
      if Node.IsLeaf then
      begin
        Inc(Node.PixelCount);
        Inc(Node.RedSum, r);
        Inc(Node.GreenSum, g);
        Inc(Node.BlueSum, b)
      end
      else
      begin
        Shift := 7 - Level;
        Index := (((r and mask[Level]) shr Shift) shl 2) or (((g and mask[Level]) shr Shift) shl 1) or
          ((b and mask[Level]) shr Shift);
        AddColor(Node.Child[Index], r, g, b, Level + 1, ReducibleNodes)
      end
    end;

    procedure ReduceTree(var LeafCount: Integer; var ReducibleNodes: TReducibleNodes);
    var
      RedSum, BlueSum, GreenSum, Children, i: Integer;
      Node: TOctreeNode;
    begin
      i := 7;
      while (i > 0) and (ReducibleNodes[i] = nil) do
        dec(i);
      Node := ReducibleNodes[i];
      ReducibleNodes[i] := Node.Next;
      RedSum := 0;
      GreenSum := 0;
      BlueSum := 0;
      Children := 0;
      for i := Low(ReducibleNodes) to High(ReducibleNodes) do
        if Node.Child[i] <> nil then
        begin
          Inc(RedSum, Node.Child[i].RedSum);
          Inc(GreenSum, Node.Child[i].GreenSum);
          Inc(BlueSum, Node.Child[i].BlueSum);
          Inc(Node.PixelCount, Node.Child[i].PixelCount);
          Node.Child[i].Free;
          Node.Child[i] := nil;
          inc(Children)
        end;
      Node.IsLeaf := true;
      Node.RedSum := RedSum;
      Node.GreenSum := GreenSum;
      Node.BlueSum := BlueSum;
      Dec(LeafCount, Children - 1)
    end;

    procedure GetPaletteColors(const Node: TOctreeNode; var RGBQuadArray: TRGBQuadArray256; var Index: integer);
    var i: integer;
    begin
      if Node.IsLeaf then
      begin
        with RGBQuadArray[Index] do
        begin
          try
            r := Byte(Node.RedSum div Node.PixelCount);
            g := Byte(Node.GreenSum div Node.PixelCount);
            b := Byte(Node.BlueSum div Node.PixelCount);
            a := 0;
          except
            r := 0;
            g := 0;
            b := 0;
            a := 0;
          end;
          a := 0
        end;
        inc(Index);
      end
      else
        for i := Low(Node.Child) to High(Node.Child) do
          if Node.Child[i] <> nil then
            GetPaletteColors(Node.Child[i], RGBQuadArray, Index)
    end;

    procedure SetPalette(Pal: array of TColor; Size: integer);
    var
      PalSize, i: integer;
      Col: PFPCompactImgRGB8BitValue;
      x, y: Cardinal;
      Red, Green, Blue: Byte;
      Pcol: PInteger;
      DistanceSquared, SmallestDistanceSquared: integer;
      R1, G1, B1: Byte;
    begin
      if Size <> -1 then PalSize := Size else PalSize := High(Pal) + 1;
      for i := 0 to PalSize - 1 do
      begin
        GetMem(Col, SizeOf(TFPCompactImgRGB8BitValue));
        Col^.r := Byte(Pal[i]);
        Col^.g := Byte(Pal[i] shr 8);
        Col^.b := Byte(Pal[i] shr 16);
        Palette.Add(Col);
      end;
      for y := 0 to $ff do
        for x := 0 to $ff do
          Mappings[y,x] := nil;
      for y := 0 to Img.Height - 1 do
        for x := 0 to Img.Width - 1 do
        begin
          Red := Byte(Img.Colors[x, y].red shr 8);
          Green := Byte(Img.Colors[x, y].green shr 8);
          Blue := Byte(Img.Colors[x, y].blue shr 8);
          //Small reduction of color space
          dec(Red, Red mod 3);
          dec(Green, Green mod 3);
          dec(Blue, Blue mod 3);
          if (Mappings[Red, Green]) = nil then
          begin
            Mappings[Red, Green] := TList.Create;
            Mappings[Red, Green].Count := 256;
          end;
          if (Mappings[Red, Green].Items[Blue] = nil) then
          begin
            GetMem(Pcol, SizeOf(integer));
            PCol^ := 0;
            SmallestDistanceSquared := $1000000;
            for i := 0 to Palette.Count - 1 do
            begin
              R1 := PFPCompactImgRGB8BitValue(Palette[i])^.r;
              G1 := PFPCompactImgRGB8BitValue(Palette[i])^.g;
              B1 := PFPCompactImgRGB8BitValue(Palette[i])^.b;
              DistanceSquared := (Red - R1) * (Red - R1) + (Green - G1) * (Green - G1) + (Blue - B1) * (Blue - B1);
              if DistanceSquared < SmallestDistanceSquared then
              begin
                PCol^ := i;
                if (Red = R1) and (Green = G1) and (Blue = B1) then break;
                SmallestDistanceSquared := DistanceSquared;
              end
            end;
            Mappings[Red, Green].Items[Blue] := PCol;
          end;
        end;
    end;

    procedure DeleteTree(var Node: TOctreeNode);
    var i: integer;
    begin
      for i := Low(TReducibleNodes) to High(TReducibleNodes) do
        if Node.Child[i] <> nil then
          DeleteTree(Node.Child[i]);
      FreeAndNil(Node);
    end;

  var
    i, j, Index: integer;
    QArr: TRGBQuadArray256;
  begin
    PaletteHasAllColours := false;
    Tree := nil;
    LeafCount := 0;
    for i := Low(ReducibleNodes) to High(ReducibleNodes) do
      ReducibleNodes[i] := nil;
    if (Img.Height > 0) and (Img.Width > 0) then
      for j := 0 to Img.Height - 1 do
        for i := 0 to Img.Width - 1 do
        begin
          AddColor(Tree, Byte(Img.Colors[i,j].red shr 8), Byte(Img.Colors[i,j].green shr 8), Byte(Img.Colors[i,j].blue shr 8), 0, ReducibleNodes);
          while LeafCount > 256 do
            ReduceTree(LeafCount, ReducibleNodes)
        end;
    Index := 0;
    GetPaletteColors(Tree, QArr, Index);
    for i := 0 to LeafCount - 1 do
      CT[i] := (QArr[i].b shl 16) + (QArr[i].g shl 8) + QArr[i].r;
    fColorTableSize := LeafCount;
    for i := 0 to fColorTableSize - 1 do
      fColorTable[i] := CT[i];
    LastColor := clNone;
    SetPalette(fColorTable, LeafCount);
    if Tree <> nil then DeleteTree(Tree);
  end;

  procedure ClearMappings;
  var i, j, k: integer;
  begin
    for j := 0 to $FF do
      for i := 0 to $FF do
      begin
        if Assigned(Mappings[i, j]) then
        begin
          for k := 0 to $FF do
            FreeMem(Mappings[i, j].Items[k], SizeOf(TColor));
          Mappings[i, j].Free;
        end;
        Mappings[i, j] := nil;
      end;
  end;

  procedure SetPixel(X, Y: Integer; Value: TColor);
  var
    Val: integer;
    PCol: PInteger;
    R, G, B: byte;
  begin
    if not ((Img.Width >= X) and (Img.Height >= Y) and (X > -1) and (Y > -1)) then exit;
    Val := -1;
    if LastColor = Value then
      Val := LastColorIndex
    else
    begin
      if PaletteHasAllColours then
      begin
        TFPCompactImgRGBA8BitValue(Value).a := 0;
        for Val := 0 to fColorTableSize - 1 do
          if fColorTable[Val] = Value then break;
      end
      else
      begin
        B := Byte(Value shr 16);
        B := B - (B mod 3);
        G := Byte(Value shr 8);
        G := G - (G mod 3);
        R := Byte(Value);
        R := R - (R mod 3);
        Val := -1;
        if Mappings[R, G] <> nil then
        begin
          PCol := Mappings[R, G].Items[B];
          if PCol <> nil then Val := PCol^;
        end;
      end;
      LastColor := Value;
      LastColorIndex := Val;
    end;
    fPixels^[Y * Img.Width + X] := Val;
  end;

  // find the color within the color table; returns 0..255, -1 if color not found
  function FindColorIndex(c: TColor): integer;
  var i: integer;
  begin
    i := 0;
    result := -1;
    while (i<fColorTableSize) and (result < 0) do
    begin
      if (fColorTable[i] = c) then result := i;
      inc(i);
    end;
  end;

  function lsb(w: word): byte;
  begin
    result := 0;
    while ((w shr result) and 1) = 0 do inc(result);
  end;

var
  x, y: cardinal;
  i, n, ci: integer;
  b: byte;
  pptr: PChar;
begin
  if not ((Img.Width < 1) or (Img.Height < 1)) then
  try
    fTransparent := false;
    // translate 64bit image to 8bit colortable image
    Palette := TList.Create;
    fColorTableSize := 0;
    SetLength(CT, 256);
    //try to make optimized palette on original Data.
    if not MakeColorTableOfAllColors() then
      MakeColorTableofReducedColors(); // to mutch colors, reduce colors
    GetMem(fPixels, Img.Height * Img.Width);
    for y := 0 to Img.Height - 1 do
      for x := 0 to Img.Width - 1 do
      begin
        SetPixel(x, y, FPColorToTColor(Img.Colors[x, y]));
        if not fTransparent then
          if Img.Colors[x, y].alpha = AlphaTransparent then
          begin
            fBackground := FPColorToTColor(Img.Colors[x, y]);
            fTransparent := true;
          end;
      end;
    // color count must be a power of 2
    if (fColorTableSize <= 2) then fColorTableSize := 2
    else if (fColorTableSize <= 4) then fColorTableSize := 4
    else if (fColorTableSize <= 8) then fColorTableSize := 8
    else if (fColorTableSize <= 16) then fColorTableSize := 16
    else if (fColorTableSize <= 32) then fColorTableSize := 32
    else if (fColorTableSize <= 64) then fColorTableSize := 64
    else if (fColorTableSize <= 128) then fColorTableSize := 128
    else fColorTableSize := 256;
  finally
    for i := 0 to Palette.Count - 1 do
      FreeMem(Palette[i], SizeOf(TFPCompactImgRGB8BitValue));
    Palette.Clear;
    ClearMappings;
    Palette.Free;
  end;

  // create a new gif image record from the given 8bit colortable image
  with fHeader do
  begin
    Signature := 'GIF';
    Version := '89a';
    ScreenWidth := Img.Width;
    ScreenHeight := Img.Height;
    b := lsb(fColorTableSize)-1;
    Packedbit := (Packedbit and $8F) or (b shl 4); // Color Resolution
    Packedbit := (Packedbit and $F7); // not sorted
    Packedbit := (Packedbit and $F8) or b;
    BackgroundColor := 0;
    Packedbit := Packedbit or $80; // Global Color valid
  end;

  // make a descriptor record, color map for this image, and space for a pixel list
  with fDescriptor do
  begin
    Left := 0;
    Top := 0;
    Width := Img.Width;
    Height := Img.Height;
    Packedbit := 0; // or $80 = but non local Color Table; or $40 = but not interlaced; or $20 but not sorted
  end;

  fPixelList := nil; // make empty pixel list
  fPixelCount := Img.Width * Img.Height;
  fPixelList := allocmem(fPixelCount);
  if (fPixelList = nil) then OutOfMemoryError;
  // and the color table
  // the first call attempts to use all colors in the bitmap
  // if too many colors, the 2nd call uses only most significat 8 bits of color
  for ci:=0 to fPixelCount-1 do
  begin
    pptr := fPixelList + ci;
    pptr^ := Chr(fPixels^[ci]);
  end;

  // set transparency for this image
  with fGraphicsCtrlExt do
  begin
    BlockSize := 4;
    Packedbit := $00;
    ColorIndex := 0;
    if (fTransparent) then
    begin
      n := FindColorIndex(fBackground);
      if (n < 0) then n := FindColorIndex(fBackground and $00E0E0E0);
      if (n < 0) then n := FindColorIndex(fBackground and $00C0E0E0);
      if (n > -1) then
      begin
        Packedbit := Packedbit or $01; // transparent color given (Packedbit or $01)
        ColorIndex := n; //transparent color index
      end;
    end;
    DelayTime := 0;
    Terminator := 0; // allways 0
  end;

  SaveToStream(Stream);

  if (fPixelList <> nil) then FreeMem(fPixelList);
  FreeMem(fPixels);
  fPixels := nil;
end;
{$ENDREGION}

initialization
  ImageHandlers.RegisterImageWriter ('GIF Graphics', 'gif', TFPWriterGif);
end.

Author: , published: , last modified:

Kontakt

Udo Schmal

Udo Schmal
Softwareentwickler
Olvengraben 41
47608 Geldern
Nordrhein-Westfalen
Germany





+49 2831 9776557
+49 1575 0663676
+49 2831 1328709
SMS
WhatsApp

Google Maps-Profile
Instagram Profile
vCard 3.0

Service Infos

CMS Info Product Name:
UDOs Webserver
Version:
0.5.0.104
Description:
All in one Webserver
Copyright:
Udo Schmal
Compilation:
Sat, 9. Oct 2021 15:16:12
Development Info Compiler:
Free Pascal FPC 3.3.1
compiled for:
OS:Linux, CPU:x86_64
System Info OS:
Ubuntu 20.04.3 LTS (Focal Fossa)
Hardware Info Model:
Hewlett-Packard HP Pavilion dv7 Notebook PC
CPU Name:
Intel(R) Core(TM) i5-2450M CPU @ 2.50GHz
CPU Type:
x86_64, 1 physical CPU(s), 2 Core(s), 4 logical CPU(s), 800.000 MHz