SA-MP/exgui/GIFImage.pas

12561 lines
353 KiB
ObjectPascal
Raw Normal View History

2023-12-06 23:03:26 +08:00
unit GIFImage;
////////////////////////////////////////////////////////////////////////////////
// //
// Project: GIF Graphics Object //
// Module: gifimage //
// Description: TGraphic implementation of the GIF89a graphics format //
// Version: 2.2 //
// Release: 5 //
// Date: 23-MAY-1999 //
// Target: Win32, Delphi 2, 3, 4 & 5, C++ Builder 3 & 4 //
// Author(s): anme: Anders Melander, anders@melander.dk //
// fila: Filip Larsen //
// rps: Reinier Sterkenburg //
// Copyright: (c) 1997-99 Anders Melander. //
// All rights reserved. //
// Formatting: 2 space indent, 8 space tabs, 80 columns. //
// //
////////////////////////////////////////////////////////////////////////////////
// Changed 2001.07.23 by Finn Tolderlund //
// Changed according to e-mail from "Rolf Frei" <rolf@eicom.ch> //
// on 2001.07.23 so that it works in Delphi 6. //
// //
// Changed 2002.07.07 by Finn Tolderlund //
// Incorporated additional modifications by Alexey Barkovoy (clootie@reactor.ru)
// found in his Delphi 6 GifImage.pas (from 22-Dec-2001). //
// Alexey Barkovoy's Delphi 6 gifimage.pas can be downloaded from //
// http://clootie.narod.ru/delphi/download_vcl.html //
// These changes made showing of animated gif files more stable. The code //
// from 2001.07.23 could crash sometimes with an Execption EAccessViolation. //
// //
////////////////////////////////////////////////////////////////////////////////
// //
// Please read the "Conditions of use" in the release notes. //
// //
////////////////////////////////////////////////////////////////////////////////
// Known problems:
//
// * The combination of buffered, tiled and transparent draw will display the
// background incorrectly (scaled).
// If this is a problem for you, use non-buffered (goDirectDraw) drawing
// instead.
//
// * The combination of non-buffered, transparent and stretched draw is
// sometimes distorted with a pattern effect when the image is displayed
// smaller than the real size (shrinked).
//
// * Buffered display flickers when TGIFImage is used by a transparent TImage
// component.
// This is a problem with TImage caused by the fact that TImage was designed
// with static images in mind. Not much I can do about it.
//
////////////////////////////////////////////////////////////////////////////////
// To do (in rough order of priority):
// { TODO -oanme -cFeature : TImage hook for destroy notification. }
// { TODO -oanme -cFeature : TBitmap pool to limit resource consumption on Win95/98. }
// { TODO -oanme -cImprovement : Make BitsPerPixel property writable. }
// { TODO -oanme -cFeature : Visual GIF component. }
// { TODO -oanme -cImprovement : Easier method to determine DrawPainter status. }
// { TODO -oanme -cFeature : Import to 256+ color GIF. }
// { TODO -oanme -cFeature : Make some of TGIFImage's properties persistent (DrawOptions etc). }
// { TODO -oanme -cFeature : Add TGIFImage.Persistent property. Should save published properties in application extension when this options is set. }
// { TODO -oanme -cBugFix : Solution for background buffering in scrollbox. }
//
//////////////////////////////////////////////////////////////////////////////////
{$ifdef BCB}
{$ObjExportAll On}
{$endif}
interface
////////////////////////////////////////////////////////////////////////////////
//
// Conditional Compiler Symbols
//
////////////////////////////////////////////////////////////////////////////////
(*
DEBUG Must be defined if any of the DEBUG_xxx
symbols are defined.
If the symbol is defined the source will not be
optimized and overflow- and range checks will be
enabled.
DEBUG_HASHPERFORMANCE Calculates hash table performance data.
DEBUG_HASHFILLFACTOR Calculates fill factor of hash table -
Interferes with DEBUG_HASHPERFORMANCE.
DEBUG_COMPRESSPERFORMANCE Calculates LZW compressor performance data.
DEBUG_DECOMPRESSPERFORMANCE Calculates LZW decompressor performance data.
DEBUG_DITHERPERFORMANCE Calculates color reduction performance data.
DEBUG_DRAWPERFORMANCE Calculates low level drawing performance data.
The performance data for DEBUG_DRAWPERFORMANCE
will be displayed when you press the Ctrl key.
DEBUG_RENDERPERFORMANCE Calculates performance data for the GIF to
bitmap converter.
The performance data for DEBUG_DRAWPERFORMANCE
will be displayed when you press the Ctrl key.
GIF_NOSAFETY Define this symbol to disable overflow- and
range checks.
Ignored if the DEBUG symbol is defined.
STRICT_MOZILLA Define to mimic Mozilla as closely as possible.
If not defined, a slightly more "optimal"
implementation is used (IMHO).
FAST_AS_HELL Define this symbol to use strictly GIF compliant
(but too fast) animation timing.
Since our paint routines are much faster and
more precise timed than Mozilla's, the standard
GIF and Mozilla values causes animations to loop
faster than they would in Mozilla.
If the symbol is _not_ defined, an alternative
set of tweaked timing values will be used.
The tweaked values are not optimal but are based
on tests performed on my reference system:
- Windows 95
- 133 MHz Pentium
- 64Mb RAM
- Diamond Stealth64/V3000
- 1600*1200 in 256 colors
The alternate values can be modified if you are
not satisfied with my defaults (they can be
found a few pages down).
REGISTER_TGIFIMAGE Define this symbol to register TGIFImage with
the TPicture class and integrate with TImage.
This is required to be able to display GIFs in
the TImage component.
The symbol is defined by default.
Undefine if you use another GIF library to
provide GIF support for TImage.
PIXELFORMAT_TOO_SLOW When this symbol is defined, the internal
PixelFormat routines are used in some places
instead of TBitmap.PixelFormat.
The current implementation (Delphi4, Builder 3)
of TBitmap.PixelFormat can in some situation
degrade performance.
The symbol is defined by default.
CREATEDIBSECTION_SLOW If this symbol is defined, TDIBWriter will
use global memory as scanline storage, instead
of a DIB section.
Benchmarks have shown that a DIB section is
twice as slow as global memory.
The symbol is defined by default.
The symbol requires that PIXELFORMAT_TOO_SLOW
is defined.
SERIALIZE_RENDER Define this symbol to serialize threaded
GIF to bitmap rendering.
When a GIF is displayed with the goAsync option
(the default), the GIF to bitmap rendering is
executed in the context of the draw thread.
If more than one thread is drawing the same GIF
or the GIF is being modified while it is
animating, the GIF to bitmap rendering should be
serialized to guarantee that the bitmap isn't
modified by more than one thread at a time. If
SERIALIZE_RENDER is defined, the draw threads
uses TThread.Synchronize to serialize GIF to
bitmap rendering.
*)
{$DEFINE REGISTER_TGIFIMAGE}
{$DEFINE PIXELFORMAT_TOO_SLOW}
{$DEFINE CREATEDIBSECTION_SLOW}
////////////////////////////////////////////////////////////////////////////////
//
// Determine Delphi and C++ Builder version
//
////////////////////////////////////////////////////////////////////////////////
// Delphi 1.x
{$IFDEF VER80}
'Error: TGIFImage does not support Delphi 1.x'
{$ENDIF}
// Delphi 2.x
{$IFDEF VER90}
{$DEFINE VER9x}
{$ENDIF}
// C++ Builder 1.x
{$IFDEF VER93}
// Good luck...
{$DEFINE VER9x}
{$ENDIF}
// Delphi 3.x
{$IFDEF VER100}
{$DEFINE VER10_PLUS}
{$DEFINE D3_BCB3}
{$ENDIF}
// C++ Builder 3.x
{$IFDEF VER110}
{$DEFINE VER10_PLUS}
{$DEFINE VER11_PLUS}
{$DEFINE D3_BCB3}
{$DEFINE BAD_STACK_ALIGNMENT}
{$ENDIF}
// Delphi 4.x
{$IFDEF VER120}
{$DEFINE VER10_PLUS}
{$DEFINE VER11_PLUS}
{$DEFINE VER12_PLUS}
{$DEFINE BAD_STACK_ALIGNMENT}
{$ENDIF}
// C++ Builder 4.x
{$IFDEF VER125}
{$DEFINE VER10_PLUS}
{$DEFINE VER11_PLUS}
{$DEFINE VER12_PLUS}
{$DEFINE VER125_PLUS}
{$DEFINE BAD_STACK_ALIGNMENT}
{$ENDIF}
// Delphi 5.x
{$IFDEF VER130}
{$DEFINE VER10_PLUS}
{$DEFINE VER11_PLUS}
{$DEFINE VER12_PLUS}
{$DEFINE VER125_PLUS}
{$DEFINE VER13_PLUS}
{$DEFINE BAD_STACK_ALIGNMENT}
{$ENDIF}
// Delphi 6.x
{$IFDEF VER140}
{$WARN SYMBOL_PLATFORM OFF}
{$DEFINE VER10_PLUS}
{$DEFINE VER11_PLUS}
{$DEFINE VER12_PLUS}
{$DEFINE VER125_PLUS}
{$DEFINE VER13_PLUS}
{$DEFINE VER14_PLUS}
{$DEFINE BAD_STACK_ALIGNMENT}
{$ENDIF}
// Unknown compiler version - assume D4 compatible
{$IFNDEF VER9x}
{$IFNDEF VER10_PLUS}
{$DEFINE VER10_PLUS}
{$DEFINE VER11_PLUS}
{$DEFINE VER12_PLUS}
{$DEFINE BAD_STACK_ALIGNMENT}
{$ENDIF}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
//
// Compiler Options required to compile this library
//
////////////////////////////////////////////////////////////////////////////////
{$A+,B-,H+,J+,K-,M-,T-,X+}
// Debug control - You can safely change these settings
{$IFDEF DEBUG}
{$C+} // ASSERTIONS
{$O-} // OPTIMIZATION
{$Q+} // OVERFLOWCHECKS
{$R+} // RANGECHECKS
{$ELSE}
{$C-} // ASSERTIONS
{$IFDEF GIF_NOSAFETY}
{$Q-}// OVERFLOWCHECKS
{$R-}// RANGECHECKS
{$ENDIF}
{$ENDIF}
// Special options for Time2Help parser
{$ifdef TIME2HELP}
{$UNDEF PIXELFORMAT_TOO_SLOW}
{$endif}
////////////////////////////////////////////////////////////////////////////////
//
// External dependecies
//
////////////////////////////////////////////////////////////////////////////////
uses
sysutils,
Windows,
Graphics,
Classes;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFImage library version
//
////////////////////////////////////////////////////////////////////////////////
const
GIFVersion = $0202;
GIFVersionMajor = 2;
GIFVersionMinor = 2;
GIFVersionRelease = 5;
////////////////////////////////////////////////////////////////////////////////
//
// Misc constants and support types
//
////////////////////////////////////////////////////////////////////////////////
const
GIFMaxColors = 256; // Max number of colors supported by GIF
// Don't bother changing this value!
BitmapAllocationThreshold = 500000; // Bitmap pixel count limit at which
// a newly allocated bitmap will be
// converted to 1 bit format before
// being resized and converted to 8 bit.
var
{$IFDEF FAST_AS_HELL}
GIFDelayExp: integer = 10; // Delay multiplier in mS.
{$ELSE}
GIFDelayExp: integer = 12; // Delay multiplier in mS. Tweaked.
{$ENDIF}
// * GIFDelayExp:
// The following delay values should all
// be multiplied by this value to
// calculate the effective time (in mS).
// According to the GIF specs, this
// value should be 10.
// Since our paint routines are much
// faster than Mozilla's, you might need
// to increase this value if your
// animations loops too fast. The
// optimal value is impossible to
// determine since it depends on the
// speed of the CPU, the viceo card,
// memory and many other factors.
GIFDefaultDelay: integer = 10; // * GIFDefaultDelay:
// Default animation delay.
// This value is used if no GCE is
// defined.
// (10 = 100 mS)
{$IFDEF FAST_AS_HELL}
GIFMinimumDelay: integer = 1; // Minimum delay (from Mozilla source).
// (1 = 10 mS)
{$ELSE}
GIFMinimumDelay: integer = 3; // Minimum delay - Tweaked.
{$ENDIF}
// * GIFMinimumDelay:
// The minumum delay used in the Mozilla
// source is 10mS. This corresponds to a
// value of 1. However, since our paint
// routines are much faster than
// Mozilla's, a value of 3 or 4 gives
// better results.
GIFMaximumDelay: integer = 1000; // * GIFMaximumDelay:
// Maximum delay when painter is running
// in main thread (goAsync is not set).
// This value guarantees that a very
// long and slow GIF does not hang the
// system.
// (1000 = 10000 mS = 10 Seconds)
type
TGIFVersion = (gvUnknown, gv87a, gv89a);
TGIFVersionRec = array[0..2] of char;
const
GIFVersions : array[gv87a..gv89a] of TGIFVersionRec = ('87a', '89a');
type
// TGIFImage mostly throws exceptions of type GIFException
GIFException = class(EInvalidGraphic);
// Severity level as indicated in the Warning methods and the OnWarning event
TGIFSeverity = (gsInfo, gsWarning, gsError);
////////////////////////////////////////////////////////////////////////////////
//
// Delphi 2.x support
//
////////////////////////////////////////////////////////////////////////////////
{$IFDEF VER9x}
// Delphi 2 doesn't support TBitmap.PixelFormat
{$DEFINE PIXELFORMAT_TOO_SLOW}
type
// TThreadList from Delphi 3 classes.pas
TThreadList = class
private
FList: TList;
FLock: TRTLCriticalSection;
public
constructor Create;
destructor Destroy; override;
procedure Add(Item: Pointer);
procedure Clear;
function LockList: TList;
procedure Remove(Item: Pointer);
procedure UnlockList;
end;
// From Delphi 3 sysutils.pas
EOutOfMemory = class(Exception);
// From Delphi 3 classes.pas
EOutOfResources = class(EOutOfMemory);
// From Delphi 3 windows.pas
PMaxLogPalette = ^TMaxLogPalette;
TMaxLogPalette = packed record
palVersion: Word;
palNumEntries: Word;
palPalEntry: array [Byte] of TPaletteEntry;
end; { TMaxLogPalette }
// From Delphi 3 graphics.pas. Used by the D3 TGraphic class.
TProgressStage = (psStarting, psRunning, psEnding);
TProgressEvent = procedure (Sender: TObject; Stage: TProgressStage;
PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string) of object;
// From Delphi 3 windows.pas
PRGBTriple = ^TRGBTriple;
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
//
// Forward declarations
//
////////////////////////////////////////////////////////////////////////////////
type
TGIFImage = class;
TGIFSubImage = class;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFItem
//
////////////////////////////////////////////////////////////////////////////////
TGIFItem = class(TPersistent)
private
FGIFImage: TGIFImage;
protected
function GetVersion: TGIFVersion; virtual;
procedure Warning(Severity: TGIFSeverity; Message: string); virtual;
public
constructor Create(GIFImage: TGIFImage); virtual;
procedure SaveToStream(Stream: TStream); virtual; abstract;
procedure LoadFromStream(Stream: TStream); virtual; abstract;
procedure SaveToFile(const Filename: string); virtual;
procedure LoadFromFile(const Filename: string); virtual;
property Version: TGIFVersion read GetVersion;
property Image: TGIFImage read FGIFImage;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFList
//
////////////////////////////////////////////////////////////////////////////////
TGIFList = class(TPersistent)
private
FItems: TList;
FImage: TGIFImage;
protected
function GetItem(Index: Integer): TGIFItem;
procedure SetItem(Index: Integer; Item: TGIFItem);
function GetCount: Integer;
procedure Warning(Severity: TGIFSeverity; Message: string); virtual;
public
constructor Create(Image: TGIFImage);
destructor Destroy; override;
function Add(Item: TGIFItem): Integer;
procedure Clear;
procedure Delete(Index: Integer);
procedure Exchange(Index1, Index2: Integer);
function First: TGIFItem;
function IndexOf(Item: TGIFItem): Integer;
procedure Insert(Index: Integer; Item: TGIFItem);
function Last: TGIFItem;
procedure Move(CurIndex, NewIndex: Integer);
function Remove(Item: TGIFItem): Integer;
procedure SaveToStream(Stream: TStream); virtual;
procedure LoadFromStream(Stream: TStream; Parent: TObject); virtual; abstract;
property Items[Index: Integer]: TGIFItem read GetItem write SetItem; default;
property Count: Integer read GetCount;
property List: TList read FItems;
property Image: TGIFImage read FImage;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFColorMap
//
////////////////////////////////////////////////////////////////////////////////
// One way to do it:
// TBaseColor = (bcRed, bcGreen, bcBlue);
// TGIFColor = array[bcRed..bcBlue] of BYTE;
// Another way:
TGIFColor = packed record
Red: byte;
Green: byte;
Blue: byte;
end;
TColorMap = packed array[0..GIFMaxColors-1] of TGIFColor;
PColorMap = ^TColorMap;
TUsageCount = record
Count : integer; // # of pixels using color index
Index : integer; // Color index
end;
TColormapHistogram = array[0..255] of TUsageCount;
TColormapReverse = array[0..255] of byte;
TGIFColorMap = class(TPersistent)
private
FColorMap : PColorMap;
FCount : integer;
FCapacity : integer;
FOptimized : boolean;
protected
function GetColor(Index: integer): TColor;
procedure SetColor(Index: integer; Value: TColor);
function GetBitsPerPixel: integer;
function DoOptimize: boolean;
procedure SetCapacity(Size: integer);
procedure Warning(Severity: TGIFSeverity; Message: string); virtual; abstract;
procedure BuildHistogram(var Histogram: TColormapHistogram); virtual; abstract;
procedure MapImages(var Map: TColormapReverse); virtual; abstract;
public
constructor Create;
destructor Destroy; override;
class function Color2RGB(Color: TColor): TGIFColor;
class function RGB2Color(Color: TGIFColor): TColor;
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream; Count: integer);
procedure Assign(Source: TPersistent); override;
function IndexOf(Color: TColor): integer;
function Add(Color: TColor): integer;
function AddUnique(Color: TColor): integer;
procedure Delete(Index: integer);
procedure Clear;
function Optimize: boolean; virtual; abstract;
procedure Changed; virtual; abstract;
procedure ImportPalette(Palette: HPalette);
procedure ImportColorTable(Pal: pointer; Count: integer);
procedure ImportDIBColors(Handle: HDC);
procedure ImportColorMap(Map: TColorMap; Count: integer);
function ExportPalette: HPalette;
property Colors[Index: integer]: TColor read GetColor write SetColor; default;
property Data: PColorMap read FColorMap;
property Count: integer read FCount;
property Optimized: boolean read FOptimized write FOptimized;
property BitsPerPixel: integer read GetBitsPerPixel;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFHeader
//
////////////////////////////////////////////////////////////////////////////////
TLogicalScreenDescriptor = packed record
ScreenWidth: word; { logical screen width }
ScreenHeight: word; { logical screen height }
PackedFields: byte; { packed fields }
BackgroundColorIndex: byte; { index to global color table }
AspectRatio: byte; { actual ratio = (AspectRatio + 15) / 64 }
end;
TGIFHeader = class(TGIFItem)
private
FLogicalScreenDescriptor: TLogicalScreenDescriptor;
FColorMap : TGIFColorMap;
procedure Prepare;
protected
function GetVersion: TGIFVersion; override;
function GetBackgroundColor: TColor;
procedure SetBackgroundColor(Color: TColor);
procedure SetBackgroundColorIndex(Index: BYTE);
function GetBitsPerPixel: integer;
function GetColorResolution: integer;
public
constructor Create(GIFImage: TGIFImage); override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
procedure Clear;
property Version: TGIFVersion read GetVersion;
property Width: WORD read FLogicalScreenDescriptor.ScreenWidth
write FLogicalScreenDescriptor.ScreenWidth;
property Height: WORD read FLogicalScreenDescriptor.ScreenHeight
write FLogicalScreenDescriptor.Screenheight;
property BackgroundColorIndex: BYTE read FLogicalScreenDescriptor.BackgroundColorIndex
write SetBackgroundColorIndex;
property BackgroundColor: TColor read GetBackgroundColor
write SetBackgroundColor;
property AspectRatio: BYTE read FLogicalScreenDescriptor.AspectRatio
write FLogicalScreenDescriptor.AspectRatio;
property ColorMap: TGIFColorMap read FColorMap;
property BitsPerPixel: integer read GetBitsPerPixel;
property ColorResolution: integer read GetColorResolution;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFExtension
//
////////////////////////////////////////////////////////////////////////////////
TGIFExtensionType = BYTE;
TGIFExtension = class;
TGIFExtensionClass = class of TGIFExtension;
TGIFGraphicControlExtension = class;
TGIFExtension = class(TGIFItem)
private
FSubImage: TGIFSubImage;
protected
function GetExtensionType: TGIFExtensionType; virtual; abstract;
function GetVersion: TGIFVersion; override;
function DoReadFromStream(Stream: TStream): TGIFExtensionType;
class procedure RegisterExtension(elabel: BYTE; eClass: TGIFExtensionClass);
class function FindExtension(Stream: TStream): TGIFExtensionClass;
class function FindSubExtension(Stream: TStream): TGIFExtensionClass; virtual;
public
// Ignore compiler warning about hiding base class constructor
constructor Create(ASubImage: TGIFSubImage); {$IFDEF VER12_PLUS} reintroduce; {$ENDIF} virtual;
destructor Destroy; override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
property ExtensionType: TGIFExtensionType read GetExtensionType;
property SubImage: TGIFSubImage read FSubImage;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFSubImage
//
////////////////////////////////////////////////////////////////////////////////
TGIFExtensionList = class(TGIFList)
protected
function GetExtension(Index: Integer): TGIFExtension;
procedure SetExtension(Index: Integer; Extension: TGIFExtension);
public
procedure LoadFromStream(Stream: TStream; Parent: TObject); override;
property Extensions[Index: Integer]: TGIFExtension read GetExtension write SetExtension; default;
end;
TImageDescriptor = packed record
Separator: byte; { fixed value of ImageSeparator }
Left: word; { Column in pixels in respect to left edge of logical screen }
Top: word; { row in pixels in respect to top of logical screen }
Width: word; { width of image in pixels }
Height: word; { height of image in pixels }
PackedFields: byte; { Bit fields }
end;
TGIFSubImage = class(TGIFItem)
private
FBitmap : TBitmap;
FMask : HBitmap;
FNeedMask : boolean;
FLocalPalette : HPalette;
FData : PChar;
FDataSize : integer;
FColorMap : TGIFColorMap;
FImageDescriptor : TImageDescriptor;
FExtensions : TGIFExtensionList;
FTransparent : boolean;
FGCE : TGIFGraphicControlExtension;
procedure Prepare;
procedure Compress(Stream: TStream);
procedure Decompress(Stream: TStream);
protected
function GetVersion: TGIFVersion; override;
function GetInterlaced: boolean;
procedure SetInterlaced(Value: boolean);
function GetColorResolution: integer;
function GetBitsPerPixel: integer;
procedure AssignTo(Dest: TPersistent); override;
function DoGetBitmap: TBitmap;
function DoGetDitherBitmap: TBitmap;
function GetBitmap: TBitmap;
procedure SetBitmap(Value: TBitmap);
procedure FreeMask;
function GetEmpty: Boolean;
function GetPalette: HPALETTE;
procedure SetPalette(Value: HPalette);
function GetActiveColorMap: TGIFColorMap;
function GetBoundsRect: TRect;
procedure SetBoundsRect(const Value: TRect);
procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer);
function GetClientRect: TRect;
function GetPixel(x, y: integer): BYTE;
function GetScanline(y: integer): pointer;
procedure NewBitmap;
procedure FreeBitmap;
procedure NewImage;
procedure FreeImage;
procedure NeedImage;
function ScaleRect(DestRect: TRect): TRect;
function HasMask: boolean;
function GetBounds(Index: integer): WORD;
procedure SetBounds(Index: integer; Value: WORD);
function GetHasBitmap: boolean;
procedure SetHasBitmap(Value: boolean);
public
constructor Create(GIFImage: TGIFImage); override;
destructor Destroy; override;
procedure Clear;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
procedure Assign(Source: TPersistent); override;
procedure Draw(ACanvas: TCanvas; const Rect: TRect;
DoTransparent, DoTile: boolean);
procedure StretchDraw(ACanvas: TCanvas; const Rect: TRect;
DoTransparent, DoTile: boolean);
procedure Crop;
procedure Merge(Previous: TGIFSubImage);
property HasBitmap: boolean read GetHasBitmap write SetHasBitmap;
property Left: WORD index 1 read GetBounds write SetBounds;
property Top: WORD index 2 read GetBounds write SetBounds;
property Width: WORD index 3 read GetBounds write SetBounds;
property Height: WORD index 4 read GetBounds write SetBounds;
property BoundsRect: TRect read GetBoundsRect write SetBoundsRect;
property ClientRect: TRect read GetClientRect;
property Interlaced: boolean read GetInterlaced write SetInterlaced;
property ColorMap: TGIFColorMap read FColorMap;
property ActiveColorMap: TGIFColorMap read GetActiveColorMap;
property Data: PChar read FData;
property DataSize: integer read FDataSize;
property Extensions: TGIFExtensionList read FExtensions;
property Version: TGIFVersion read GetVersion;
property ColorResolution: integer read GetColorResolution;
property BitsPerPixel: integer read GetBitsPerPixel;
property Bitmap: TBitmap read GetBitmap write SetBitmap;
property Mask: HBitmap read FMask;
property Palette: HPALETTE read GetPalette write SetPalette;
property Empty: boolean read GetEmpty;
property Transparent: boolean read FTransparent;
property GraphicControlExtension: TGIFGraphicControlExtension read FGCE;
property Pixels[x, y: integer]: BYTE read GetPixel;
property Scanline[y: integer]: pointer read GetScanline;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFTrailer
//
////////////////////////////////////////////////////////////////////////////////
TGIFTrailer = class(TGIFItem)
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFGraphicControlExtension
//
////////////////////////////////////////////////////////////////////////////////
// Graphic Control Extension block a.k.a GCE
TGIFGCERec = packed record
BlockSize: byte; { should be 4 }
PackedFields: Byte;
DelayTime: Word; { in centiseconds }
TransparentColorIndex: Byte;
Terminator: Byte;
end;
TDisposalMethod = (dmNone, dmNoDisposal, dmBackground, dmPrevious);
TGIFGraphicControlExtension = class(TGIFExtension)
private
FGCExtension: TGIFGCERec;
protected
function GetExtensionType: TGIFExtensionType; override;
function GetTransparent: boolean;
procedure SetTransparent(Value: boolean);
function GetTransparentColor: TColor;
procedure SetTransparentColor(Color: TColor);
function GetTransparentColorIndex: BYTE;
procedure SetTransparentColorIndex(Value: BYTE);
function GetDelay: WORD;
procedure SetDelay(Value: WORD);
function GetUserInput: boolean;
procedure SetUserInput(Value: boolean);
function GetDisposal: TDisposalMethod;
procedure SetDisposal(Value: TDisposalMethod);
public
constructor Create(ASubImage: TGIFSubImage); override;
destructor Destroy; override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
property Delay: WORD read GetDelay write SetDelay;
property Transparent: boolean read GetTransparent write SetTransparent;
property TransparentColorIndex: BYTE read GetTransparentColorIndex
write SetTransparentColorIndex;
property TransparentColor: TColor read GetTransparentColor write SetTransparentColor;
property UserInput: boolean read GetUserInput write SetUserInput;
property Disposal: TDisposalMethod read GetDisposal write SetDisposal;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFTextExtension
//
////////////////////////////////////////////////////////////////////////////////
TGIFPlainTextExtensionRec = packed record
BlockSize: byte; { should be 12 }
Left, Top, Width, Height: Word;
CellWidth, CellHeight: Byte;
TextFGColorIndex,
TextBGColorIndex: Byte;
end;
TGIFTextExtension = class(TGIFExtension)
private
FText : TStrings;
FPlainTextExtension : TGIFPlainTextExtensionRec;
protected
function GetExtensionType: TGIFExtensionType; override;
function GetForegroundColor: TColor;
procedure SetForegroundColor(Color: TColor);
function GetBackgroundColor: TColor;
procedure SetBackgroundColor(Color: TColor);
function GetBounds(Index: integer): WORD;
procedure SetBounds(Index: integer; Value: WORD);
function GetCharWidthHeight(Index: integer): BYTE;
procedure SetCharWidthHeight(Index: integer; Value: BYTE);
function GetColorIndex(Index: integer): BYTE;
procedure SetColorIndex(Index: integer; Value: BYTE);
public
constructor Create(ASubImage: TGIFSubImage); override;
destructor Destroy; override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
property Left: WORD index 1 read GetBounds write SetBounds;
property Top: WORD index 2 read GetBounds write SetBounds;
property GridWidth: WORD index 3 read GetBounds write SetBounds;
property GridHeight: WORD index 4 read GetBounds write SetBounds;
property CharWidth: BYTE index 1 read GetCharWidthHeight write SetCharWidthHeight;
property CharHeight: BYTE index 2 read GetCharWidthHeight write SetCharWidthHeight;
property ForegroundColorIndex: BYTE index 1 read GetColorIndex write SetColorIndex;
property ForegroundColor: TColor read GetForegroundColor;
property BackgroundColorIndex: BYTE index 2 read GetColorIndex write SetColorIndex;
property BackgroundColor: TColor read GetBackgroundColor;
property Text: TStrings read FText write FText;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFCommentExtension
//
////////////////////////////////////////////////////////////////////////////////
TGIFCommentExtension = class(TGIFExtension)
private
FText : TStrings;
protected
function GetExtensionType: TGIFExtensionType; override;
public
constructor Create(ASubImage: TGIFSubImage); override;
destructor Destroy; override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
property Text: TStrings read FText;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFApplicationExtension
//
////////////////////////////////////////////////////////////////////////////////
TGIFIdentifierCode = array[0..7] of char;
TGIFAuthenticationCode = array[0..2] of char;
TGIFApplicationRec = packed record
Identifier : TGIFIdentifierCode;
Authentication : TGIFAuthenticationCode;
end;
TGIFApplicationExtension = class;
TGIFAppExtensionClass = class of TGIFApplicationExtension;
TGIFApplicationExtension = class(TGIFExtension)
private
FIdent : TGIFApplicationRec;
function GetAuthentication: string;
function GetIdentifier: string;
protected
function GetExtensionType: TGIFExtensionType; override;
procedure SetAuthentication(const Value: string);
procedure SetIdentifier(const Value: string);
procedure SaveData(Stream: TStream); virtual; abstract;
procedure LoadData(Stream: TStream); virtual; abstract;
public
constructor Create(ASubImage: TGIFSubImage); override;
destructor Destroy; override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
class procedure RegisterExtension(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass);
class function FindSubExtension(Stream: TStream): TGIFExtensionClass; override;
property Identifier: string read GetIdentifier write SetIdentifier;
property Authentication: string read GetAuthentication write SetAuthentication;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFUnknownAppExtension
//
////////////////////////////////////////////////////////////////////////////////
TGIFBlock = class(TObject)
private
FSize : BYTE;
FData : pointer;
public
constructor Create(ASize: integer);
destructor Destroy; override;
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
property Size: BYTE read FSize;
property Data: pointer read FData;
end;
TGIFUnknownAppExtension = class(TGIFApplicationExtension)
private
FBlocks : TList;
protected
procedure SaveData(Stream: TStream); override;
procedure LoadData(Stream: TStream); override;
public
constructor Create(ASubImage: TGIFSubImage); override;
destructor Destroy; override;
property Blocks: TList read FBlocks;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFAppExtNSLoop
//
////////////////////////////////////////////////////////////////////////////////
TGIFAppExtNSLoop = class(TGIFApplicationExtension)
private
FLoops : WORD;
FBufferSize : DWORD;
protected
procedure SaveData(Stream: TStream); override;
procedure LoadData(Stream: TStream); override;
public
constructor Create(ASubImage: TGIFSubImage); override;
property Loops: WORD read FLoops write FLoops;
property BufferSize: DWORD read FBufferSize write FBufferSize;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFImage
//
////////////////////////////////////////////////////////////////////////////////
TGIFImageList = class(TGIFList)
protected
function GetImage(Index: Integer): TGIFSubImage;
procedure SetImage(Index: Integer; SubImage: TGIFSubImage);
public
procedure LoadFromStream(Stream: TStream; Parent: TObject); override;
procedure SaveToStream(Stream: TStream); override;
property SubImages[Index: Integer]: TGIFSubImage read GetImage write SetImage; default;
end;
// Compression algorithms
TGIFCompression =
(gcLZW, // Normal LZW compression
gcRLE // GIF compatible RLE compression
);
// Color reduction methods
TColorReduction =
(rmNone, // Do not perform color reduction
rmWindows20, // Reduce to the Windows 20 color system palette
rmWindows256, // Reduce to the Windows 256 color halftone palette (Only works in 256 color display mode)
rmWindowsGray, // Reduce to the Windows 4 grayscale colors
rmMonochrome, // Reduce to a black/white monochrome palette
rmGrayScale, // Reduce to a uniform 256 shade grayscale palette
rmNetscape, // Reduce to the Netscape 216 color palette
rmQuantize, // Reduce to optimal 2^n color palette
rmQuantizeWindows, // Reduce to optimal 256 color windows palette
rmPalette // Reduce to custom palette
);
TDitherMode =
(dmNearest, // Nearest color matching w/o error correction
dmFloydSteinberg, // Floyd Steinberg Error Diffusion dithering
dmStucki, // Stucki Error Diffusion dithering
dmSierra, // Sierra Error Diffusion dithering
dmJaJuNI, // Jarvis, Judice & Ninke Error Diffusion dithering
dmSteveArche, // Stevenson & Arche Error Diffusion dithering
dmBurkes // Burkes Error Diffusion dithering
// dmOrdered, // Ordered dither
);
// Optimization options
TGIFOptimizeOption =
(ooCrop, // Crop animated GIF frames
ooMerge, // Merge pixels of same color
ooCleanup, // Remove comments and application extensions
ooColorMap, // Sort color map by usage and remove unused entries
ooReduceColors // Reduce color depth ***NOT IMPLEMENTED***
);
TGIFOptimizeOptions = set of TGIFOptimizeOption;
TGIFDrawOption =
(goAsync, // Asyncronous draws (paint in thread)
goTransparent, // Transparent draws
goAnimate, // Animate draws
goLoop, // Loop animations
goLoopContinously, // Ignore loop count and loop forever
goValidateCanvas, // Validate canvas in threaded paint ***NOT IMPLEMENTED***
goDirectDraw, // Draw() directly on canvas
goClearOnLoop, // Clear animation on loop
goTile, // Tiled display
goDither, // Dither to Netscape palette
goAutoDither // Only dither on 256 color systems
);
TGIFDrawOptions = set of TGIFDrawOption;
// Note: if goAsync is not set then goDirectDraw should be set. Otherwise
// the image will not be displayed.
PGIFPainter = ^TGIFPainter;
TGIFPainter = class(TThread)
private
FImage : TGIFImage; // The TGIFImage that owns this painter
FCanvas : TCanvas; // Destination canvas
FRect : TRect; // Destination rect
FDrawOptions : TGIFDrawOptions;// Paint options
FAnimationSpeed : integer; // Animation speed %
FActiveImage : integer; // Current frame
Disposal , // Used by synchronized paint
OldDisposal : TDisposalMethod;// Used by synchronized paint
BackupBuffer : TBitmap; // Used by synchronized paint
FrameBuffer : TBitmap; // Used by synchronized paint
Background : TBitmap; // Used by synchronized paint
ValidateDC : HDC;
DoRestart : boolean; // Flag used to restart animation
FStarted : boolean; // Flag used to signal start of paint
PainterRef : PGIFPainter; // Pointer to var referencing painter
FEventHandle : THandle; // Animation delay event
ExceptObject : Exception; // Eaten exception
ExceptAddress : pointer; // Eaten exceptions address
FEvent : TNotifyEvent; // Used by synchronized events
FOnStartPaint : TNotifyEvent;
FOnPaint : TNotifyEvent;
FOnAfterPaint : TNotifyEvent;
FOnLoop : TNotifyEvent;
FOnEndPaint : TNotifyEvent;
procedure DoOnTerminate(Sender: TObject);// Sync. shutdown procedure
procedure DoSynchronize(Method: TThreadMethod);// Conditional sync stub
{$ifdef SERIALIZE_RENDER}
procedure PrefetchBitmap; // Sync. bitmap prefetch
{$endif}
procedure DoPaintFrame; // Sync. buffered paint procedure
procedure DoPaint; // Sync. paint procedure
procedure DoEvent;
procedure SetActiveImage(const Value: integer);// Sync. event procedure
protected
procedure Execute; override;
procedure SetAnimationSpeed(Value: integer);
public
constructor Create(AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect;
Options: TGIFDrawOptions);
constructor CreateRef(Painter: PGIFPainter; AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect;
Options: TGIFDrawOptions);
destructor Destroy; override;
procedure Start;
procedure Stop;
procedure Restart;
property Image: TGIFImage read FImage;
property Canvas: TCanvas read FCanvas;
property Rect: TRect read FRect write FRect;
property DrawOptions: TGIFDrawOptions read FDrawOptions write FDrawOptions;
property AnimationSpeed: integer read FAnimationSpeed write SetAnimationSpeed;
property Started: boolean read FStarted;
property ActiveImage: integer read FActiveImage write SetActiveImage;
property OnStartPaint: TNotifyEvent read FOnStartPaint write FOnStartPaint;
property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
property OnAfterPaint: TNotifyEvent read FOnAfterPaint write FOnAfterPaint;
property OnLoop: TNotifyEvent read FOnLoop write FOnLoop;
property OnEndPaint : TNotifyEvent read FOnEndPaint write FOnEndPaint ;
property EventHandle: THandle read FEventHandle;
end;
TGIFWarning = procedure(Sender: TObject; Severity: TGIFSeverity; Message: string) of object;
TGIFImage = class(TGraphic)
private
IsDrawing : Boolean;
IsInsideGetPalette : boolean;
FImages : TGIFImageList;
FHeader : TGIFHeader;
FGlobalPalette : HPalette;
FPainters : TThreadList;
FDrawOptions : TGIFDrawOptions;
FColorReduction : TColorReduction;
FReductionBits : integer;
FDitherMode : TDitherMode;
FCompression : TGIFCompression;
FOnWarning : TGIFWarning;
FBitmap : TBitmap;
FDrawPainter : TGIFPainter;
FThreadPriority : TThreadPriority;
FAnimationSpeed : integer;
FDrawBackgroundColor: TColor;
FOnStartPaint : TNotifyEvent;
FOnPaint : TNotifyEvent;
FOnAfterPaint : TNotifyEvent;
FOnLoop : TNotifyEvent;
FOnEndPaint : TNotifyEvent;
{$IFDEF VER9x}
FPaletteModified : Boolean;
FOnProgress : TProgressEvent;
{$ENDIF}
function GetAnimate: Boolean; // 2002.07.07
procedure SetAnimate(const Value: Boolean); // 2002.07.07
protected
// Obsolete: procedure Changed(Sender: TObject); {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF}
function GetHeight: Integer; override;
procedure SetHeight(Value: Integer); override;
function GetWidth: Integer; override;
procedure SetWidth(Value: Integer); override;
procedure AssignTo(Dest: TPersistent); override;
function InternalPaint(Painter: PGIFPainter; ACanvas: TCanvas; const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter;
procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
function Equals(Graphic: TGraphic): Boolean; override;
function GetPalette: HPALETTE; {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF}
procedure SetPalette(Value: HPalette); {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF}
function GetEmpty: Boolean; override;
procedure WriteData(Stream: TStream); override;
function GetIsTransparent: Boolean;
function GetVersion: TGIFVersion;
function GetColorResolution: integer;
function GetBitsPerPixel: integer;
function GetBackgroundColorIndex: BYTE;
procedure SetBackgroundColorIndex(const Value: BYTE);
function GetBackgroundColor: TColor;
procedure SetBackgroundColor(const Value: TColor);
function GetAspectRatio: BYTE;
procedure SetAspectRatio(const Value: BYTE);
procedure SetDrawOptions(Value: TGIFDrawOptions);
procedure SetAnimationSpeed(Value: integer);
procedure SetReductionBits(Value: integer);
procedure NewImage;
function GetBitmap: TBitmap;
function NewBitmap: TBitmap;
procedure FreeBitmap;
function GetColorMap: TGIFColorMap;
function GetDoDither: boolean;
property DrawPainter: TGIFPainter read FDrawPainter; // Extremely volatile
property DoDither: boolean read GetDoDither;
{$IFDEF VER9x}
procedure Progress(Sender: TObject; Stage: TProgressStage;
PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string); dynamic;
{$ENDIF}
public
constructor Create; override;
destructor Destroy; override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
procedure LoadFromResourceName(Instance: THandle; const ResName: String); // 2002.07.07
function Add(Source: TPersistent): integer;
procedure Pack;
procedure OptimizeColorMap;
procedure Optimize(Options: TGIFOptimizeOptions;
ColorReduction: TColorReduction; DitherMode: TDitherMode;
ReductionBits: integer);
procedure Clear;
procedure StopDraw;
function Paint(ACanvas: TCanvas; const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter;
procedure PaintStart;
procedure PaintPause;
procedure PaintStop;
procedure PaintResume;
procedure PaintRestart;
procedure Warning(Sender: TObject; Severity: TGIFSeverity; Message: string); virtual;
procedure Assign(Source: TPersistent); override;
procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle;
APalette: HPALETTE); override;
procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
var APalette: HPALETTE); override;
property GlobalColorMap: TGIFColorMap read GetColorMap;
property Version: TGIFVersion read GetVersion;
property Images: TGIFImageList read FImages;
property ColorResolution: integer read GetColorResolution;
property BitsPerPixel: integer read GetBitsPerPixel;
property BackgroundColorIndex: BYTE read GetBackgroundColorIndex write SetBackgroundColorIndex;
property BackgroundColor: TColor read GetBackgroundColor write SetBackgroundColor;
property AspectRatio: BYTE read GetAspectRatio write SetAspectRatio;
property Header: TGIFHeader read FHeader; // ***OBSOLETE***
property IsTransparent: boolean read GetIsTransparent;
property DrawOptions: TGIFDrawOptions read FDrawOptions write SetDrawOptions;
property DrawBackgroundColor: TColor read FDrawBackgroundColor write FDrawBackgroundColor;
property ColorReduction: TColorReduction read FColorReduction write FColorReduction;
property ReductionBits: integer read FReductionBits write SetReductionBits;
property DitherMode: TDitherMode read FDitherMode write FDitherMode;
property Compression: TGIFCompression read FCompression write FCompression;
property AnimationSpeed: integer read FAnimationSpeed write SetAnimationSpeed;
property Animate: Boolean read GetAnimate write SetAnimate; // 2002.07.07
property Painters: TThreadList read FPainters;
property ThreadPriority: TThreadPriority read FThreadPriority write FThreadPriority;
property Bitmap: TBitmap read GetBitmap; // Volatile - beware!
property OnWarning: TGIFWarning read FOnWarning write FOnWarning;
property OnStartPaint: TNotifyEvent read FOnStartPaint write FOnStartPaint;
property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
property OnAfterPaint: TNotifyEvent read FOnAfterPaint write FOnAfterPaint;
property OnLoop: TNotifyEvent read FOnLoop write FOnLoop;
property OnEndPaint : TNotifyEvent read FOnEndPaint write FOnEndPaint ;
{$IFDEF VER9x}
property Palette: HPALETTE read GetPalette write SetPalette;
property PaletteModified: Boolean read FPaletteModified write FPaletteModified;
property OnProgress: TProgressEvent read FOnProgress write FOnProgress;
{$ENDIF}
end;
////////////////////////////////////////////////////////////////////////////////
//
// Utility routines
//
////////////////////////////////////////////////////////////////////////////////
// WebPalette creates a 216 color uniform palette a.k.a. the Netscape Palette
function WebPalette: HPalette;
// ReduceColors
// Map colors in a bitmap to their nearest representation in a palette using
// the methods specified by the ColorReduction and DitherMode parameters.
// The ReductionBits parameter specifies the desired number of colors (bits
// per pixel) when the reduction method is rmQuantize. The CustomPalette
// specifies the palette when the rmPalette reduction method is used.
function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction;
DitherMode: TDitherMode; ReductionBits: integer; CustomPalette: hPalette): TBitmap;
// CreateOptimizedPaletteFromManyBitmaps
//: Performs Color Quantization on multiple bitmaps.
// The Bitmaps parameter is a list of bitmaps. Returns an optimized palette.
function CreateOptimizedPaletteFromManyBitmaps(Bitmaps: TList; Colors, ColorBits: integer;
Windows: boolean): hPalette;
{$IFDEF VER9x}
// From Delphi 3 graphics.pas
type
TPixelFormat = (pfDevice, pf1bit, pf4bit, pf8bit, pf15bit, pf16bit, pf24bit, pf32bit, pfCustom);
{$ENDIF}
procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: Integer;
var ImageSize: longInt; PixelFormat: TPixelFormat);
function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE;
var BitmapInfo; var Bits; PixelFormat: TPixelFormat): Boolean;
////////////////////////////////////////////////////////////////////////////////
//
// Global variables
//
////////////////////////////////////////////////////////////////////////////////
// GIF Clipboard format identifier for use by LoadFromClipboardFormat and
// SaveToClipboardFormat.
// Set in Initialization section.
var
CF_GIF: WORD;
////////////////////////////////////////////////////////////////////////////////
//
// Library defaults
//
////////////////////////////////////////////////////////////////////////////////
var
//: Default options for TGIFImage.DrawOptions.
GIFImageDefaultDrawOptions : TGIFDrawOptions =
[goAsync, goLoop, goTransparent, goAnimate, goDither, goAutoDither
{$IFDEF STRICT_MOZILLA}
,goClearOnLoop
{$ENDIF}
];
// WARNING! Do not use goAsync and goDirectDraw unless you have absolute
// control of the destination canvas.
// TGIFPainter will continue to write on the canvas even after the canvas has
// been deleted, unless *you* prevent it.
// The goValidateCanvas option will fix this problem if it is ever implemented.
//: Default color reduction methods for bitmap import.
// These are the fastest settings, but also the ones that gives the
// worst result (in most cases).
GIFImageDefaultColorReduction: TColorReduction = rmNetscape;
GIFImageDefaultColorReductionBits: integer = 8; // Range 3 - 8
GIFImageDefaultDitherMode: TDitherMode = dmNearest;
//: Default encoder compression method.
GIFImageDefaultCompression: TGIFCompression = gcLZW;
//: Default painter thread priority
GIFImageDefaultThreadPriority: TThreadPriority = tpNormal;
//: Default animation speed in % of normal speed (range 0 - 1000)
GIFImageDefaultAnimationSpeed: integer = 100;
// DoAutoDither is set to True in the initializaion section if the desktop DC
// supports 256 colors or less.
// It can be modified in your application to disable/enable Auto Dithering
DoAutoDither: boolean = False;
// Palette is set to True in the initialization section if the desktop DC
// supports 256 colors or less.
// You should NOT modify it.
PaletteDevice: boolean = False;
// Set GIFImageRenderOnLoad to True to render (convert to bitmap) the
// GIF frames as they are loaded instead of rendering them on-demand.
// This might increase resource consumption and will increase load time,
// but will cause animated GIFs to display more smoothly.
GIFImageRenderOnLoad: boolean = False;
// If GIFImageOptimizeOnStream is true, the GIF will be optimized
// before it is streamed to the DFM file.
// This will not affect TGIFImage.SaveToStream or SaveToFile.
GIFImageOptimizeOnStream: boolean = False;
////////////////////////////////////////////////////////////////////////////////
//
// Design Time support
//
////////////////////////////////////////////////////////////////////////////////
// Dummy component registration for design time support of GIFs in TImage
procedure Register;
////////////////////////////////////////////////////////////////////////////////
//
// Error messages
//
////////////////////////////////////////////////////////////////////////////////
{$ifndef VER9x}
resourcestring
{$else}
const
{$endif}
// GIF Error messages
sOutOfData = 'Premature end of data';
sTooManyColors = 'Color table overflow';
sBadColorIndex = 'Invalid color index';
sBadVersion = 'Unsupported GIF version';
sBadSignature = 'Invalid GIF signature';
sScreenBadColorSize = 'Invalid number of colors specified in Screen Descriptor';
sImageBadColorSize = 'Invalid number of colors specified in Image Descriptor';
sUnknownExtension = 'Unknown extension type';
sBadExtensionLabel = 'Invalid extension introducer';
sOutOfMemDIB = 'Failed to allocate memory for GIF DIB';
sDIBCreate = 'Failed to create DIB from Bitmap';
sDecodeTooFewBits = 'Decoder bit buffer under-run';
sDecodeCircular = 'Circular decoder table entry';
sBadTrailer = 'Invalid Image trailer';
sBadExtensionInstance = 'Internal error: Extension Instance does not match Extension Label';
sBadBlockSize = 'Unsupported Application Extension block size';
sBadBlock = 'Unknown GIF block type';
sUnsupportedClass = 'Object type not supported for operation';
sInvalidData = 'Invalid GIF data';
sBadHeight = 'Image height too small for contained frames';
sBadWidth = 'Image width too small for contained frames';
{$IFNDEF REGISTER_TGIFIMAGE}
sGIFToClipboard = 'Clipboard operations not supported for GIF objects';
{$ELSE}
sFailedPaste = 'Failed to store GIF on clipboard';
{$IFDEF VER9x}
sUnknownClipboardFormat= 'Unsupported clipboard format';
{$ENDIF}
{$ENDIF}
sScreenSizeExceeded = 'Image exceeds Logical Screen size';
sNoColorTable = 'No global or local color table defined';
sBadPixelCoordinates = 'Invalid pixel coordinates';
sUnsupportedBitmap = 'Unsupported bitmap format';
sInvalidPixelFormat = 'Unsupported PixelFormat';
sBadDimension = 'Invalid image dimensions';
sNoDIB = 'Image has no DIB';
sInvalidStream = 'Invalid stream operation';
sInvalidColor = 'Color not in color table';
sInvalidBitSize = 'Invalid Bits Per Pixel value';
sEmptyColorMap = 'Color table is empty';
sEmptyImage = 'Image is empty';
sInvalidBitmapList = 'Invalid bitmap list';
sInvalidReduction = 'Invalid reduction method';
{$IFDEF VER9x}
// From Delphi 3 consts.pas
SOutOfResources = 'Out of system resources';
SInvalidBitmap = 'Bitmap image is not valid';
SScanLine = 'Scan line index out of range';
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
//
// Misc texts
//
////////////////////////////////////////////////////////////////////////////////
// File filter name
sGIFImageFile = 'GIF Image';
// Progress messages
sProgressLoading = 'Loading...';
sProgressSaving = 'Saving...';
sProgressConverting = 'Converting...';
sProgressRendering = 'Rendering...';
sProgressCopying = 'Copying...';
sProgressOptimizing = 'Optimizing...';
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Implementation
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
implementation
{ This makes me long for the C preprocessor... }
{$ifdef DEBUG}
{$ifdef DEBUG_COMPRESSPERFORMANCE}
{$define DEBUG_PERFORMANCE}
{$else}
{$ifdef DEBUG_DECOMPRESSPERFORMANCE}
{$define DEBUG_PERFORMANCE}
{$else}
{$ifdef DEBUG_DITHERPERFORMANCE}
{$define DEBUG_PERFORMANCE}
{$else}
{$ifdef DEBUG_DITHERPERFORMANCE}
{$define DEBUG_PERFORMANCE}
{$else}
{$ifdef DEBUG_DRAWPERFORMANCE}
{$define DEBUG_PERFORMANCE}
{$else}
{$ifdef DEBUG_RENDERPERFORMANCE}
{$define DEBUG_PERFORMANCE}
{$endif}
{$endif}
{$endif}
{$endif}
{$endif}
{$endif}
{$endif}
uses
{$ifdef DEBUG}
dialogs,
{$endif}
mmsystem, // timeGetTime()
messages,
Consts;
////////////////////////////////////////////////////////////////////////////////
//
// Misc consts
//
////////////////////////////////////////////////////////////////////////////////
const
{ Extension/block label values }
bsPlainTextExtension = $01;
bsGraphicControlExtension = $F9;
bsCommentExtension = $FE;
bsApplicationExtension = $FF;
bsImageDescriptor = Ord(',');
bsExtensionIntroducer = Ord('!');
bsTrailer = ord(';');
// Thread messages - Used by TThread.Synchronize()
CM_DESTROYWINDOW = $8FFE; // Defined in classes.pas
CM_EXECPROC = $8FFF; // Defined in classes.pas
////////////////////////////////////////////////////////////////////////////////
//
// Design Time support
//
////////////////////////////////////////////////////////////////////////////////
//: Dummy component registration to add design-time support of GIFs to TImage.
// Since TGIFImage isn't a component there's nothing to register here, but
// since Register is only called at design time we can set the design time
// GIF paint options here (modify as you please):
procedure Register;
begin
// Don't loop animations at design-time. Animated GIFs will animate once and
// then stop thus not using CPU resources and distracting the developer.
Exclude(GIFImageDefaultDrawOptions, goLoop);
end;
////////////////////////////////////////////////////////////////////////////////
//
// Utilities
//
////////////////////////////////////////////////////////////////////////////////
//: Creates a 216 color uniform non-dithering Netscape palette.
function WebPalette: HPalette;
type
TLogWebPalette = packed record
palVersion : word;
palNumEntries : word;
PalEntries : array[0..5,0..5,0..5] of TPaletteEntry;
end;
var
r, g, b : byte;
LogWebPalette : TLogWebPalette;
LogPalette : TLogpalette absolute LogWebPalette; // Stupid typecast
begin
with LogWebPalette do
begin
palVersion:= $0300;
palNumEntries:= 216;
for r:=0 to 5 do
for g:=0 to 5 do
for b:=0 to 5 do
begin
with PalEntries[r,g,b] do
begin
peRed := 51 * r;
peGreen := 51 * g;
peBlue := 51 * b;
peFlags := 0;
end;
end;
end;
Result := CreatePalette(Logpalette);
end;
(*
** GDI Error handling
** Adapted from graphics.pas
*)
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
{$ifdef D3_BCB3}
function GDICheck(Value: Integer): Integer;
{$else}
function GDICheck(Value: Cardinal): Cardinal;
{$endif}
var
ErrorCode : integer;
Buf : array [byte] of char;
function ReturnAddr: Pointer;
// From classes.pas
asm
MOV EAX,[EBP+4] // sysutils.pas says [EBP-4], but this works !
end;
begin
if (Value = 0) then
begin
ErrorCode := GetLastError;
if (ErrorCode <> 0) and (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil,
ErrorCode, LOCALE_USER_DEFAULT, Buf, sizeof(Buf), nil) <> 0) then
raise EOutOfResources.Create(Buf) at ReturnAddr
else
raise EOutOfResources.Create(SOutOfResources) at ReturnAddr;
end;
Result := Value;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
(*
** Raise error condition
*)
procedure Error(msg: string);
function ReturnAddr: Pointer;
// From classes.pas
asm
MOV EAX,[EBP+4] // sysutils.pas says [EBP-4] !
end;
begin
raise GIFException.Create(msg) at ReturnAddr;
end;
(*
** Return number bytes required to
** hold a given number of bits.
*)
function ByteAlignBit(Bits: Cardinal): Cardinal;
begin
Result := (Bits+7) SHR 3;
end;
// Rounded up to nearest 2
function WordAlignBit(Bits: Cardinal): Cardinal;
begin
Result := ((Bits+15) SHR 4) SHL 1;
end;
// Rounded up to nearest 4
function DWordAlignBit(Bits: Cardinal): Cardinal;
begin
Result := ((Bits+31) SHR 5) SHL 2;
end;
// Round to arbitrary number of bits
function AlignBit(Bits, BitsPerPixel, Alignment: Cardinal): Cardinal;
begin
Dec(Alignment);
Result := ((Bits * BitsPerPixel) + Alignment) and not Alignment;
Result := Result SHR 3;
end;
(*
** Compute Bits per Pixel from Number of Colors
** (Return the ceiling log of n)
*)
function Colors2bpp(Colors: integer): integer;
var
MaxColor : integer;
begin
(*
** This might be faster computed by multiple if then else statements
*)
if (Colors = 0) then
Result := 0
else
begin
Result := 1;
MaxColor := 2;
while (Colors > MaxColor) do
begin
inc(Result);
MaxColor := MaxColor SHL 1;
end;
end;
end;
(*
** Write an ordinal byte value to a stream
*)
procedure WriteByte(Stream: TStream; b: BYTE);
begin
Stream.Write(b, 1);
end;
(*
** Read an ordinal byte value from a stream
*)
function ReadByte(Stream: TStream): BYTE;
begin
Stream.Read(Result, 1);
end;
(*
** Read data from stream and raise exception of EOF
*)
procedure ReadCheck(Stream: TStream; var Buffer; Size: LongInt);
var
ReadSize : integer;
begin
ReadSize := Stream.Read(Buffer, Size);
if (ReadSize <> Size) then
Error(sOutOfData);
end;
(*
** Write a string list to a stream as multiple blocks
** of max 255 characters in each.
*)
procedure WriteStrings(Stream: TStream; Text: TStrings);
var
i : integer;
b : BYTE;
size : integer;
s : string;
begin
for i := 0 to Text.Count-1 do
begin
s := Text[i];
size := length(s);
if (size > 255) then
b := 255
else
b := size;
while (size > 0) do
begin
dec(size, b);
WriteByte(Stream, b);
Stream.Write(PChar(s)^, b);
delete(s, 1, b);
if (b > size) then
b := size;
end;
end;
// Terminating zero (length = 0)
WriteByte(Stream, 0);
end;
(*
** Read a string list from a stream as multiple blocks
** of max 255 characters in each.
*)
{ TODO -oanme -cImprovement : Replace ReadStrings with TGIFReader. }
procedure ReadStrings(Stream: TStream; Text: TStrings);
var
size : BYTE;
buf : array[0..255] of char;
begin
Text.Clear;
if (Stream.Read(size, 1) <> 1) then
exit;
while (size > 0) do
begin
ReadCheck(Stream, buf, size);
buf[size] := #0;
Text.Add(Buf);
if (Stream.Read(size, 1) <> 1) then
exit;
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// Delphi 2.x / C++ Builder 1.x support
//
////////////////////////////////////////////////////////////////////////////////
{$IFDEF VER9x}
var
// From Delphi 3 graphics.pas
SystemPalette16: HPalette; // 16 color palette that maps to the system palette
type
TPixelFormats = set of TPixelFormat;
const
// Only pf1bit, pf4bit and pf8bit is supported since they are the only ones
// with palettes
SupportedPixelformats: TPixelFormats = [pf1bit, pf4bit, pf8bit];
{$ENDIF}
// --------------------------
// InitializeBitmapInfoHeader
// --------------------------
// Fills a TBitmapInfoHeader with the values of a bitmap when converted to a
// DIB of a specified PixelFormat.
//
// Parameters:
// Bitmap The handle of the source bitmap.
// Info The TBitmapInfoHeader buffer that will receive the values.
// PixelFormat The pixel format of the destination DIB.
//
{$IFDEF BAD_STACK_ALIGNMENT}
// Disable optimization to circumvent optimizer bug...
{$IFOPT O+}
{$DEFINE O_PLUS}
{$O-}
{$ENDIF}
{$ENDIF}
procedure InitializeBitmapInfoHeader(Bitmap: HBITMAP; var Info: TBitmapInfoHeader;
PixelFormat: TPixelFormat);
// From graphics.pas, "optimized" for our use
var
DIB : TDIBSection;
Bytes : Integer;
begin
DIB.dsbmih.biSize := 0;
Bytes := GetObject(Bitmap, SizeOf(DIB), @DIB);
if (Bytes = 0) then
Error(sInvalidBitmap);
if (Bytes >= (sizeof(DIB.dsbm) + sizeof(DIB.dsbmih))) and
(DIB.dsbmih.biSize >= sizeof(DIB.dsbmih)) then
Info := DIB.dsbmih
else
begin
FillChar(Info, sizeof(Info), 0);
with Info, DIB.dsbm do
begin
biSize := SizeOf(Info);
biWidth := bmWidth;
biHeight := bmHeight;
end;
end;
case PixelFormat of
pf1bit: Info.biBitCount := 1;
pf4bit: Info.biBitCount := 4;
pf8bit: Info.biBitCount := 8;
pf24bit: Info.biBitCount := 24;
else
Error(sInvalidPixelFormat);
// Info.biBitCount := DIB.dsbm.bmBitsPixel * DIB.dsbm.bmPlanes;
end;
Info.biPlanes := 1;
Info.biCompression := BI_RGB; // Always return data in RGB format
Info.biSizeImage := AlignBit(Info.biWidth, Info.biBitCount, 32) * Cardinal(abs(Info.biHeight));
end;
{$IFDEF O_PLUS}
{$O+}
{$UNDEF O_PLUS}
{$ENDIF}
// -------------------
// InternalGetDIBSizes
// -------------------
// Calculates the buffer sizes nescessary for convertion of a bitmap to a DIB
// of a specified PixelFormat.
// See the GetDIBSizes API function for more info.
//
// Parameters:
// Bitmap The handle of the source bitmap.
// InfoHeaderSize
// The returned size of a buffer that will receive the DIB's
// TBitmapInfo structure.
// ImageSize The returned size of a buffer that will receive the DIB's
// pixel data.
// PixelFormat The pixel format of the destination DIB.
//
procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: Integer;
var ImageSize: longInt; PixelFormat: TPixelFormat);
// From graphics.pas, "optimized" for our use
var
Info : TBitmapInfoHeader;
begin
InitializeBitmapInfoHeader(Bitmap, Info, PixelFormat);
// Check for palette device format
if (Info.biBitCount > 8) then
begin
// Header but no palette
InfoHeaderSize := SizeOf(TBitmapInfoHeader);
if ((Info.biCompression and BI_BITFIELDS) <> 0) then
Inc(InfoHeaderSize, 12);
end else
// Header and palette
InfoHeaderSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * (1 shl Info.biBitCount);
ImageSize := Info.biSizeImage;
end;
// --------------
// InternalGetDIB
// --------------
// Converts a bitmap to a DIB of a specified PixelFormat.
//
// Parameters:
// Bitmap The handle of the source bitmap.
// Pal The handle of the source palette.
// BitmapInfo The buffer that will receive the DIB's TBitmapInfo structure.
// A buffer of sufficient size must have been allocated prior to
// calling this function.
// Bits The buffer that will receive the DIB's pixel data.
// A buffer of sufficient size must have been allocated prior to
// calling this function.
// PixelFormat The pixel format of the destination DIB.
//
// Returns:
// True on success, False on failure.
//
// Note: The InternalGetDIBSizes function can be used to calculate the
// nescessary sizes of the BitmapInfo and Bits buffers.
//
function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE;
var BitmapInfo; var Bits; PixelFormat: TPixelFormat): Boolean;
// From graphics.pas, "optimized" for our use
var
OldPal : HPALETTE;
DC : HDC;
begin
InitializeBitmapInfoHeader(Bitmap, TBitmapInfoHeader(BitmapInfo), PixelFormat);
OldPal := 0;
DC := CreateCompatibleDC(0);
try
if (Palette <> 0) then
begin
OldPal := SelectPalette(DC, Palette, False);
RealizePalette(DC);
end;
Result := (GetDIBits(DC, Bitmap, 0, abs(TBitmapInfoHeader(BitmapInfo).biHeight),
@Bits, TBitmapInfo(BitmapInfo), DIB_RGB_COLORS) <> 0);
finally
if (OldPal <> 0) then
SelectPalette(DC, OldPal, False);
DeleteDC(DC);
end;
end;
// ----------
// DIBFromBit
// ----------
// Converts a bitmap to a DIB of a specified PixelFormat.
// The DIB is returned in a TMemoryStream ready for streaming to a BMP file.
//
// Note: As opposed to D2's DIBFromBit function, the returned stream also
// contains a TBitmapFileHeader at offset 0.
//
// Parameters:
// Stream The TMemoryStream used to store the bitmap data.
// The stream must be allocated and freed by the caller prior to
// calling this function.
// Src The handle of the source bitmap.
// Pal The handle of the source palette.
// PixelFormat The pixel format of the destination DIB.
// DIBHeader A pointer to the DIB's TBitmapInfo (or TBitmapInfoHeader)
// structure in the memory stream.
// The size of the structure can either be deduced from the
// pixel format (i.e. number of colors) or calculated by
// subtracting the DIBHeader pointer from the DIBBits pointer.
// DIBBits A pointer to the DIB's pixel data in the memory stream.
//
procedure DIBFromBit(Stream: TMemoryStream; Src: HBITMAP;
Pal: HPALETTE; PixelFormat: TPixelFormat; var DIBHeader, DIBBits: Pointer);
// (From D2 graphics.pas, "optimized" for our use)
var
HeaderSize : integer;
FileSize : longInt;
ImageSize : longInt;
BitmapFileHeader : PBitmapFileHeader;
begin
if (Src = 0) then
Error(sInvalidBitmap);
// Get header- and pixel data size for new pixel format
InternalGetDIBSizes(Src, HeaderSize, ImageSize, PixelFormat);
// Make room in stream for a TBitmapInfo and pixel data
FileSize := sizeof(TBitmapFileHeader) + HeaderSize + ImageSize;
Stream.SetSize(FileSize);
// Get pointer to TBitmapFileHeader
BitmapFileHeader := Stream.Memory;
// Get pointer to TBitmapInfo
DIBHeader := Pointer(Longint(BitmapFileHeader) + sizeof(TBitmapFileHeader));
// Get pointer to pixel data
DIBBits := Pointer(Longint(DIBHeader) + HeaderSize);
// Initialize file header
FillChar(BitmapFileHeader^, sizeof(TBitmapFileHeader), 0);
with BitmapFileHeader^ do
begin
bfType := $4D42; // 'BM' = Windows BMP signature
bfSize := FileSize; // File size (not needed)
bfOffBits := sizeof(TBitmapFileHeader) + HeaderSize; // Offset of pixel data
end;
// Get pixel data in new pixel format
InternalGetDIB(Src, Pal, DIBHeader^, DIBBits^, PixelFormat);
end;
// --------------
// GetPixelFormat
// --------------
// Returns the current pixel format of a bitmap.
//
// Replacement for delphi 3 TBitmap.PixelFormat getter.
//
// Parameters:
// Bitmap The bitmap which pixel format is returned.
//
// Returns:
// The PixelFormat of the bitmap
//
function GetPixelFormat(Bitmap: TBitmap): TPixelFormat;
{$IFDEF VER9x}
// From graphics.pas, "optimized" for our use
var
DIBSection : TDIBSection;
Bytes : Integer;
Handle : HBitmap;
begin
Result := pfCustom; // This value is never returned
// BAD_STACK_ALIGNMENT
// Note: To work around an optimizer bug, we do not use Bitmap.Handle
// directly. Instead we store the value and use it indirectly. Unless we do
// this, the register containing Bitmap.Handle will be overwritten!
Handle := Bitmap.Handle;
if (Handle <> 0) then
begin
Bytes := GetObject(Handle, SizeOf(DIBSection), @DIBSection);
if (Bytes = 0) then
Error(sInvalidBitmap);
with (DIBSection) do
begin
// Check for NT bitmap
if (Bytes < (SizeOf(dsbm) + SizeOf(dsbmih))) or (dsbmih.biSize < SizeOf(dsbmih)) then
DIBSection.dsBmih.biBitCount := dsbm.bmBitsPixel * dsbm.bmPlanes;
case (dsBmih.biBitCount) of
0: Result := pfDevice;
1: Result := pf1bit;
4: Result := pf4bit;
8: Result := pf8bit;
16: case (dsBmih.biCompression) of
BI_RGB:
Result := pf15Bit;
BI_BITFIELDS:
if (dsBitFields[1] = $07E0) then
Result := pf16Bit;
end;
24: Result := pf24Bit;
32: if (dsBmih.biCompression = BI_RGB) then
Result := pf32Bit;
else
Error(sUnsupportedBitmap);
end;
end;
end else
// Result := pfDevice;
Error(sUnsupportedBitmap);
end;
{$ELSE}
begin
Result := Bitmap.PixelFormat;
end;
{$ENDIF}
// --------------
// SetPixelFormat
// --------------
// Changes the pixel format of a TBitmap.
//
// Replacement for delphi 3 TBitmap.PixelFormat setter.
// The returned TBitmap will always be a DIB.
//
// Note: Under Delphi 3.x this function will leak a palette handle each time it
// converts a TBitmap to pf8bit format!
// If possible, use SafeSetPixelFormat instead to avoid this.
//
// Parameters:
// Bitmap The bitmap to modify.
// PixelFormat The pixel format to convert to.
//
procedure SetPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat);
{$IFDEF VER9x}
var
Stream : TMemoryStream;
Header ,
Bits : Pointer;
begin
// Can't change anything without a handle
if (Bitmap.Handle = 0) then
Error(sInvalidBitmap);
// Only convert to supported formats
if not(PixelFormat in SupportedPixelformats) then
Error(sInvalidPixelFormat);
// No need to convert to same format
if (GetPixelFormat(Bitmap) = PixelFormat) then
exit;
Stream := TMemoryStream.Create;
try
// Convert to DIB file in memory stream
DIBFromBit(Stream, Bitmap.Handle, Bitmap.Palette, PixelFormat, Header, Bits);
// Load DIB from stream
Stream.Position := 0;
Bitmap.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
{$ELSE}
begin
Bitmap.PixelFormat := PixelFormat;
end;
{$ENDIF}
{$IFDEF VER100}
var
pf8BitBitmap: TBitmap = nil;
{$ENDIF}
// ------------------
// SafeSetPixelFormat
// ------------------
// Changes the pixel format of a TBitmap but doesn't preserve the contents.
//
// Replacement for Delphi 3 TBitmap.PixelFormat setter.
// The returned TBitmap will always be an empty DIB of the same size as the
// original bitmap.
//
// This function is used to avoid the palette handle leak that Delphi 3's
// SetPixelFormat and TBitmap.PixelFormat suffers from.
//
// Parameters:
// Bitmap The bitmap to modify.
// PixelFormat The pixel format to convert to.
//
procedure SafeSetPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat);
{$IFDEF VER9x}
begin
SetPixelFormat(Bitmap, PixelFormat);
end;
{$ELSE}
{$IFNDEF VER100}
var
Palette : hPalette;
begin
Bitmap.PixelFormat := PixelFormat;
// Work around a bug in TBitmap:
// When converting to pf8bit format, the palette assigned to TBitmap.Palette
// will be a half tone palette (which only contains the 20 system colors).
// Unfortunately this is not the palette used to render the bitmap and it
// is also not the palette saved with the bitmap.
if (PixelFormat = pf8bit) then
begin
// Disassociate the wrong palette from the bitmap (without affecting
// the DIB color table)
Palette := Bitmap.ReleasePalette;
if (Palette <> 0) then
DeleteObject(Palette);
// Recreate the palette from the DIB color table
Bitmap.Palette;
end;
end;
{$ELSE}
var
Width ,
Height : integer;
begin
if (PixelFormat = pf8bit) then
begin
// Partial solution to "TBitmap.PixelFormat := pf8bit" leak
// by Greg Chapman <glc@well.com>
if (pf8BitBitmap = nil) then
begin
// Create a "template" bitmap
// The bitmap is deleted in the finalization section of the unit.
pf8BitBitmap:= TBitmap.Create;
// Convert template to pf8bit format
// This will leak 1 palette handle, but only once
pf8BitBitmap.PixelFormat:= pf8Bit;
end;
// Store the size of the original bitmap
Width := Bitmap.Width;
Height := Bitmap.Height;
// Convert to pf8bit format by copying template
Bitmap.Assign(pf8BitBitmap);
// Restore the original size
Bitmap.Width := Width;
Bitmap.Height := Height;
end else
// This is safe since only pf8bit leaks
Bitmap.PixelFormat := PixelFormat;
end;
{$ENDIF}
{$ENDIF}
{$IFDEF VER9x}
// -----------
// CopyPalette
// -----------
// Copies a HPALETTE.
//
// Copied from D3 graphics.pas.
// This is declared private in some old versions of Delphi 2 so we have to
// implement it here to support those old versions.
//
// Parameters:
// Palette The palette to copy.
//
// Returns:
// The handle to a new palette.
//
function CopyPalette(Palette: HPALETTE): HPALETTE;
var
PaletteSize: Integer;
LogPal: TMaxLogPalette;
begin
Result := 0;
if Palette = 0 then Exit;
PaletteSize := 0;
if GetObject(Palette, SizeOf(PaletteSize), @PaletteSize) = 0 then Exit;
if PaletteSize = 0 then Exit;
with LogPal do
begin
palVersion := $0300;
palNumEntries := PaletteSize;
GetPaletteEntries(Palette, 0, PaletteSize, palPalEntry);
end;
Result := CreatePalette(PLogPalette(@LogPal)^);
end;
// TThreadList implementation from Delphi 3 classes.pas
constructor TThreadList.Create;
begin
inherited Create;
InitializeCriticalSection(FLock);
FList := TList.Create;
end;
destructor TThreadList.Destroy;
begin
LockList; // Make sure nobody else is inside the list.
try
FList.Free;
inherited Destroy;
finally
UnlockList;
DeleteCriticalSection(FLock);
end;
end;
procedure TThreadList.Add(Item: Pointer);
begin
LockList;
try
if FList.IndexOf(Item) = -1 then
FList.Add(Item);
finally
UnlockList;
end;
end;
procedure TThreadList.Clear;
begin
LockList;
try
FList.Clear;
finally
UnlockList;
end;
end;
function TThreadList.LockList: TList;
begin
EnterCriticalSection(FLock);
Result := FList;
end;
procedure TThreadList.Remove(Item: Pointer);
begin
LockList;
try
FList.Remove(Item);
finally
UnlockList;
end;
end;
procedure TThreadList.UnlockList;
begin
LeaveCriticalSection(FLock);
end;
// End of TThreadList implementation
// From Delphi 3 sysutils.pas
{ CompareMem performs a binary compare of Length bytes of memory referenced
by P1 to that of P2. CompareMem returns True if the memory referenced by
P1 is identical to that of P2. }
function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; assembler;
asm
PUSH ESI
PUSH EDI
MOV ESI,P1
MOV EDI,P2
MOV EDX,ECX
XOR EAX,EAX
AND EDX,3
SHR ECX,1
SHR ECX,1
REPE CMPSD
JNE @@2
MOV ECX,EDX
REPE CMPSB
JNE @@2
@@1: INC EAX
@@2: POP EDI
POP ESI
end;
// Dummy ASSERT procedure since ASSERT does not exist in Delphi 2.x
procedure ASSERT(Condition: boolean; Message: string);
begin
end;
{$ENDIF} // Delphi 2.x stuff
////////////////////////////////////////////////////////////////////////////////
//
// TDIB Classes
//
// These classes gives read and write access to TBitmap's pixel data
// independently of the Delphi version used.
//
////////////////////////////////////////////////////////////////////////////////
type
TDIB = class(TObject)
private
FBitmap : TBitmap;
FPixelFormat : TPixelFormat;
protected
function GetScanline(Row: integer): pointer; virtual; abstract;
constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat);
public
property Scanline[Row: integer]: pointer read GetScanline;
property Bitmap: TBitmap read FBitmap;
property PixelFormat: TPixelFormat read FPixelFormat;
end;
TDIBReader = class(TDIB)
private
{$ifdef VER9x}
FDIB : TDIBSection;
FDC : HDC;
FScanLine : pointer;
FLastRow : integer;
FInfo : PBitmapInfo;
FBytes : integer;
{$endif}
protected
function GetScanline(Row: integer): pointer; override;
public
constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat);
destructor Destroy; override;
end;
TDIBWriter = class(TDIB)
private
{$ifdef PIXELFORMAT_TOO_SLOW}
FDIBInfo : PBitmapInfo;
FDIBBits : pointer;
FDIBInfoSize : integer;
FDIBBitsSize : longInt;
{$ifndef CREATEDIBSECTION_SLOW}
FDIB : HBITMAP;
{$endif}
{$endif}
FPalette : HPalette;
FHeight : integer;
FWidth : integer;
protected
procedure CreateDIB;
procedure FreeDIB;
procedure NeedDIB;
function GetScanline(Row: integer): pointer; override;
public
constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat;
AWidth, AHeight: integer; APalette: HPalette);
destructor Destroy; override;
procedure UpdateBitmap;
property Width: integer read FWidth;
property Height: integer read FHeight;
property Palette: HPalette read FPalette;
end;
////////////////////////////////////////////////////////////////////////////////
constructor TDIB.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat);
begin
inherited Create;
FBitmap := ABitmap;
FPixelFormat := APixelFormat;
end;
////////////////////////////////////////////////////////////////////////////////
constructor TDIBReader.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat);
{$ifdef VER9x}
var
InfoHeaderSize : integer;
ImageSize : longInt;
{$endif}
begin
inherited Create(ABitmap, APixelFormat);
{$ifndef VER9x}
SetPixelFormat(FBitmap, FPixelFormat);
{$else}
FDC := CreateCompatibleDC(0);
SelectPalette(FDC, FBitmap.Palette, False);
// Allocate DIB info structure
InternalGetDIBSizes(ABitmap.Handle, InfoHeaderSize, ImageSize, APixelFormat);
GetMem(FInfo, InfoHeaderSize);
// Get DIB info
InitializeBitmapInfoHeader(ABitmap.Handle, FInfo^.bmiHeader, APixelFormat);
// Allocate scan line buffer
GetMem(FScanLine, ImageSize DIV abs(FInfo^.bmiHeader.biHeight));
FLastRow := -1;
{$endif}
end;
destructor TDIBReader.Destroy;
begin
{$ifdef VER9x}
DeleteDC(FDC);
FreeMem(FScanLine);
FreeMem(FInfo);
{$endif}
inherited Destroy;
end;
function TDIBReader.GetScanline(Row: integer): pointer;
begin
{$ifdef VER9x}
if (Row < 0) or (Row >= FBitmap.Height) then
raise EInvalidGraphicOperation.Create(SScanLine);
GDIFlush;
Result := FScanLine;
if (Row = FLastRow) then
exit;
FLastRow := Row;
if (FInfo^.bmiHeader.biHeight > 0) then // bottom-up DIB
Row := FInfo^.bmiHeader.biHeight - Row - 1;
GetDIBits(FDC, FBitmap.Handle, Row, 1, FScanLine, FInfo^, DIB_RGB_COLORS);
{$else}
Result := FBitmap.ScanLine[Row];
{$endif}
end;
////////////////////////////////////////////////////////////////////////////////
constructor TDIBWriter.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat;
AWidth, AHeight: integer; APalette: HPalette);
begin
inherited Create(ABitmap, APixelFormat);
// DIB writer only supports 8 or 24 bit bitmaps
if not(APixelFormat in [pf8bit, pf24bit]) then
Error(sInvalidPixelFormat);
if (AWidth = 0) or (AHeight = 0) then
Error(sBadDimension);
FHeight := AHeight;
FWidth := AWidth;
{$ifndef PIXELFORMAT_TOO_SLOW}
FBitmap.Palette := 0;
FBitmap.Height := FHeight;
FBitmap.Width := FWidth;
SafeSetPixelFormat(FBitmap, FPixelFormat);
FPalette := CopyPalette(APalette);
FBitmap.Palette := FPalette;
{$else}
FPalette := APalette;
FDIBInfo := nil;
FDIBBits := nil;
{$ifndef CREATEDIBSECTION_SLOW}
FDIB := 0;
{$endif}
{$endif}
end;
destructor TDIBWriter.Destroy;
begin
UpdateBitmap;
FreeDIB;
inherited Destroy;
end;
function TDIBWriter.GetScanline(Row: integer): pointer;
begin
{$ifdef PIXELFORMAT_TOO_SLOW}
NeedDIB;
if (FDIBBits = nil) then
Error(sNoDIB);
with FDIBInfo^.bmiHeader do
begin
if (Row < 0) or (Row >= Height) then
raise EInvalidGraphicOperation.Create(SScanLine);
GDIFlush;
if biHeight > 0 then // bottom-up DIB
Row := biHeight - Row - 1;
Result := PChar(Cardinal(FDIBBits) + Cardinal(Row) * AlignBit(biWidth, biBitCount, 32));
end;
{$else}
Result := FBitmap.ScanLine[Row];
{$endif}
end;
procedure TDIBWriter.CreateDIB;
{$IFDEF PIXELFORMAT_TOO_SLOW}
var
SrcColors : WORD;
// ScreenDC : HDC;
// From Delphi 3.02 graphics.pas
// There is a bug in the ByteSwapColors from Delphi 3.0!
procedure ByteSwapColors(var Colors; Count: Integer);
var // convert RGB to BGR and vice-versa. TRGBQuad <-> TPaletteEntry
SysInfo: TSystemInfo;
begin
GetSystemInfo(SysInfo);
asm
MOV EDX, Colors
MOV ECX, Count
DEC ECX
JS @@END
LEA EAX, SysInfo
CMP [EAX].TSystemInfo.wProcessorLevel, 3
JE @@386
@@1: MOV EAX, [EDX+ECX*4]
BSWAP EAX
SHR EAX,8
MOV [EDX+ECX*4],EAX
DEC ECX
JNS @@1
JMP @@END
@@386:
PUSH EBX
@@2: XOR EBX,EBX
MOV EAX, [EDX+ECX*4]
MOV BH, AL
MOV BL, AH
SHR EAX,16
SHL EBX,8
MOV BL, AL
MOV [EDX+ECX*4],EBX
DEC ECX
JNS @@2
POP EBX
@@END:
end;
end;
{$ENDIF}
begin
{$ifdef PIXELFORMAT_TOO_SLOW}
FreeDIB;
if (PixelFormat = pf8bit) then
// 8 bit: Header and palette
FDIBInfoSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * (1 shl 8)
else
// 24 bit: Header but no palette
FDIBInfoSize := SizeOf(TBitmapInfoHeader);
// Allocate TBitmapInfo structure
GetMem(FDIBInfo, FDIBInfoSize);
try
FDIBInfo^.bmiHeader.biSize := SizeOf(FDIBInfo^.bmiHeader);
FDIBInfo^.bmiHeader.biWidth := Width;
FDIBInfo^.bmiHeader.biHeight := Height;
FDIBInfo^.bmiHeader.biPlanes := 1;
FDIBInfo^.bmiHeader.biSizeImage := 0;
FDIBInfo^.bmiHeader.biCompression := BI_RGB;
if (PixelFormat = pf8bit) then
begin
FDIBInfo^.bmiHeader.biBitCount := 8;
// Find number of colors defined by palette
if (Palette <> 0) and
(GetObject(Palette, sizeof(SrcColors), @SrcColors) <> 0) and
(SrcColors <> 0) then
begin
// Copy all colors...
GetPaletteEntries(Palette, 0, SrcColors, FDIBInfo^.bmiColors[0]);
// ...and convert BGR to RGB
ByteSwapColors(FDIBInfo^.bmiColors[0], SrcColors);
end else
SrcColors := 0;
// Finally zero any unused entried
if (SrcColors < 256) then
FillChar(pointer(LongInt(@FDIBInfo^.bmiColors)+SizeOf(TRGBQuad)*SrcColors)^,
256 - SrcColors, 0);
FDIBInfo^.bmiHeader.biClrUsed := 256;
FDIBInfo^.bmiHeader.biClrImportant := SrcColors;
end else
begin
FDIBInfo^.bmiHeader.biBitCount := 24;
FDIBInfo^.bmiHeader.biClrUsed := 0;
FDIBInfo^.bmiHeader.biClrImportant := 0;
end;
FDIBBitsSize := AlignBit(Width, FDIBInfo^.bmiHeader.biBitCount, 32) * Cardinal(abs(Height));
{$ifdef CREATEDIBSECTION_SLOW}
FDIBBits := GlobalAllocPtr(GMEM_MOVEABLE, FDIBBitsSize);
if (FDIBBits = nil) then
raise EOutOfMemory.Create(sOutOfMemDIB);
{$else}
// ScreenDC := GDICheck(GetDC(0));
try
// Allocate DIB section
// Note: You can ignore warnings about the HDC parameter being 0. The
// parameter is not used for 24 bit bitmaps
FDIB := GDICheck(CreateDIBSection(0 {ScreenDC}, FDIBInfo^, DIB_RGB_COLORS,
FDIBBits,
{$IFDEF VER9x} nil, {$ELSE} 0, {$ENDIF}
0));
finally
// ReleaseDC(0, ScreenDC);
end;
{$endif}
except
FreeDIB;
raise;
end;
{$endif}
end;
procedure TDIBWriter.FreeDIB;
begin
{$ifdef PIXELFORMAT_TOO_SLOW}
if (FDIBInfo <> nil) then
FreeMem(FDIBInfo);
{$ifdef CREATEDIBSECTION_SLOW}
if (FDIBBits <> nil) then
GlobalFreePtr(FDIBBits);
{$else}
if (FDIB <> 0) then
DeleteObject(FDIB);
FDIB := 0;
{$endif}
FDIBInfo := nil;
FDIBBits := nil;
{$endif}
end;
procedure TDIBWriter.NeedDIB;
begin
{$ifdef PIXELFORMAT_TOO_SLOW}
{$ifdef CREATEDIBSECTION_SLOW}
if (FDIBBits = nil) then
{$else}
if (FDIB = 0) then
{$endif}
CreateDIB;
{$endif}
end;
// Convert the DIB created by CreateDIB back to a TBitmap
procedure TDIBWriter.UpdateBitmap;
{$ifdef PIXELFORMAT_TOO_SLOW}
var
Stream : TMemoryStream;
FileSize : longInt;
BitmapFileHeader : TBitmapFileHeader;
{$endif}
begin
{$ifdef PIXELFORMAT_TOO_SLOW}
{$ifdef CREATEDIBSECTION_SLOW}
if (FDIBBits = nil) then
{$else}
if (FDIB = 0) then
{$endif}
exit;
// Win95 and NT differs in what solution performs best
{$ifndef CREATEDIBSECTION_SLOW}
{$ifdef VER10_PLUS}
if (Win32Platform = VER_PLATFORM_WIN32_NT) then
begin
// Assign DIB to bitmap
FBitmap.Handle := FDIB;
FDIB := 0;
FBitmap.Palette := CopyPalette(Palette);
end else
{$endif}
{$endif}
begin
// Write DIB to a stream in the BMP file format
Stream := TMemoryStream.Create;
try
// Make room in stream for a TBitmapInfo and pixel data
FileSize := sizeof(TBitmapFileHeader) + FDIBInfoSize + FDIBBitsSize;
Stream.SetSize(FileSize);
// Initialize file header
FillChar(BitmapFileHeader, sizeof(TBitmapFileHeader), 0);
with BitmapFileHeader do
begin
bfType := $4D42; // 'BM' = Windows BMP signature
bfSize := FileSize; // File size (not needed)
bfOffBits := sizeof(TBitmapFileHeader) + FDIBInfoSize; // Offset of pixel data
end;
// Save file header
Stream.Write(BitmapFileHeader, sizeof(TBitmapFileHeader));
// Save TBitmapInfo structure
Stream.Write(FDIBInfo^, FDIBInfoSize);
// Save pixel data
Stream.Write(FDIBBits^, FDIBBitsSize);
// Rewind and load bitmap from stream
Stream.Position := 0;
FBitmap.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
{$endif}
end;
////////////////////////////////////////////////////////////////////////////////
//
// Color Mapping
//
////////////////////////////////////////////////////////////////////////////////
type
TColorLookup = class(TObject)
private
FColors : integer;
public
constructor Create(Palette: hPalette); virtual;
function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; virtual; abstract;
property Colors: integer read FColors;
end;
PRGBQuadArray = ^TRGBQuadArray; // From Delphi 3 graphics.pas
TRGBQuadArray = array[Byte] of TRGBQuad; // From Delphi 3 graphics.pas
BGRArray = array[0..0] of TRGBTriple;
PBGRArray = ^BGRArray;
PalArray = array[byte] of TPaletteEntry;
PPalArray = ^PalArray;
// TFastColorLookup implements a simple but reasonably fast generic color
// mapper. It trades precision for speed by reducing the size of the color
// space.
// Using a class instead of inline code results in a speed penalty of
// approx. 15% but reduces the complexity of the color reduction routines that
// uses it. If bitmap to GIF conversion speed is really important to you, the
// implementation can easily be inlined again.
TInverseLookup = array[0..1 SHL 15-1] of SmallInt;
PInverseLookup = ^TInverseLookup;
TFastColorLookup = class(TColorLookup)
private
FPaletteEntries : PPalArray;
FInverseLookup : PInverseLookup;
public
constructor Create(Palette: hPalette); override;
destructor Destroy; override;
function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
end;
// TSlowColorLookup implements a precise but very slow generic color mapper.
// It uses the GetNearestPaletteIndex GDI function.
// Note: Tests has shown TFastColorLookup to be more precise than
// TSlowColorLookup in many cases. I can't explain why...
TSlowColorLookup = class(TColorLookup)
private
FPaletteEntries : PPalArray;
FPalette : hPalette;
public
constructor Create(Palette: hPalette); override;
destructor Destroy; override;
function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
end;
// TNetscapeColorLookup maps colors to the netscape 6*6*6 color cube.
TNetscapeColorLookup = class(TColorLookup)
public
constructor Create(Palette: hPalette); override;
function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
end;
// TGrayWindowsLookup maps colors to 4 shade palette.
TGrayWindowsLookup = class(TSlowColorLookup)
public
constructor Create(Palette: hPalette); override;
function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
end;
// TGrayScaleLookup maps colors to a uniform 256 shade palette.
TGrayScaleLookup = class(TColorLookup)
public
constructor Create(Palette: hPalette); override;
function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
end;
// TMonochromeLookup maps colors to a black/white palette.
TMonochromeLookup = class(TColorLookup)
public
constructor Create(Palette: hPalette); override;
function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
end;
constructor TColorLookup.Create(Palette: hPalette);
begin
inherited Create;
end;
constructor TFastColorLookup.Create(Palette: hPalette);
var
i : integer;
InverseIndex : integer;
begin
inherited Create(Palette);
GetMem(FPaletteEntries, sizeof(TPaletteEntry) * 256);
FColors := GetPaletteEntries(Palette, 0, 256, FPaletteEntries^);
New(FInverseLookup);
for i := low(TInverseLookup) to high(TInverseLookup) do
FInverseLookup^[i] := -1;
// Premap palette colors
if (FColors > 0) then
for i := 0 to FColors-1 do
with FPaletteEntries^[i] do
begin
InverseIndex := (peRed SHR 3) OR ((peGreen AND $F8) SHL 2) OR ((peBlue AND $F8) SHL 7);
if (FInverseLookup^[InverseIndex] = -1) then
FInverseLookup^[InverseIndex] := i;
end;
end;
destructor TFastColorLookup.Destroy;
begin
if (FPaletteEntries <> nil) then
FreeMem(FPaletteEntries);
if (FInverseLookup <> nil) then
Dispose(FInverseLookup);
inherited Destroy;
end;
// Map color to arbitrary palette
function TFastColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
var
i : integer;
InverseIndex : integer;
Delta ,
MinDelta ,
MinColor : integer;
begin
// Reduce color space with 3 bits in each dimension
InverseIndex := (Red SHR 3) OR ((Green AND $F8) SHL 2) OR ((Blue AND $F8) SHL 7);
if (FInverseLookup^[InverseIndex] <> -1) then
Result := char(FInverseLookup^[InverseIndex])
else
begin
// Sequential scan for nearest color to minimize euclidian distance
MinDelta := 3 * (256 * 256);
MinColor := 0;
for i := 0 to FColors-1 do
with FPaletteEntries[i] do
begin
Delta := ABS(peRed - Red) + ABS(peGreen - Green) + ABS(peBlue - Blue);
if (Delta < MinDelta) then
begin
MinDelta := Delta;
MinColor := i;
end;
end;
Result := char(MinColor);
FInverseLookup^[InverseIndex] := MinColor;
end;
with FPaletteEntries^[ord(Result)] do
begin
R := peRed;
G := peGreen;
B := peBlue;
end;
end;
constructor TSlowColorLookup.Create(Palette: hPalette);
begin
inherited Create(Palette);
FPalette := Palette;
FColors := GetPaletteEntries(Palette, 0, 256, nil^);
if (FColors > 0) then
begin
GetMem(FPaletteEntries, sizeof(TPaletteEntry) * FColors);
FColors := GetPaletteEntries(Palette, 0, 256, FPaletteEntries^);
end;
end;
destructor TSlowColorLookup.Destroy;
begin
if (FPaletteEntries <> nil) then
FreeMem(FPaletteEntries);
inherited Destroy;
end;
// Map color to arbitrary palette
function TSlowColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
begin
Result := char(GetNearestPaletteIndex(FPalette, Red OR (Green SHL 8) OR (Blue SHL 16)));
if (FPaletteEntries <> nil) then
with FPaletteEntries^[ord(Result)] do
begin
R := peRed;
G := peGreen;
B := peBlue;
end;
end;
constructor TNetscapeColorLookup.Create(Palette: hPalette);
begin
inherited Create(Palette);
FColors := 6*6*6; // This better be true or something is wrong
end;
// Map color to netscape 6*6*6 color cube
function TNetscapeColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
begin
R := (Red+3) DIV 51;
G := (Green+3) DIV 51;
B := (Blue+3) DIV 51;
Result := char(B + 6*G + 36*R);
R := R * 51;
G := G * 51;
B := B * 51;
end;
constructor TGrayWindowsLookup.Create(Palette: hPalette);
begin
inherited Create(Palette);
FColors := 4;
end;
// Convert color to windows grays
function TGrayWindowsLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
begin
Result := inherited Lookup(MulDiv(Red, 77, 256),
MulDiv(Green, 150, 256), MulDiv(Blue, 29, 256), R, G, B);
end;
constructor TGrayScaleLookup.Create(Palette: hPalette);
begin
inherited Create(Palette);
FColors := 256;
end;
// Convert color to grayscale
function TGrayScaleLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
begin
Result := char((Blue*29 + Green*150 + Red*77) DIV 256);
R := ord(Result);
G := ord(Result);
B := ord(Result);
end;
constructor TMonochromeLookup.Create(Palette: hPalette);
begin
inherited Create(Palette);
FColors := 2;
end;
// Convert color to black/white
function TMonochromeLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
begin
if ((Blue*29 + Green*150 + Red*77) > 32512) then
begin
Result := #1;
R := 255;
G := 255;
B := 255;
end else
begin
Result := #0;
R := 0;
G := 0;
B := 0;
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// Dithering engine
//
////////////////////////////////////////////////////////////////////////////////
type
TDitherEngine = class
private
protected
FDirection : integer;
FColumn : integer;
FLookup : TColorLookup;
Width : integer;
public
constructor Create(AWidth: integer; Lookup: TColorLookup); virtual;
function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; virtual;
procedure NextLine; virtual;
procedure NextColumn;
property Direction: integer read FDirection;
property Column: integer read FColumn;
end;
// Note: TErrorTerm does only *need* to be 16 bits wide, but since
// it is *much* faster to use native machine words (32 bit), we sacrifice
// some bytes (a lot actually) to improve performance.
TErrorTerm = Integer;
TErrors = array[0..0] of TErrorTerm;
PErrors = ^TErrors;
TFloydSteinbergDitherer = class(TDitherEngine)
private
ErrorsR ,
ErrorsG ,
ErrorsB : PErrors;
ErrorR ,
ErrorG ,
ErrorB : PErrors;
CurrentErrorR , // Current error or pixel value
CurrentErrorG ,
CurrentErrorB ,
BelowErrorR , // Error for pixel below current
BelowErrorG ,
BelowErrorB ,
BelowPrevErrorR , // Error for pixel below previous pixel
BelowPrevErrorG ,
BelowPrevErrorB : TErrorTerm;
public
constructor Create(AWidth: integer; Lookup: TColorLookup); override;
destructor Destroy; override;
function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
procedure NextLine; override;
end;
T5by3Ditherer = class(TDitherEngine)
private
ErrorsR0 ,
ErrorsG0 ,
ErrorsB0 ,
ErrorsR1 ,
ErrorsG1 ,
ErrorsB1 ,
ErrorsR2 ,
ErrorsG2 ,
ErrorsB2 : PErrors;
ErrorR0 ,
ErrorG0 ,
ErrorB0 ,
ErrorR1 ,
ErrorG1 ,
ErrorB1 ,
ErrorR2 ,
ErrorG2 ,
ErrorB2 : PErrors;
FDirection2 : integer;
protected
FDivisor : integer;
procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); virtual; abstract;
public
constructor Create(AWidth: integer; Lookup: TColorLookup); override;
destructor Destroy; override;
function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
procedure NextLine; override;
end;
TStuckiDitherer = class(T5by3Ditherer)
protected
procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override;
public
constructor Create(AWidth: integer; Lookup: TColorLookup); override;
end;
TSierraDitherer = class(T5by3Ditherer)
protected
procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override;
public
constructor Create(AWidth: integer; Lookup: TColorLookup); override;
end;
TJaJuNiDitherer = class(T5by3Ditherer)
protected
procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override;
public
constructor Create(AWidth: integer; Lookup: TColorLookup); override;
end;
TSteveArcheDitherer = class(TDitherEngine)
private
ErrorsR0 ,
ErrorsG0 ,
ErrorsB0 ,
ErrorsR1 ,
ErrorsG1 ,
ErrorsB1 ,
ErrorsR2 ,
ErrorsG2 ,
ErrorsB2 ,
ErrorsR3 ,
ErrorsG3 ,
ErrorsB3 : PErrors;
ErrorR0 ,
ErrorG0 ,
ErrorB0 ,
ErrorR1 ,
ErrorG1 ,
ErrorB1 ,
ErrorR2 ,
ErrorG2 ,
ErrorB2 ,
ErrorR3 ,
ErrorG3 ,
ErrorB3 : PErrors;
FDirection2 ,
FDirection3 : integer;
public
constructor Create(AWidth: integer; Lookup: TColorLookup); override;
destructor Destroy; override;
function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
procedure NextLine; override;
end;
TBurkesDitherer = class(TDitherEngine)
private
ErrorsR0 ,
ErrorsG0 ,
ErrorsB0 ,
ErrorsR1 ,
ErrorsG1 ,
ErrorsB1 : PErrors;
ErrorR0 ,
ErrorG0 ,
ErrorB0 ,
ErrorR1 ,
ErrorG1 ,
ErrorB1 : PErrors;
FDirection2 : integer;
public
constructor Create(AWidth: integer; Lookup: TColorLookup); override;
destructor Destroy; override;
function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
procedure NextLine; override;
end;
////////////////////////////////////////////////////////////////////////////////
// TDitherEngine
constructor TDitherEngine.Create(AWidth: integer; Lookup: TColorLookup);
begin
inherited Create;
FLookup := Lookup;
Width := AWidth;
FDirection := 1;
FColumn := 0;
end;
function TDitherEngine.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
begin
// Map color to palette
Result := FLookup.Lookup(Red, Green, Blue, R, G, B);
NextColumn;
end;
procedure TDitherEngine.NextLine;
begin
FDirection := -FDirection;
if (FDirection = 1) then
FColumn := 0
else
FColumn := Width-1;
end;
procedure TDitherEngine.NextColumn;
begin
inc(FColumn, FDirection);
end;
////////////////////////////////////////////////////////////////////////////////
// TFloydSteinbergDitherer
constructor TFloydSteinbergDitherer.Create(AWidth: integer; Lookup: TColorLookup);
begin
inherited Create(AWidth, Lookup);
// The Error arrays has (columns + 2) entries; the extra entry at
// each end saves us from special-casing the first and last pixels.
// We can get away with a single array (holding one row's worth of errors)
// by using it to store the current row's errors at pixel columns not yet
// processed, but the next row's errors at columns already processed. We
// need only a few extra variables to hold the errors immediately around the
// current column. (If we are lucky, those variables are in registers, but
// even if not, they're probably cheaper to access than array elements are.)
GetMem(ErrorsR, sizeof(TErrorTerm)*(Width+2));
GetMem(ErrorsG, sizeof(TErrorTerm)*(Width+2));
GetMem(ErrorsB, sizeof(TErrorTerm)*(Width+2));
FillChar(ErrorsR^, sizeof(TErrorTerm)*(Width+2), 0);
FillChar(ErrorsG^, sizeof(TErrorTerm)*(Width+2), 0);
FillChar(ErrorsB^, sizeof(TErrorTerm)*(Width+2), 0);
ErrorR := ErrorsR;
ErrorG := ErrorsG;
ErrorB := ErrorsB;
CurrentErrorR := 0;
CurrentErrorG := CurrentErrorR;
CurrentErrorB := CurrentErrorR;
BelowErrorR := CurrentErrorR;
BelowErrorG := CurrentErrorR;
BelowErrorB := CurrentErrorR;
BelowPrevErrorR := CurrentErrorR;
BelowPrevErrorG := CurrentErrorR;
BelowPrevErrorB := CurrentErrorR;
end;
destructor TFloydSteinbergDitherer.Destroy;
begin
FreeMem(ErrorsR);
FreeMem(ErrorsG);
FreeMem(ErrorsB);
inherited Destroy;
end;
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
function TFloydSteinbergDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
var
BelowNextError : TErrorTerm;
Delta : TErrorTerm;
begin
CurrentErrorR := Red + (CurrentErrorR + ErrorR[0] + 8) DIV 16;
// CurrentErrorR := Red + (CurrentErrorR + ErrorR[Direction] + 8) DIV 16;
if (CurrentErrorR < 0) then
CurrentErrorR := 0
else if (CurrentErrorR > 255) then
CurrentErrorR := 255;
CurrentErrorG := Green + (CurrentErrorG + ErrorG[0] + 8) DIV 16;
// CurrentErrorG := Green + (CurrentErrorG + ErrorG[Direction] + 8) DIV 16;
if (CurrentErrorG < 0) then
CurrentErrorG := 0
else if (CurrentErrorG > 255) then
CurrentErrorG := 255;
CurrentErrorB := Blue + (CurrentErrorB + ErrorB[0] + 8) DIV 16;
// CurrentErrorB := Blue + (CurrentErrorB + ErrorB[Direction] + 8) DIV 16;
if (CurrentErrorB < 0) then
CurrentErrorB := 0
else if (CurrentErrorB > 255) then
CurrentErrorB := 255;
// Map color to palette
Result := inherited Dither(CurrentErrorR, CurrentErrorG, CurrentErrorB, R, G, B);
// Propagate Floyd-Steinberg error terms.
// Errors are accumulated into the error arrays, at a resolution of
// 1/16th of a pixel count. The error at a given pixel is propagated
// to its not-yet-processed neighbors using the standard F-S fractions,
// ... (here) 7/16
// 3/16 5/16 1/16
// We work left-to-right on even rows, right-to-left on odd rows.
// Red component
CurrentErrorR := CurrentErrorR - R;
if (CurrentErrorR <> 0) then
begin
BelowNextError := CurrentErrorR; // Error * 1
Delta := CurrentErrorR * 2;
inc(CurrentErrorR, Delta);
ErrorR[0] := BelowPrevErrorR + CurrentErrorR; // Error * 3
inc(CurrentErrorR, Delta);
BelowPrevErrorR := BelowErrorR + CurrentErrorR; // Error * 5
BelowErrorR := BelowNextError; // Error * 1
inc(CurrentErrorR, Delta); // Error * 7
end;
// Green component
CurrentErrorG := CurrentErrorG - G;
if (CurrentErrorG <> 0) then
begin
BelowNextError := CurrentErrorG; // Error * 1
Delta := CurrentErrorG * 2;
inc(CurrentErrorG, Delta);
ErrorG[0] := BelowPrevErrorG + CurrentErrorG; // Error * 3
inc(CurrentErrorG, Delta);
BelowPrevErrorG := BelowErrorG + CurrentErrorG; // Error * 5
BelowErrorG := BelowNextError; // Error * 1
inc(CurrentErrorG, Delta); // Error * 7
end;
// Blue component
CurrentErrorB := CurrentErrorB - B;
if (CurrentErrorB <> 0) then
begin
BelowNextError := CurrentErrorB; // Error * 1
Delta := CurrentErrorB * 2;
inc(CurrentErrorB, Delta);
ErrorB[0] := BelowPrevErrorB + CurrentErrorB; // Error * 3
inc(CurrentErrorB, Delta);
BelowPrevErrorB := BelowErrorB + CurrentErrorB; // Error * 5
BelowErrorB := BelowNextError; // Error * 1
inc(CurrentErrorB, Delta); // Error * 7
end;
// Move on to next column
if (Direction = 1) then
begin
inc(longInt(ErrorR), sizeof(TErrorTerm));
inc(longInt(ErrorG), sizeof(TErrorTerm));
inc(longInt(ErrorB), sizeof(TErrorTerm));
end else
begin
dec(longInt(ErrorR), sizeof(TErrorTerm));
dec(longInt(ErrorG), sizeof(TErrorTerm));
dec(longInt(ErrorB), sizeof(TErrorTerm));
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
procedure TFloydSteinbergDitherer.NextLine;
begin
ErrorR[0] := BelowPrevErrorR;
ErrorG[0] := BelowPrevErrorG;
ErrorB[0] := BelowPrevErrorB;
// Note: The optimizer produces better code for this construct:
// a := 0; b := a; c := a;
// compared to this construct:
// a := 0; b := 0; c := 0;
CurrentErrorR := 0;
CurrentErrorG := CurrentErrorR;
CurrentErrorB := CurrentErrorG;
BelowErrorR := CurrentErrorG;
BelowErrorG := CurrentErrorG;
BelowErrorB := CurrentErrorG;
BelowPrevErrorR := CurrentErrorG;
BelowPrevErrorG := CurrentErrorG;
BelowPrevErrorB := CurrentErrorG;
inherited NextLine;
if (Direction = 1) then
begin
ErrorR := ErrorsR;
ErrorG := ErrorsG;
ErrorB := ErrorsB;
end else
begin
ErrorR := @ErrorsR[Width+1];
ErrorG := @ErrorsG[Width+1];
ErrorB := @ErrorsB[Width+1];
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
// T5by3Ditherer
constructor T5by3Ditherer.Create(AWidth: integer; Lookup: TColorLookup);
begin
inherited Create(AWidth, Lookup);
GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsR2, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsG2, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsB2, sizeof(TErrorTerm)*(Width+4));
FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsR2^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsG2^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsB2^, sizeof(TErrorTerm)*(Width+4), 0);
FDivisor := 1;
FDirection2 := 2 * Direction;
ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm));
ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm));
ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm));
ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm));
ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm));
ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm));
ErrorR2 := PErrors(longInt(ErrorsR2)+2*sizeof(TErrorTerm));
ErrorG2 := PErrors(longInt(ErrorsG2)+2*sizeof(TErrorTerm));
ErrorB2 := PErrors(longInt(ErrorsB2)+2*sizeof(TErrorTerm));
end;
destructor T5by3Ditherer.Destroy;
begin
FreeMem(ErrorsR0);
FreeMem(ErrorsG0);
FreeMem(ErrorsB0);
FreeMem(ErrorsR1);
FreeMem(ErrorsG1);
FreeMem(ErrorsB1);
FreeMem(ErrorsR2);
FreeMem(ErrorsG2);
FreeMem(ErrorsB2);
inherited Destroy;
end;
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
function T5by3Ditherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
var
ColorR ,
ColorG ,
ColorB : integer; // Error for current pixel
begin
// Apply red component error correction
ColorR := Red + (ErrorR0[0] + FDivisor DIV 2) DIV FDivisor;
if (ColorR < 0) then
ColorR := 0
else if (ColorR > 255) then
ColorR := 255;
// Apply green component error correction
ColorG := Green + (ErrorG0[0] + FDivisor DIV 2) DIV FDivisor;
if (ColorG < 0) then
ColorG := 0
else if (ColorG > 255) then
ColorG := 255;
// Apply blue component error correction
ColorB := Blue + (ErrorB0[0] + FDivisor DIV 2) DIV FDivisor;
if (ColorB < 0) then
ColorB := 0
else if (ColorB > 255) then
ColorB := 255;
// Map color to palette
Result := inherited Dither(ColorR, ColorG, ColorB, R, G, B);
// Propagate red component error
Propagate(ErrorR0, ErrorR1, ErrorR2, ColorR - R);
// Propagate green component error
Propagate(ErrorG0, ErrorG1, ErrorG2, ColorG - G);
// Propagate blue component error
Propagate(ErrorB0, ErrorB1, ErrorB2, ColorB - B);
// Move on to next column
if (Direction = 1) then
begin
inc(longInt(ErrorR0), sizeof(TErrorTerm));
inc(longInt(ErrorG0), sizeof(TErrorTerm));
inc(longInt(ErrorB0), sizeof(TErrorTerm));
inc(longInt(ErrorR1), sizeof(TErrorTerm));
inc(longInt(ErrorG1), sizeof(TErrorTerm));
inc(longInt(ErrorB1), sizeof(TErrorTerm));
inc(longInt(ErrorR2), sizeof(TErrorTerm));
inc(longInt(ErrorG2), sizeof(TErrorTerm));
inc(longInt(ErrorB2), sizeof(TErrorTerm));
end else
begin
dec(longInt(ErrorR0), sizeof(TErrorTerm));
dec(longInt(ErrorG0), sizeof(TErrorTerm));
dec(longInt(ErrorB0), sizeof(TErrorTerm));
dec(longInt(ErrorR1), sizeof(TErrorTerm));
dec(longInt(ErrorG1), sizeof(TErrorTerm));
dec(longInt(ErrorB1), sizeof(TErrorTerm));
dec(longInt(ErrorR2), sizeof(TErrorTerm));
dec(longInt(ErrorG2), sizeof(TErrorTerm));
dec(longInt(ErrorB2), sizeof(TErrorTerm));
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
procedure T5by3Ditherer.NextLine;
var
TempErrors : PErrors;
begin
FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0);
// Swap lines
TempErrors := ErrorsR0;
ErrorsR0 := ErrorsR1;
ErrorsR1 := ErrorsR2;
ErrorsR2 := TempErrors;
TempErrors := ErrorsG0;
ErrorsG0 := ErrorsG1;
ErrorsG1 := ErrorsG2;
ErrorsG2 := TempErrors;
TempErrors := ErrorsB0;
ErrorsB0 := ErrorsB1;
ErrorsB1 := ErrorsB2;
ErrorsB2 := TempErrors;
inherited NextLine;
FDirection2 := 2 * Direction;
if (Direction = 1) then
begin
// ErrorsR0[1] gives compiler error, so we
// use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead...
ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm));
ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm));
ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm));
ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm));
ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm));
ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm));
ErrorR2 := PErrors(longInt(ErrorsR2)+2*sizeof(TErrorTerm));
ErrorG2 := PErrors(longInt(ErrorsG2)+2*sizeof(TErrorTerm));
ErrorB2 := PErrors(longInt(ErrorsB2)+2*sizeof(TErrorTerm));
end else
begin
ErrorR0 := @ErrorsR0[Width+1];
ErrorG0 := @ErrorsG0[Width+1];
ErrorB0 := @ErrorsB0[Width+1];
ErrorR1 := @ErrorsR1[Width+1];
ErrorG1 := @ErrorsG1[Width+1];
ErrorB1 := @ErrorsB1[Width+1];
ErrorR2 := @ErrorsR2[Width+1];
ErrorG2 := @ErrorsG2[Width+1];
ErrorB2 := @ErrorsB2[Width+1];
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
// TStuckiDitherer
constructor TStuckiDitherer.Create(AWidth: integer; Lookup: TColorLookup);
begin
inherited Create(AWidth, Lookup);
FDivisor := 42;
end;
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
procedure TStuckiDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer);
begin
if (Error = 0) then
exit;
// Propagate Stucki error terms:
// ... ... (here) 8/42 4/42
// 2/42 4/42 8/42 4/42 2/42
// 1/42 2/42 4/42 2/42 1/42
inc(Errors2[FDirection2], Error); // Error * 1
inc(Errors2[-FDirection2], Error); // Error * 1
Error := Error + Error;
inc(Errors1[FDirection2], Error); // Error * 2
inc(Errors1[-FDirection2], Error); // Error * 2
inc(Errors2[Direction], Error); // Error * 2
inc(Errors2[-Direction], Error); // Error * 2
Error := Error + Error;
inc(Errors0[FDirection2], Error); // Error * 4
inc(Errors1[-Direction], Error); // Error * 4
inc(Errors1[Direction], Error); // Error * 4
inc(Errors2[0], Error); // Error * 4
Error := Error + Error;
inc(Errors0[Direction], Error); // Error * 8
inc(Errors1[0], Error); // Error * 8
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
// TSierraDitherer
constructor TSierraDitherer.Create(AWidth: integer; Lookup: TColorLookup);
begin
inherited Create(AWidth, Lookup);
FDivisor := 32;
end;
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
procedure TSierraDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer);
var
TempError : integer;
begin
if (Error = 0) then
exit;
// Propagate Sierra error terms:
// ... ... (here) 5/32 3/32
// 2/32 4/32 5/32 4/32 2/32
// ... 2/32 3/32 2/32 ...
TempError := Error + Error;
inc(Errors1[FDirection2], TempError); // Error * 2
inc(Errors1[-FDirection2], TempError);// Error * 2
inc(Errors2[Direction], TempError); // Error * 2
inc(Errors2[-Direction], TempError); // Error * 2
inc(TempError, Error);
inc(Errors0[FDirection2], TempError); // Error * 3
inc(Errors2[0], TempError); // Error * 3
inc(TempError, Error);
inc(Errors1[-Direction], TempError); // Error * 4
inc(Errors1[Direction], TempError); // Error * 4
inc(TempError, Error);
inc(Errors0[Direction], TempError); // Error * 5
inc(Errors1[0], TempError); // Error * 5
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
// TJaJuNiDitherer
constructor TJaJuNiDitherer.Create(AWidth: integer; Lookup: TColorLookup);
begin
inherited Create(AWidth, Lookup);
FDivisor := 38;
end;
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
procedure TJaJuNiDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer);
var
TempError : integer;
begin
if (Error = 0) then
exit;
// Propagate Jarvis, Judice and Ninke error terms:
// ... ... (here) 8/38 4/38
// 2/38 4/38 8/38 4/38 2/38
// 1/38 2/38 4/38 2/38 1/38
inc(Errors2[FDirection2], Error); // Error * 1
inc(Errors2[-FDirection2], Error); // Error * 1
TempError := Error + Error;
inc(Error, TempError);
inc(Errors1[FDirection2], Error); // Error * 3
inc(Errors1[-FDirection2], Error); // Error * 3
inc(Errors2[Direction], Error); // Error * 3
inc(Errors2[-Direction], Error); // Error * 3
inc(Error, TempError);
inc(Errors0[FDirection2], Error); // Error * 5
inc(Errors1[-Direction], Error); // Error * 5
inc(Errors1[Direction], Error); // Error * 5
inc(Errors2[0], Error); // Error * 5
inc(Error, TempError);
inc(Errors0[Direction], Error); // Error * 7
inc(Errors1[0], Error); // Error * 7
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
// TSteveArcheDitherer
constructor TSteveArcheDitherer.Create(AWidth: integer; Lookup: TColorLookup);
begin
inherited Create(AWidth, Lookup);
GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsR2, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsG2, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsB2, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsR3, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsG3, sizeof(TErrorTerm)*(Width+6));
GetMem(ErrorsB3, sizeof(TErrorTerm)*(Width+6));
FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsR2^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsG2^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsB2^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsR3^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsG3^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsB3^, sizeof(TErrorTerm)*(Width+6), 0);
FDirection2 := 2 * Direction;
FDirection3 := 3 * Direction;
ErrorR0 := PErrors(longInt(ErrorsR0)+3*sizeof(TErrorTerm));
ErrorG0 := PErrors(longInt(ErrorsG0)+3*sizeof(TErrorTerm));
ErrorB0 := PErrors(longInt(ErrorsB0)+3*sizeof(TErrorTerm));
ErrorR1 := PErrors(longInt(ErrorsR1)+3*sizeof(TErrorTerm));
ErrorG1 := PErrors(longInt(ErrorsG1)+3*sizeof(TErrorTerm));
ErrorB1 := PErrors(longInt(ErrorsB1)+3*sizeof(TErrorTerm));
ErrorR2 := PErrors(longInt(ErrorsR2)+3*sizeof(TErrorTerm));
ErrorG2 := PErrors(longInt(ErrorsG2)+3*sizeof(TErrorTerm));
ErrorB2 := PErrors(longInt(ErrorsB2)+3*sizeof(TErrorTerm));
ErrorR3 := PErrors(longInt(ErrorsR3)+3*sizeof(TErrorTerm));
ErrorG3 := PErrors(longInt(ErrorsG3)+3*sizeof(TErrorTerm));
ErrorB3 := PErrors(longInt(ErrorsB3)+3*sizeof(TErrorTerm));
end;
destructor TSteveArcheDitherer.Destroy;
begin
FreeMem(ErrorsR0);
FreeMem(ErrorsG0);
FreeMem(ErrorsB0);
FreeMem(ErrorsR1);
FreeMem(ErrorsG1);
FreeMem(ErrorsB1);
FreeMem(ErrorsR2);
FreeMem(ErrorsG2);
FreeMem(ErrorsB2);
FreeMem(ErrorsR3);
FreeMem(ErrorsG3);
FreeMem(ErrorsB3);
inherited Destroy;
end;
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
function TSteveArcheDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
var
ColorR ,
ColorG ,
ColorB : integer; // Error for current pixel
// Propagate Stevenson & Arche error terms:
// ... ... ... (here) ... 32/200 ...
// 12/200 ... 26/200 ... 30/200 ... 16/200
// ... 12/200 ... 26/200 ... 12/200 ...
// 5/200 ... 12/200 ... 12/200 ... 5/200
procedure Propagate(Errors0, Errors1, Errors2, Errors3: PErrors; Error: integer);
var
TempError : integer;
begin
if (Error = 0) then
exit;
TempError := 5 * Error;
inc(Errors3[FDirection3], TempError); // Error * 5
inc(Errors3[-FDirection3], TempError); // Error * 5
TempError := 12 * Error;
inc(Errors1[-FDirection3], TempError); // Error * 12
inc(Errors2[-FDirection2], TempError); // Error * 12
inc(Errors2[FDirection2], TempError); // Error * 12
inc(Errors3[-Direction], TempError); // Error * 12
inc(Errors3[Direction], TempError); // Error * 12
inc(Errors1[FDirection3], 16 * TempError); // Error * 16
TempError := 26 * Error;
inc(Errors1[-Direction], TempError); // Error * 26
inc(Errors2[0], TempError); // Error * 26
inc(Errors1[Direction], 30 * Error); // Error * 30
inc(Errors0[FDirection2], 32 * Error); // Error * 32
end;
begin
// Apply red component error correction
ColorR := Red + (ErrorR0[0] + 100) DIV 200;
if (ColorR < 0) then
ColorR := 0
else if (ColorR > 255) then
ColorR := 255;
// Apply green component error correction
ColorG := Green + (ErrorG0[0] + 100) DIV 200;
if (ColorG < 0) then
ColorG := 0
else if (ColorG > 255) then
ColorG := 255;
// Apply blue component error correction
ColorB := Blue + (ErrorB0[0] + 100) DIV 200;
if (ColorB < 0) then
ColorB := 0
else if (ColorB > 255) then
ColorB := 255;
// Map color to palette
Result := inherited Dither(ColorR, ColorG, ColorB, R, G, B);
// Propagate red component error
Propagate(ErrorR0, ErrorR1, ErrorR2, ErrorR3, ColorR - R);
// Propagate green component error
Propagate(ErrorG0, ErrorG1, ErrorG2, ErrorG3, ColorG - G);
// Propagate blue component error
Propagate(ErrorB0, ErrorB1, ErrorB2, ErrorB3, ColorB - B);
// Move on to next column
if (Direction = 1) then
begin
inc(longInt(ErrorR0), sizeof(TErrorTerm));
inc(longInt(ErrorG0), sizeof(TErrorTerm));
inc(longInt(ErrorB0), sizeof(TErrorTerm));
inc(longInt(ErrorR1), sizeof(TErrorTerm));
inc(longInt(ErrorG1), sizeof(TErrorTerm));
inc(longInt(ErrorB1), sizeof(TErrorTerm));
inc(longInt(ErrorR2), sizeof(TErrorTerm));
inc(longInt(ErrorG2), sizeof(TErrorTerm));
inc(longInt(ErrorB2), sizeof(TErrorTerm));
inc(longInt(ErrorR3), sizeof(TErrorTerm));
inc(longInt(ErrorG3), sizeof(TErrorTerm));
inc(longInt(ErrorB3), sizeof(TErrorTerm));
end else
begin
dec(longInt(ErrorR0), sizeof(TErrorTerm));
dec(longInt(ErrorG0), sizeof(TErrorTerm));
dec(longInt(ErrorB0), sizeof(TErrorTerm));
dec(longInt(ErrorR1), sizeof(TErrorTerm));
dec(longInt(ErrorG1), sizeof(TErrorTerm));
dec(longInt(ErrorB1), sizeof(TErrorTerm));
dec(longInt(ErrorR2), sizeof(TErrorTerm));
dec(longInt(ErrorG2), sizeof(TErrorTerm));
dec(longInt(ErrorB2), sizeof(TErrorTerm));
dec(longInt(ErrorR3), sizeof(TErrorTerm));
dec(longInt(ErrorG3), sizeof(TErrorTerm));
dec(longInt(ErrorB3), sizeof(TErrorTerm));
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
procedure TSteveArcheDitherer.NextLine;
var
TempErrors : PErrors;
begin
FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+6), 0);
FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+6), 0);
// Swap lines
TempErrors := ErrorsR0;
ErrorsR0 := ErrorsR1;
ErrorsR1 := ErrorsR2;
ErrorsR2 := ErrorsR3;
ErrorsR3 := TempErrors;
TempErrors := ErrorsG0;
ErrorsG0 := ErrorsG1;
ErrorsG1 := ErrorsG2;
ErrorsG2 := ErrorsG3;
ErrorsG3 := TempErrors;
TempErrors := ErrorsB0;
ErrorsB0 := ErrorsB1;
ErrorsB1 := ErrorsB2;
ErrorsB2 := ErrorsB3;
ErrorsB3 := TempErrors;
inherited NextLine;
FDirection2 := 2 * Direction;
FDirection3 := 3 * Direction;
if (Direction = 1) then
begin
// ErrorsR0[1] gives compiler error, so we
// use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead...
ErrorR0 := PErrors(longInt(ErrorsR0)+3*sizeof(TErrorTerm));
ErrorG0 := PErrors(longInt(ErrorsG0)+3*sizeof(TErrorTerm));
ErrorB0 := PErrors(longInt(ErrorsB0)+3*sizeof(TErrorTerm));
ErrorR1 := PErrors(longInt(ErrorsR1)+3*sizeof(TErrorTerm));
ErrorG1 := PErrors(longInt(ErrorsG1)+3*sizeof(TErrorTerm));
ErrorB1 := PErrors(longInt(ErrorsB1)+3*sizeof(TErrorTerm));
ErrorR2 := PErrors(longInt(ErrorsR2)+3*sizeof(TErrorTerm));
ErrorG2 := PErrors(longInt(ErrorsG2)+3*sizeof(TErrorTerm));
ErrorB2 := PErrors(longInt(ErrorsB2)+3*sizeof(TErrorTerm));
ErrorR3 := PErrors(longInt(ErrorsR3)+3*sizeof(TErrorTerm));
ErrorG3 := PErrors(longInt(ErrorsG3)+3*sizeof(TErrorTerm));
ErrorB3 := PErrors(longInt(ErrorsB3)+3*sizeof(TErrorTerm));
end else
begin
ErrorR0 := @ErrorsR0[Width+2];
ErrorG0 := @ErrorsG0[Width+2];
ErrorB0 := @ErrorsB0[Width+2];
ErrorR1 := @ErrorsR1[Width+2];
ErrorG1 := @ErrorsG1[Width+2];
ErrorB1 := @ErrorsB1[Width+2];
ErrorR2 := @ErrorsR2[Width+2];
ErrorG2 := @ErrorsG2[Width+2];
ErrorB2 := @ErrorsB2[Width+2];
ErrorR3 := @ErrorsR2[Width+2];
ErrorG3 := @ErrorsG2[Width+2];
ErrorB3 := @ErrorsB2[Width+2];
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
// TBurkesDitherer
constructor TBurkesDitherer.Create(AWidth: integer; Lookup: TColorLookup);
begin
inherited Create(AWidth, Lookup);
GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+4));
GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+4));
FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+4), 0);
FDirection2 := 2 * Direction;
ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm));
ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm));
ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm));
ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm));
ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm));
ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm));
end;
destructor TBurkesDitherer.Destroy;
begin
FreeMem(ErrorsR0);
FreeMem(ErrorsG0);
FreeMem(ErrorsB0);
FreeMem(ErrorsR1);
FreeMem(ErrorsG1);
FreeMem(ErrorsB1);
inherited Destroy;
end;
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
function TBurkesDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
var
ErrorR ,
ErrorG ,
ErrorB : integer; // Error for current pixel
// Propagate Burkes error terms:
// ... ... (here) 8/32 4/32
// 2/32 4/32 8/32 4/32 2/32
procedure Propagate(Errors0, Errors1: PErrors; Error: integer);
begin
if (Error = 0) then
exit;
inc(Error, Error);
inc(Errors1[FDirection2], Error); // Error * 2
inc(Errors1[-FDirection2], Error); // Error * 2
inc(Error, Error);
inc(Errors0[FDirection2], Error); // Error * 4
inc(Errors1[-Direction], Error); // Error * 4
inc(Errors1[Direction], Error); // Error * 4
inc(Error, Error);
inc(Errors0[Direction], Error); // Error * 8
inc(Errors1[0], Error); // Error * 8
end;
begin
// Apply red component error correction
ErrorR := Red + (ErrorR0[0] + 16) DIV 32;
if (ErrorR < 0) then
ErrorR := 0
else if (ErrorR > 255) then
ErrorR := 255;
// Apply green component error correction
ErrorG := Green + (ErrorG0[0] + 16) DIV 32;
if (ErrorG < 0) then
ErrorG := 0
else if (ErrorG > 255) then
ErrorG := 255;
// Apply blue component error correction
ErrorB := Blue + (ErrorB0[0] + 16) DIV 32;
if (ErrorB < 0) then
ErrorB := 0
else if (ErrorB > 255) then
ErrorB := 255;
// Map color to palette
Result := inherited Dither(ErrorR, ErrorG, ErrorB, R, G, B);
// Propagate red component error
Propagate(ErrorR0, ErrorR1, ErrorR - R);
// Propagate green component error
Propagate(ErrorG0, ErrorG1, ErrorG - G);
// Propagate blue component error
Propagate(ErrorB0, ErrorB1, ErrorB - B);
// Move on to next column
if (Direction = 1) then
begin
inc(longInt(ErrorR0), sizeof(TErrorTerm));
inc(longInt(ErrorG0), sizeof(TErrorTerm));
inc(longInt(ErrorB0), sizeof(TErrorTerm));
inc(longInt(ErrorR1), sizeof(TErrorTerm));
inc(longInt(ErrorG1), sizeof(TErrorTerm));
inc(longInt(ErrorB1), sizeof(TErrorTerm));
end else
begin
dec(longInt(ErrorR0), sizeof(TErrorTerm));
dec(longInt(ErrorG0), sizeof(TErrorTerm));
dec(longInt(ErrorB0), sizeof(TErrorTerm));
dec(longInt(ErrorR1), sizeof(TErrorTerm));
dec(longInt(ErrorG1), sizeof(TErrorTerm));
dec(longInt(ErrorB1), sizeof(TErrorTerm));
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
procedure TBurkesDitherer.NextLine;
var
TempErrors : PErrors;
begin
FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0);
FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0);
// Swap lines
TempErrors := ErrorsR0;
ErrorsR0 := ErrorsR1;
ErrorsR1 := TempErrors;
TempErrors := ErrorsG0;
ErrorsG0 := ErrorsG1;
ErrorsG1 := TempErrors;
TempErrors := ErrorsB0;
ErrorsB0 := ErrorsB1;
ErrorsB1 := TempErrors;
inherited NextLine;
FDirection2 := 2 * Direction;
if (Direction = 1) then
begin
// ErrorsR0[1] gives compiler error, so we
// use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead...
ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm));
ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm));
ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm));
ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm));
ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm));
ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm));
end else
begin
ErrorR0 := @ErrorsR0[Width+1];
ErrorG0 := @ErrorsG0[Width+1];
ErrorB0 := @ErrorsB0[Width+1];
ErrorR1 := @ErrorsR1[Width+1];
ErrorG1 := @ErrorsG1[Width+1];
ErrorB1 := @ErrorsB1[Width+1];
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
//
// Octree Color Quantization Engine
//
////////////////////////////////////////////////////////////////////////////////
// Adapted from Earl F. Glynn's ColorQuantizationLibrary, March 1998
////////////////////////////////////////////////////////////////////////////////
type
TOctreeNode = class; // Forward definition so TReducibleNodes can be declared
TReducibleNodes = array[0..7] of TOctreeNode;
TOctreeNode = Class(TObject)
public
IsLeaf : Boolean;
PixelCount : integer;
RedSum : integer;
GreenSum : integer;
BlueSum : integer;
Next : TOctreeNode;
Child : TReducibleNodes;
constructor Create(Level: integer; ColorBits: integer; var LeafCount: integer;
var ReducibleNodes: TReducibleNodes);
destructor Destroy; override;
end;
TColorQuantizer = class(TObject)
private
FTree : TOctreeNode;
FLeafCount : integer;
FReducibleNodes : TReducibleNodes;
FMaxColors : integer;
FColorBits : integer;
protected
procedure AddColor(var Node: TOctreeNode; r, g, b: byte; ColorBits: integer;
Level: integer; var LeafCount: integer; var ReducibleNodes: TReducibleNodes);
procedure DeleteTree(var Node: TOctreeNode);
procedure GetPaletteColors(const Node: TOctreeNode;
var RGBQuadArray: TRGBQuadArray; var Index: integer);
procedure ReduceTree(ColorBits: integer; var LeafCount: integer;
var ReducibleNodes: TReducibleNodes);
public
constructor Create(MaxColors: integer; ColorBits: integer);
destructor Destroy; override;
procedure GetColorTable(var RGBQuadArray: TRGBQuadArray);
function ProcessImage(const DIB: TDIBReader): boolean;
property ColorCount: integer read FLeafCount;
end;
constructor TOctreeNode.Create(Level: integer; ColorBits: 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 = ColorBits);
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 := High(Child) downto Low(Child) do
Child[i].Free;
end;
constructor TColorQuantizer.Create(MaxColors: integer; ColorBits: integer);
var
i : integer;
begin
ASSERT(ColorBits <= 8, 'ColorBits must be 8 or less');
FTree := nil;
FLeafCount := 0;
// Initialize all nodes even though only ColorBits+1 of them are needed
for i := Low(FReducibleNodes) to High(FReducibleNodes) do
FReducibleNodes[i] := nil;
FMaxColors := MaxColors;
FColorBits := ColorBits;
end;
destructor TColorQuantizer.Destroy;
begin
if (FTree <> nil) then
DeleteTree(FTree);
end;
procedure TColorQuantizer.GetColorTable(var RGBQuadArray: TRGBQuadArray);
var
Index : integer;
begin
Index := 0;
GetPaletteColors(FTree, RGBQuadArray, Index);
end;
// Handles passed to ProcessImage should refer to DIB sections, not DDBs.
// In certain cases, specifically when it's called upon to process 1, 4, or
// 8-bit per pixel images on systems with palettized display adapters,
// ProcessImage can produce incorrect results if it's passed a handle to a
// DDB.
function TColorQuantizer.ProcessImage(const DIB: TDIBReader): boolean;
var
i ,
j : integer;
ScanLine : pointer;
Pixel : PRGBTriple;
begin
Result := True;
for j := 0 to DIB.Bitmap.Height-1 do
begin
Scanline := DIB.Scanline[j];
Pixel := ScanLine;
for i := 0 to DIB.Bitmap.Width-1 do
begin
with Pixel^ do
AddColor(FTree, rgbtRed, rgbtGreen, rgbtBlue,
FColorBits, 0, FLeafCount, FReducibleNodes);
while FLeafCount > FMaxColors do
ReduceTree(FColorbits, FLeafCount, FReducibleNodes);
inc(Pixel);
end;
end;
end;
procedure TColorQuantizer.AddColor(var Node: TOctreeNode; r,g,b: byte;
ColorBits: integer; Level: integer; var LeafCount: integer;
var ReducibleNodes: TReducibleNodes);
const
Mask: array[0..7] of BYTE = ($80, $40, $20, $10, $08, $04, $02, $01);
var
Index : integer;
Shift : integer;
begin
// If the node doesn't exist, create it.
if (Node = nil) then
Node := TOctreeNode.Create(Level, ColorBits, 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
// Recurse a level deeper if the node is not a leaf.
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, ColorBits, Level+1, LeafCount, ReducibleNodes);
end;
end;
procedure TColorQuantizer.DeleteTree(var Node: TOctreeNode);
var
i : integer;
begin
for i := High(TReducibleNodes) downto Low(TReducibleNodes) do
if (Node.Child[i] <> nil) then
DeleteTree(Node.Child[i]);
Node.Free;
Node := nil;
end;
procedure TColorQuantizer.GetPaletteColors(const Node: TOctreeNode;
var RGBQuadArray: TRGBQuadArray; var Index: integer);
var
i : integer;
begin
if (Node.IsLeaf) then
begin
with RGBQuadArray[Index] do
begin
if (Node.PixelCount <> 0) then
begin
rgbRed := BYTE(Node.RedSum DIV Node.PixelCount);
rgbGreen := BYTE(Node.GreenSum DIV Node.PixelCount);
rgbBlue := BYTE(Node.BlueSum DIV Node.PixelCount);
end else
begin
rgbRed := 0;
rgbGreen := 0;
rgbBlue := 0;
end;
rgbReserved := 0;
end;
inc(Index);
end else
begin
for i := Low(Node.Child) to High(Node.Child) do
if (Node.Child[i] <> nil) then
GetPaletteColors(Node.Child[i], RGBQuadArray, Index);
end;
end;
procedure TColorQuantizer.ReduceTree(ColorBits: integer; var LeafCount: integer;
var ReducibleNodes: TReducibleNodes);
var
RedSum ,
GreenSum ,
BlueSum : integer;
Children : integer;
i : integer;
Node : TOctreeNode;
begin
// Find the deepest level containing at least one reducible node
i := Colorbits - 1;
while (i > 0) and (ReducibleNodes[i] = nil) do
dec(i);
// Reduce the node most recently added to the list at level 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;
////////////////////////////////////////////////////////////////////////////////
//
// Octree Color Quantization Wrapper
//
////////////////////////////////////////////////////////////////////////////////
// Adapted from Earl F. Glynn's PaletteLibrary, March 1998
////////////////////////////////////////////////////////////////////////////////
// Wrapper for internal use - uses TDIBReader for bitmap access
function doCreateOptimizedPaletteFromSingleBitmap(const DIB: TDIBReader;
Colors, ColorBits: integer; Windows: boolean): hPalette;
var
SystemPalette : HPalette;
ColorQuantizer : TColorQuantizer;
i : integer;
LogicalPalette : TMaxLogPalette;
RGBQuadArray : TRGBQuadArray;
Offset : integer;
begin
LogicalPalette.palVersion := $0300;
LogicalPalette.palNumEntries := Colors;
if (Windows) then
begin
// Get the windows 20 color system palette
SystemPalette := GetStockObject(DEFAULT_PALETTE);
GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]);
GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[245]);
Colors := 236;
Offset := 10;
LogicalPalette.palNumEntries := 256;
end else
Offset := 0;
// Normally for 24-bit images, use ColorBits of 5 or 6. For 8-bit images
// use ColorBits = 8.
ColorQuantizer := TColorQuantizer.Create(Colors, ColorBits);
try
ColorQuantizer.ProcessImage(DIB);
ColorQuantizer.GetColorTable(RGBQuadArray);
finally
ColorQuantizer.Free;
end;
for i := 0 to Colors-1 do
with LogicalPalette.palPalEntry[i+Offset] do
begin
peRed := RGBQuadArray[i].rgbRed;
peGreen := RGBQuadArray[i].rgbGreen;
peBlue := RGBQuadArray[i].rgbBlue;
peFlags := RGBQuadArray[i].rgbReserved;
end;
Result := CreatePalette(pLogPalette(@LogicalPalette)^);
end;
function CreateOptimizedPaletteFromSingleBitmap(const Bitmap: TBitmap;
Colors, ColorBits: integer; Windows: boolean): hPalette;
var
DIB : TDIBReader;
begin
DIB := TDIBReader.Create(Bitmap, pf24bit);
try
Result := doCreateOptimizedPaletteFromSingleBitmap(DIB, Colors, ColorBits, Windows);
finally
DIB.Free;
end;
end;
function CreateOptimizedPaletteFromManyBitmaps(Bitmaps: TList; Colors, ColorBits: integer;
Windows: boolean): hPalette;
var
SystemPalette : HPalette;
ColorQuantizer : TColorQuantizer;
i : integer;
LogicalPalette : TMaxLogPalette;
RGBQuadArray : TRGBQuadArray;
Offset : integer;
DIB : TDIBReader;
begin
if (Bitmaps = nil) or (Bitmaps.Count = 0) then
Error(sInvalidBitmapList);
LogicalPalette.palVersion := $0300;
LogicalPalette.palNumEntries := Colors;
if (Windows) then
begin
// Get the windows 20 color system palette
SystemPalette := GetStockObject(DEFAULT_PALETTE);
GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]);
GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[245]);
Colors := 236;
Offset := 10;
LogicalPalette.palNumEntries := 256;
end else
Offset := 0;
// Normally for 24-bit images, use ColorBits of 5 or 6. For 8-bit images
// use ColorBits = 8.
ColorQuantizer := TColorQuantizer.Create(Colors, ColorBits);
try
for i := 0 to Bitmaps.Count-1 do
begin
DIB := TDIBReader.Create(TBitmap(Bitmaps[i]), pf24bit);
try
ColorQuantizer.ProcessImage(DIB);
finally
DIB.Free;
end;
end;
ColorQuantizer.GetColorTable(RGBQuadArray);
finally
ColorQuantizer.Free;
end;
for i := 0 to Colors-1 do
with LogicalPalette.palPalEntry[i+Offset] do
begin
peRed := RGBQuadArray[i].rgbRed;
peGreen := RGBQuadArray[i].rgbGreen;
peBlue := RGBQuadArray[i].rgbBlue;
peFlags := RGBQuadArray[i].rgbReserved;
end;
Result := CreatePalette(pLogPalette(@LogicalPalette)^);
end;
////////////////////////////////////////////////////////////////////////////////
//
// Color reduction
//
////////////////////////////////////////////////////////////////////////////////
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
//: Reduces the color depth of a bitmap using color quantization and dithering.
function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction;
DitherMode: TDitherMode; ReductionBits: integer; CustomPalette: hPalette): TBitmap;
var
Palette : hPalette;
ColorLookup : TColorLookup;
Ditherer : TDitherEngine;
Row : Integer;
DIBResult : TDIBWriter;
DIBSource : TDIBReader;
SrcScanLine ,
Src : PRGBTriple;
DstScanLine ,
Dst : PChar;
BGR : TRGBTriple;
{$ifdef DEBUG_DITHERPERFORMANCE}
TimeStart ,
TimeStop : DWORD;
{$endif}
function GrayScalePalette: hPalette;
var
i : integer;
Pal : TMaxLogPalette;
begin
Pal.palVersion := $0300;
Pal.palNumEntries := 256;
for i := 0 to 255 do
begin
with (Pal.palPalEntry[i]) do
begin
peRed := i;
peGreen := i;
peBlue := i;
peFlags := PC_NOCOLLAPSE;
end;
end;
Result := CreatePalette(pLogPalette(@Pal)^);
end;
function MonochromePalette: hPalette;
var
i : integer;
Pal : TMaxLogPalette;
const
Values : array[0..1] of byte
= (0, 255);
begin
Pal.palVersion := $0300;
Pal.palNumEntries := 2;
for i := 0 to 1 do
begin
with (Pal.palPalEntry[i]) do
begin
peRed := Values[i];
peGreen := Values[i];
peBlue := Values[i];
peFlags := PC_NOCOLLAPSE;
end;
end;
Result := CreatePalette(pLogPalette(@Pal)^);
end;
function WindowsGrayScalePalette: hPalette;
var
i : integer;
Pal : TMaxLogPalette;
const
Values : array[0..3] of byte
= (0, 128, 192, 255);
begin
Pal.palVersion := $0300;
Pal.palNumEntries := 4;
for i := 0 to 3 do
begin
with (Pal.palPalEntry[i]) do
begin
peRed := Values[i];
peGreen := Values[i];
peBlue := Values[i];
peFlags := PC_NOCOLLAPSE;
end;
end;
Result := CreatePalette(pLogPalette(@Pal)^);
end;
function WindowsHalftonePalette: hPalette;
var
DC : HDC;
begin
DC := GDICheck(GetDC(0));
try
Result := CreateHalfTonePalette(DC);
finally
ReleaseDC(0, DC);
end;
end;
begin
{$ifdef DEBUG_DITHERPERFORMANCE}
timeBeginPeriod(5);
TimeStart := timeGetTime;
{$endif}
Result := TBitmap.Create;
try
if (ColorReduction = rmNone) then
begin
Result.Assign(Bitmap);
{$ifndef VER9x}
SetPixelFormat(Result, pf24bit);
{$endif}
exit;
end;
{$IFNDEF VER9x}
if (Bitmap.Width*Bitmap.Height > BitmapAllocationThreshold) then
SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize
{$ENDIF}
ColorLookup := nil;
Ditherer := nil;
DIBResult := nil;
DIBSource := nil;
Palette := 0;
try // Protect above resources
// Dithering and color mapper only supports 24 bit bitmaps,
// so we have convert the source bitmap to the appropiate format.
DIBSource := TDIBReader.Create(Bitmap, pf24bit);
// Create a palette based on current options
case (ColorReduction) of
rmQuantize:
Palette := doCreateOptimizedPaletteFromSingleBitmap(DIBSource, 1 SHL ReductionBits, 8, False);
rmQuantizeWindows:
Palette := CreateOptimizedPaletteFromSingleBitmap(Bitmap, 256, 8, True);
rmNetscape:
Palette := WebPalette;
rmGrayScale:
Palette := GrayScalePalette;
rmMonochrome:
Palette := MonochromePalette;
rmWindowsGray:
Palette := WindowsGrayScalePalette;
rmWindows20:
Palette := GetStockObject(DEFAULT_PALETTE);
rmWindows256:
Palette := WindowsHalftonePalette;
rmPalette:
Palette := CopyPalette(CustomPalette);
else
exit;
end;
{ TODO -oanme -cImprovement : Gray scale conversion should be done prior to dithering/mapping. Otherwise corrected values will be converted multiple times. }
// Create a color mapper based on current options
case (ColorReduction) of
// For some strange reason my fast and dirty color lookup
// is more precise that Windows GetNearestPaletteIndex...
// rmWindows20:
// ColorLookup := TSlowColorLookup.Create(Palette);
// rmWindowsGray:
// ColorLookup := TGrayWindowsLookup.Create(Palette);
rmQuantize:
ColorLookup := TFastColorLookup.Create(Palette);
rmNetscape:
ColorLookup := TNetscapeColorLookup.Create(Palette);
rmGrayScale:
ColorLookup := TGrayScaleLookup.Create(Palette);
rmMonochrome:
ColorLookup := TMonochromeLookup.Create(Palette);
else
ColorLookup := TFastColorLookup.Create(Palette);
end;
// Nothing to do if palette doesn't contain any colors
if (ColorLookup.Colors = 0) then
exit;
// Create a ditherer based on current options
case (DitherMode) of
dmNearest:
Ditherer := TDitherEngine.Create(Bitmap.Width, ColorLookup);
dmFloydSteinberg:
Ditherer := TFloydSteinbergDitherer.Create(Bitmap.Width, ColorLookup);
dmStucki:
Ditherer := TStuckiDitherer.Create(Bitmap.Width, ColorLookup);
dmSierra:
Ditherer := TSierraDitherer.Create(Bitmap.Width, ColorLookup);
dmJaJuNI:
Ditherer := TJaJuNIDitherer.Create(Bitmap.Width, ColorLookup);
dmSteveArche:
Ditherer := TSteveArcheDitherer.Create(Bitmap.Width, ColorLookup);
dmBurkes:
Ditherer := TBurkesDitherer.Create(Bitmap.Width, ColorLookup);
else
exit;
end;
// The processed bitmap is returned in pf8bit format
DIBResult := TDIBWriter.Create(Result, pf8bit, Bitmap.Width, Bitmap.Height,
Palette);
// Process the image
Row := 0;
while (Row < Bitmap.Height) do
begin
SrcScanline := DIBSource.ScanLine[Row];
DstScanline := DIBResult.ScanLine[Row];
Src := pointer(longInt(SrcScanLine) + Ditherer.Column*sizeof(TRGBTriple));
Dst := pointer(longInt(DstScanLine) + Ditherer.Column);
while (Ditherer.Column < Ditherer.Width) and (Ditherer.Column >= 0) do
begin
BGR := Src^;
// Dither and map a single pixel
Dst^ := Ditherer.Dither(BGR.rgbtRed, BGR.rgbtGreen, BGR.rgbtBlue,
BGR.rgbtRed, BGR.rgbtGreen, BGR.rgbtBlue);
inc(Src, Ditherer.Direction);
inc(Dst, Ditherer.Direction);
end;
Inc(Row);
Ditherer.NextLine;
end;
finally
if (ColorLookup <> nil) then
ColorLookup.Free;
if (Ditherer <> nil) then
Ditherer.Free;
if (DIBResult <> nil) then
DIBResult.Free;
if (DIBSource <> nil) then
DIBSource.Free;
// Must delete palette after TDIBWriter since TDIBWriter uses palette
if (Palette <> 0) then
DeleteObject(Palette);
end;
except
Result.Free;
raise;
end;
{$ifdef DEBUG_DITHERPERFORMANCE}
TimeStop := timeGetTime;
ShowMessage(format('Dithered %d pixels in %d mS, Rate %d pixels/mS (%d pixels/S)',
[Bitmap.Height*Bitmap.Width, TimeStop-TimeStart,
MulDiv(Bitmap.Height, Bitmap.Width, TimeStop-TimeStart+1),
MulDiv(Bitmap.Height, Bitmap.Width * 1000, TimeStop-TimeStart+1)]));
timeEndPeriod(5);
{$endif}
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
//
// TGIFColorMap
//
////////////////////////////////////////////////////////////////////////////////
const
InitColorMapSize = 16;
DeltaColorMapSize = 32;
//: Creates an instance of a TGIFColorMap object.
constructor TGIFColorMap.Create;
begin
inherited Create;
FColorMap := nil;
FCapacity := 0;
FCount := 0;
FOptimized := False;
end;
//: Destroys an instance of a TGIFColorMap object.
destructor TGIFColorMap.Destroy;
begin
Clear;
Changed;
inherited Destroy;
end;
//: Empties the color map.
procedure TGIFColorMap.Clear;
begin
if (FColorMap <> nil) then
FreeMem(FColorMap);
FColorMap := nil;
FCapacity := 0;
FCount := 0;
FOptimized := False;
end;
//: Converts a Windows color value to a RGB value.
class function TGIFColorMap.Color2RGB(Color: TColor): TGIFColor;
begin
Result.Blue := (Color shr 16) and $FF;
Result.Green := (Color shr 8) and $FF;
Result.Red := Color and $FF;
end;
//: Converts a RGB value to a Windows color value.
class function TGIFColorMap.RGB2Color(Color: TGIFColor): TColor;
begin
Result := (Color.Blue SHL 16) OR (Color.Green SHL 8) OR Color.Red;
end;
//: Saves the color map to a stream.
procedure TGIFColorMap.SaveToStream(Stream: TStream);
var
Dummies : integer;
Dummy : TGIFColor;
begin
if (FCount = 0) then
exit;
Stream.WriteBuffer(FColorMap^, FCount*sizeof(TGIFColor));
Dummies := (1 SHL BitsPerPixel)-FCount;
Dummy.Red := 0;
Dummy.Green := 0;
Dummy.Blue := 0;
while (Dummies > 0) do
begin
Stream.WriteBuffer(Dummy, sizeof(TGIFColor));
dec(Dummies);
end;
end;
//: Loads the color map from a stream.
procedure TGIFColorMap.LoadFromStream(Stream: TStream; Count: integer);
begin
Clear;
SetCapacity(Count);
ReadCheck(Stream, FColorMap^, Count*sizeof(TGIFColor));
FCount := Count;
end;
//: Returns the position of a color in the color map.
function TGIFColorMap.IndexOf(Color: TColor): integer;
var
RGB : TGIFColor;
begin
RGB := Color2RGB(Color);
if (FOptimized) then
begin
// Optimized palette has most frequently occuring entries first
Result := 0;
// Reverse search to (hopefully) check latest colors first
while (Result < FCount) do
with (FColorMap^[Result]) do
begin
if (RGB.Red = Red) and (RGB.Green = Green) and (RGB.Blue = Blue) then
exit;
Inc(Result);
end;
Result := -1;
end else
begin
Result := FCount-1;
// Reverse search to (hopefully) check latest colors first
while (Result >= 0) do
with (FColorMap^[Result]) do
begin
if (RGB.Red = Red) and (RGB.Green = Green) and (RGB.Blue = Blue) then
exit;
Dec(Result);
end;
end;
end;
procedure TGIFColorMap.SetCapacity(Size: integer);
begin
if (Size >= FCapacity) then
begin
if (Size <= InitColorMapSize) then
FCapacity := InitColorMapSize
else
FCapacity := (Size + DeltaColorMapSize - 1) DIV DeltaColorMapSize * DeltaColorMapSize;
if (FCapacity > GIFMaxColors) then
FCapacity := GIFMaxColors;
ReallocMem(FColorMap, FCapacity * sizeof(TGIFColor));
end;
end;
//: Imports a Windows palette into the color map.
procedure TGIFColorMap.ImportPalette(Palette: HPalette);
type
PalArray = array[byte] of TPaletteEntry;
var
Pal : PalArray;
NewCount : integer;
i : integer;
begin
Clear;
NewCount := GetPaletteEntries(Palette, 0, 256, pal);
if (NewCount = 0) then
exit;
SetCapacity(NewCount);
for i := 0 to NewCount-1 do
with FColorMap[i], Pal[i] do
begin
Red := peRed;
Green := peGreen;
Blue := peBlue;
end;
FCount := NewCount;
Changed;
end;
//: Imports a color map structure into the color map.
procedure TGIFColorMap.ImportColorMap(Map: TColorMap; Count: integer);
begin
Clear;
if (Count = 0) then
exit;
SetCapacity(Count);
FCount := Count;
System.Move(Map, FColorMap^, FCount * sizeof(TGIFColor));
Changed;
end;
//: Imports a Windows palette structure into the color map.
procedure TGIFColorMap.ImportColorTable(Pal: pointer; Count: integer);
var
i : integer;
begin
Clear;
if (Count = 0) then
exit;
SetCapacity(Count);
for i := 0 to Count-1 do
with FColorMap[i], PRGBQuadArray(Pal)[i] do
begin
Red := rgbRed;
Green := rgbGreen;
Blue := rgbBlue;
end;
FCount := Count;
Changed;
end;
//: Imports the color table of a DIB into the color map.
procedure TGIFColorMap.ImportDIBColors(Handle: HDC);
var
Pal : Pointer;
NewCount : integer;
begin
Clear;
GetMem(Pal, sizeof(TRGBQuad) * 256);
try
NewCount := GetDIBColorTable(Handle, 0, 256, Pal^);
ImportColorTable(Pal, NewCount);
finally
FreeMem(Pal);
end;
Changed;
end;
//: Creates a Windows palette from the color map.
function TGIFColorMap.ExportPalette: HPalette;
var
Pal : TMaxLogPalette;
i : Integer;
begin
if (Count = 0) then
begin
Result := 0;
exit;
end;
Pal.palVersion := $300;
Pal.palNumEntries := Count;
for i := 0 to Count-1 do
with FColorMap[i], Pal.palPalEntry[i] do
begin
peRed := Red;
peGreen := Green;
peBlue := Blue;
peFlags := PC_NOCOLLAPSE; { TODO -oanme -cImprovement : Verify that PC_NOCOLLAPSE is the correct value to use. }
end;
Result := CreatePalette(PLogPalette(@Pal)^);
end;
//: Adds a color to the color map.
function TGIFColorMap.Add(Color: TColor): integer;
begin
if (FCount >= GIFMaxColors) then
// Color map full
Error(sTooManyColors);
Result := FCount;
if (Result >= FCapacity) then
SetCapacity(FCount+1);
FColorMap^[FCount] := Color2RGB(Color);
inc(FCount);
FOptimized := False;
Changed;
end;
function TGIFColorMap.AddUnique(Color: TColor): integer;
begin
// Look up color before add (same as IndexOf)
Result := IndexOf(Color);
if (Result >= 0) then
// Color already in map
exit;
Result := Add(Color);
end;
//: Removes a color from the color map.
procedure TGIFColorMap.Delete(Index: integer);
begin
if (Index < 0) or (Index >= FCount) then
// Color index out of range
Error(sBadColorIndex);
dec(FCount);
if (Index < FCount) then
System.Move(FColorMap^[Index + 1], FColorMap^[Index], (FCount - Index)* sizeof(TGIFColor));
FOptimized := False;
Changed;
end;
function TGIFColorMap.GetColor(Index: integer): TColor;
begin
if (Index < 0) or (Index >= FCount) then
begin
// Color index out of range
Warning(gsWarning, sBadColorIndex);
// Raise an exception if the color map is empty
if (FCount = 0) then
Error(sEmptyColorMap);
// Default to color index 0
Index := 0;
end;
Result := RGB2Color(FColorMap^[Index]);
end;
procedure TGIFColorMap.SetColor(Index: integer; Value: TColor);
begin
if (Index < 0) or (Index >= FCount) then
// Color index out of range
Error(sBadColorIndex);
FColorMap^[Index] := Color2RGB(Value);
Changed;
end;
function TGIFColorMap.DoOptimize: boolean;
var
Usage : TColormapHistogram;
TempMap : array[0..255] of TGIFColor;
ReverseMap : TColormapReverse;
i : integer;
LastFound : boolean;
NewCount : integer;
T : TUsageCount;
Pivot : integer;
procedure QuickSort(iLo, iHi: Integer);
var
Lo, Hi: Integer;
begin
repeat
Lo := iLo;
Hi := iHi;
Pivot := Usage[(iLo + iHi) SHR 1].Count;
repeat
while (Usage[Lo].Count - Pivot > 0) do inc(Lo);
while (Usage[Hi].Count - Pivot < 0) do dec(Hi);
if (Lo <= Hi) then
begin
T := Usage[Lo];
Usage[Lo] := Usage[Hi];
Usage[Hi] := T;
inc(Lo);
dec(Hi);
end;
until (Lo > Hi);
if (iLo < Hi) then
QuickSort(iLo, Hi);
iLo := Lo;
until (Lo >= iHi);
end;
begin
if (FCount <= 1) then
begin
Result := False;
exit;
end;
FOptimized := True;
Result := True;
BuildHistogram(Usage);
(*
** Sort according to usage count
*)
QuickSort(0, FCount-1);
(*
** Test for table already sorted
*)
for i := 0 to FCount-1 do
if (Usage[i].Index <> i) then
break;
if (i = FCount) then
exit;
(*
** Build old to new map
*)
for i := 0 to FCount-1 do
ReverseMap[Usage[i].Index] := i;
MapImages(ReverseMap);
(*
** Reorder colormap
*)
LastFound := False;
NewCount := FCount;
Move(FColorMap^, TempMap, FCount * sizeof(TGIFColor));
for i := 0 to FCount-1 do
begin
FColorMap^[ReverseMap[i]] := TempMap[i];
// Find last used color index
if (Usage[i].Count = 0) and not(LastFound) then
begin
LastFound := True;
NewCount := i;
end;
end;
FCount := NewCount;
Changed;
end;
function TGIFColorMap.GetBitsPerPixel: integer;
begin
Result := Colors2bpp(FCount);
end;
//: Copies one color map to another.
procedure TGIFColorMap.Assign(Source: TPersistent);
begin
if (Source is TGIFColorMap) then
begin
Clear;
FCapacity := TGIFColorMap(Source).FCapacity;
FCount := TGIFColorMap(Source).FCount;
FOptimized := TGIFColorMap(Source).FOptimized;
FColorMap := AllocMem(FCapacity * sizeof(TGIFColor));
System.Move(TGIFColorMap(Source).FColorMap^, FColorMap^, FCount * sizeof(TGIFColor));
Changed;
end else
inherited Assign(Source);
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFItem
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFItem.Create(GIFImage: TGIFImage);
begin
inherited Create;
FGIFImage := GIFImage;
end;
procedure TGIFItem.Warning(Severity: TGIFSeverity; Message: string);
begin
FGIFImage.Warning(self, Severity, Message);
end;
function TGIFItem.GetVersion: TGIFVersion;
begin
Result := gv87a;
end;
procedure TGIFItem.LoadFromFile(const Filename: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(Filename, fmOpenRead OR fmShareDenyWrite);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure TGIFItem.SaveToFile(const Filename: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(Filename, fmCreate);
try
SaveToStream(Stream);
finally
Stream.Free;
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFList
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFList.Create(Image: TGIFImage);
begin
inherited Create;
FImage := Image;
FItems := TList.Create;
end;
destructor TGIFList.Destroy;
begin
Clear;
FItems.Free;
inherited Destroy;
end;
function TGIFList.GetItem(Index: Integer): TGIFItem;
begin
Result := TGIFItem(FItems[Index]);
end;
procedure TGIFList.SetItem(Index: Integer; Item: TGIFItem);
begin
FItems[Index] := Item;
end;
function TGIFList.GetCount: Integer;
begin
Result := FItems.Count;
end;
function TGIFList.Add(Item: TGIFItem): Integer;
begin
Result := FItems.Add(Item);
end;
procedure TGIFList.Clear;
begin
while (FItems.Count > 0) do
Delete(0);
end;
procedure TGIFList.Delete(Index: Integer);
var
Item : TGIFItem;
begin
Item := TGIFItem(FItems[Index]);
// Delete before item is destroyed to avoid recursion
FItems.Delete(Index);
Item.Free;
end;
procedure TGIFList.Exchange(Index1, Index2: Integer);
begin
FItems.Exchange(Index1, Index2);
end;
function TGIFList.First: TGIFItem;
begin
Result := TGIFItem(FItems.First);
end;
function TGIFList.IndexOf(Item: TGIFItem): Integer;
begin
Result := FItems.IndexOf(Item);
end;
procedure TGIFList.Insert(Index: Integer; Item: TGIFItem);
begin
FItems.Insert(Index, Item);
end;
function TGIFList.Last: TGIFItem;
begin
Result := TGIFItem(FItems.Last);
end;
procedure TGIFList.Move(CurIndex, NewIndex: Integer);
begin
FItems.Move(CurIndex, NewIndex);
end;
function TGIFList.Remove(Item: TGIFItem): Integer;
begin
// Note: TGIFList.Remove must not destroy item
Result := FItems.Remove(Item);
end;
procedure TGIFList.SaveToStream(Stream: TStream);
var
i : integer;
begin
for i := 0 to FItems.Count-1 do
TGIFItem(FItems[i]).SaveToStream(Stream);
end;
procedure TGIFList.Warning(Severity: TGIFSeverity; Message: string);
begin
Image.Warning(self, Severity, Message);
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFGlobalColorMap
//
////////////////////////////////////////////////////////////////////////////////
type
TGIFGlobalColorMap = class(TGIFColorMap)
private
FHeader : TGIFHeader;
protected
procedure Warning(Severity: TGIFSeverity; Message: string); override;
procedure BuildHistogram(var Histogram: TColormapHistogram); override;
procedure MapImages(var Map: TColormapReverse); override;
public
constructor Create(HeaderItem: TGIFHeader);
function Optimize: boolean; override;
procedure Changed; override;
end;
constructor TGIFGlobalColorMap.Create(HeaderItem: TGIFHeader);
begin
Inherited Create;
FHeader := HeaderItem;
end;
procedure TGIFGlobalColorMap.Warning(Severity: TGIFSeverity; Message: string);
begin
FHeader.Image.Warning(self, Severity, Message);
end;
procedure TGIFGlobalColorMap.BuildHistogram(var Histogram: TColormapHistogram);
var
Pixel ,
LastPixel : PChar;
i : integer;
begin
(*
** Init histogram
*)
for i := 0 to Count-1 do
begin
Histogram[i].Index := i;
Histogram[i].Count := 0;
end;
for i := 0 to FHeader.Image.Images.Count-1 do
if (FHeader.Image.Images[i].ActiveColorMap = self) then
begin
Pixel := FHeader.Image.Images[i].Data;
LastPixel := Pixel + FHeader.Image.Images[i].Width * FHeader.Image.Images[i].Height;
(*
** Sum up usage count for each color
*)
while (Pixel < LastPixel) do
begin
inc(Histogram[ord(Pixel^)].Count);
inc(Pixel);
end;
end;
end;
procedure TGIFGlobalColorMap.MapImages(var Map: TColormapReverse);
var
Pixel ,
LastPixel : PChar;
i : integer;
begin
for i := 0 to FHeader.Image.Images.Count-1 do
if (FHeader.Image.Images[i].ActiveColorMap = self) then
begin
Pixel := FHeader.Image.Images[i].Data;
LastPixel := Pixel + FHeader.Image.Images[i].Width * FHeader.Image.Images[i].Height;
(*
** Reorder all pixel to new map
*)
while (Pixel < LastPixel) do
begin
Pixel^ := chr(Map[ord(Pixel^)]);
inc(Pixel);
end;
(*
** Reorder transparent colors
*)
if (FHeader.Image.Images[i].Transparent) then
FHeader.Image.Images[i].GraphicControlExtension.TransparentColorIndex :=
Map[FHeader.Image.Images[i].GraphicControlExtension.TransparentColorIndex];
end;
end;
function TGIFGlobalColorMap.Optimize: boolean;
begin
{ Optimize with first image, Remove unused colors if only one image }
if (FHeader.Image.Images.Count > 0) then
Result := DoOptimize
else
Result := False;
end;
procedure TGIFGlobalColorMap.Changed;
begin
FHeader.Image.Palette := 0;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFHeader
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFHeader.Create(GIFImage: TGIFImage);
begin
inherited Create(GIFImage);
FColorMap := TGIFGlobalColorMap.Create(self);
Clear;
end;
destructor TGIFHeader.Destroy;
begin
FColorMap.Free;
inherited Destroy;
end;
procedure TGIFHeader.Clear;
begin
FColorMap.Clear;
FLogicalScreenDescriptor.ScreenWidth := 0;
FLogicalScreenDescriptor.ScreenHeight := 0;
FLogicalScreenDescriptor.PackedFields := 0;
FLogicalScreenDescriptor.BackgroundColorIndex := 0;
FLogicalScreenDescriptor.AspectRatio := 0;
end;
procedure TGIFHeader.Assign(Source: TPersistent);
begin
if (Source is TGIFHeader) then
begin
ColorMap.Assign(TGIFHeader(Source).ColorMap);
FLogicalScreenDescriptor := TGIFHeader(Source).FLogicalScreenDescriptor;
end else
if (Source is TGIFColorMap) then
begin
Clear;
ColorMap.Assign(TGIFColorMap(Source));
end else
inherited Assign(Source);
end;
type
TGIFHeaderRec = packed record
Signature: array[0..2] of char; { contains 'GIF' }
Version: TGIFVersionRec; { '87a' or '89a' }
end;
const
{ logical screen descriptor packed field masks }
lsdGlobalColorTable = $80; { set if global color table follows L.S.D. }
lsdColorResolution = $70; { Color resolution - 3 bits }
lsdSort = $08; { set if global color table is sorted - 1 bit }
lsdColorTableSize = $07; { size of global color table - 3 bits }
{ Actual size = 2^value+1 - value is 3 bits }
procedure TGIFHeader.Prepare;
var
pack : BYTE;
begin
Pack := $00;
if (ColorMap.Count > 0) then
begin
Pack := lsdGlobalColorTable;
if (ColorMap.Optimized) then
Pack := Pack OR lsdSort;
end;
// Note: The SHL below was SHL 5 in the original source, but that looks wrong
Pack := Pack OR ((Image.ColorResolution SHL 4) AND lsdColorResolution);
Pack := Pack OR ((Image.BitsPerPixel-1) AND lsdColorTableSize);
FLogicalScreenDescriptor.PackedFields := Pack;
end;
procedure TGIFHeader.SaveToStream(Stream: TStream);
var
GifHeader : TGIFHeaderRec;
v : TGIFVersion;
begin
v := Image.Version;
if (v = gvUnknown) then
Error(sBadVersion);
GifHeader.Signature := 'GIF';
GifHeader.Version := GIFVersions[v];
Prepare;
Stream.Write(GifHeader, sizeof(GifHeader));
Stream.Write(FLogicalScreenDescriptor, sizeof(FLogicalScreenDescriptor));
if (FLogicalScreenDescriptor.PackedFields AND lsdGlobalColorTable = lsdGlobalColorTable) then
ColorMap.SaveToStream(Stream);
end;
procedure TGIFHeader.LoadFromStream(Stream: TStream);
var
GifHeader : TGIFHeaderRec;
ColorCount : integer;
Position : integer;
begin
Position := Stream.Position;
ReadCheck(Stream, GifHeader, sizeof(GifHeader));
if (uppercase(GifHeader.Signature) <> 'GIF') then
begin
// Attempt recovery in case we are reading a GIF stored in a form by rxLib
Stream.Position := Position;
// Seek past size stored in stream
Stream.Seek(sizeof(longInt), soFromCurrent);
// Attempt to read signature again
ReadCheck(Stream, GifHeader, sizeof(GifHeader));
if (uppercase(GifHeader.Signature) <> 'GIF') then
Error(sBadSignature);
end;
ReadCheck(Stream, FLogicalScreenDescriptor, sizeof(FLogicalScreenDescriptor));
if (FLogicalScreenDescriptor.PackedFields AND lsdGlobalColorTable = lsdGlobalColorTable) then
begin
ColorCount := 2 SHL (FLogicalScreenDescriptor.PackedFields AND lsdColorTableSize);
if (ColorCount < 2) or (ColorCount > 256) then
Error(sScreenBadColorSize);
ColorMap.LoadFromStream(Stream, ColorCount)
end else
ColorMap.Clear;
end;
function TGIFHeader.GetVersion: TGIFVersion;
begin
if (FColorMap.Optimized) or (AspectRatio <> 0) then
Result := gv89a
else
Result := inherited GetVersion;
end;
function TGIFHeader.GetBackgroundColor: TColor;
begin
Result := FColorMap[BackgroundColorIndex];
end;
procedure TGIFHeader.SetBackgroundColor(Color: TColor);
begin
BackgroundColorIndex := FColorMap.AddUnique(Color);
end;
procedure TGIFHeader.SetBackgroundColorIndex(Index: BYTE);
begin
if ((Index >= FColorMap.Count) and (FColorMap.Count > 0)) then
begin
Warning(gsWarning, sBadColorIndex);
Index := 0;
end;
FLogicalScreenDescriptor.BackgroundColorIndex := Index;
end;
function TGIFHeader.GetBitsPerPixel: integer;
begin
Result := FColorMap.BitsPerPixel;
end;
function TGIFHeader.GetColorResolution: integer;
begin
Result := FColorMap.BitsPerPixel-1;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFLocalColorMap
//
////////////////////////////////////////////////////////////////////////////////
type
TGIFLocalColorMap = class(TGIFColorMap)
private
FSubImage : TGIFSubImage;
protected
procedure Warning(Severity: TGIFSeverity; Message: string); override;
procedure BuildHistogram(var Histogram: TColormapHistogram); override;
procedure MapImages(var Map: TColormapReverse); override;
public
constructor Create(SubImage: TGIFSubImage);
function Optimize: boolean; override;
procedure Changed; override;
end;
constructor TGIFLocalColorMap.Create(SubImage: TGIFSubImage);
begin
Inherited Create;
FSubImage := SubImage;
end;
procedure TGIFLocalColorMap.Warning(Severity: TGIFSeverity; Message: string);
begin
FSubImage.Image.Warning(self, Severity, Message);
end;
procedure TGIFLocalColorMap.BuildHistogram(var Histogram: TColormapHistogram);
var
Pixel ,
LastPixel : PChar;
i : integer;
begin
Pixel := FSubImage.Data;
LastPixel := Pixel + FSubImage.Width * FSubImage.Height;
(*
** Init histogram
*)
for i := 0 to Count-1 do
begin
Histogram[i].Index := i;
Histogram[i].Count := 0;
end;
(*
** Sum up usage count for each color
*)
while (Pixel < LastPixel) do
begin
inc(Histogram[ord(Pixel^)].Count);
inc(Pixel);
end;
end;
procedure TGIFLocalColorMap.MapImages(var Map: TColormapReverse);
var
Pixel ,
LastPixel : PChar;
begin
Pixel := FSubImage.Data;
LastPixel := Pixel + FSubImage.Width * FSubImage.Height;
(*
** Reorder all pixel to new map
*)
while (Pixel < LastPixel) do
begin
Pixel^ := chr(Map[ord(Pixel^)]);
inc(Pixel);
end;
(*
** Reorder transparent colors
*)
if (FSubImage.Transparent) then
FSubImage.GraphicControlExtension.TransparentColorIndex :=
Map[FSubImage.GraphicControlExtension.TransparentColorIndex];
end;
function TGIFLocalColorMap.Optimize: boolean;
begin
Result := DoOptimize;
end;
procedure TGIFLocalColorMap.Changed;
begin
FSubImage.Palette := 0;
end;
////////////////////////////////////////////////////////////////////////////////
//
// LZW Decoder
//
////////////////////////////////////////////////////////////////////////////////
const
GIFCodeBits = 12; // Max number of bits per GIF token code
GIFCodeMax = (1 SHL GIFCodeBits)-1;// Max GIF token code
// 12 bits = 4095
StackSize = (2 SHL GIFCodeBits); // Size of decompression stack
TableSize = (1 SHL GIFCodeBits); // Size of decompression table
procedure TGIFSubImage.Decompress(Stream: TStream);
var
table0 : array[0..TableSize-1] of integer;
table1 : array[0..TableSize-1] of integer;
firstcode, oldcode : integer;
buf : array[0..257] of BYTE;
Dest : PChar;
v ,
xpos, ypos, pass : integer;
stack : array[0..StackSize-1] of integer;
Source : ^integer;
BitsPerCode : integer; // number of CodeTableBits/code
InitialBitsPerCode : BYTE;
MaxCode : integer; // maximum code, given BitsPerCode
MaxCodeSize : integer;
ClearCode : integer; // Special code to signal "Clear table"
EOFCode : integer; // Special code to signal EOF
step : integer;
i : integer;
StartBit , // Index of bit buffer start
LastBit , // Index of last bit in buffer
LastByte : integer; // Index of last byte in buffer
get_done ,
return_clear ,
ZeroBlock : boolean;
ClearValue : BYTE;
{$ifdef DEBUG_DECOMPRESSPERFORMANCE}
TimeStartDecompress ,
TimeStopDecompress : DWORD;
{$endif}
function nextCode(BitsPerCode: integer): integer;
const
masks: array[0..15] of integer =
($0000, $0001, $0003, $0007,
$000f, $001f, $003f, $007f,
$00ff, $01ff, $03ff, $07ff,
$0fff, $1fff, $3fff, $7fff);
var
StartIndex, EndIndex : integer;
ret : integer;
EndBit : integer;
count : BYTE;
begin
if (return_clear) then
begin
return_clear := False;
Result := ClearCode;
exit;
end;
EndBit := StartBit + BitsPerCode;
if (EndBit >= LastBit) then
begin
if (get_done) then
begin
if (StartBit >= LastBit) then
Warning(gsWarning, sDecodeTooFewBits);
Result := -1;
exit;
end;
buf[0] := buf[LastByte-2];
buf[1] := buf[LastByte-1];
if (Stream.Read(count, 1) <> 1) then
begin
Result := -1;
exit;
end;
if (count = 0) then
begin
ZeroBlock := True;
get_done := TRUE;
end else
begin
// Handle premature end of file
if (Stream.Size - Stream.Position < Count) then
begin
Warning(gsWarning, sOutOfData);
// Not enough data left - Just read as much as we can get
Count := Stream.Size - Stream.Position;
end;
if (Count <> 0) then
ReadCheck(Stream, Buf[2], Count);
end;
LastByte := 2 + count;
StartBit := (StartBit - LastBit) + 16;
LastBit := LastByte * 8;
EndBit := StartBit + BitsPerCode;
end;
EndIndex := EndBit DIV 8;
StartIndex := StartBit DIV 8;
ASSERT(StartIndex <= high(buf), 'StartIndex too large');
if (StartIndex = EndIndex) then
ret := buf[StartIndex]
else
if (StartIndex + 1 = EndIndex) then
ret := buf[StartIndex] OR (buf[StartIndex+1] SHL 8)
else
ret := buf[StartIndex] OR (buf[StartIndex+1] SHL 8) OR (buf[StartIndex+2] SHL 16);
ret := (ret SHR (StartBit AND $0007)) AND masks[BitsPerCode];
Inc(StartBit, BitsPerCode);
Result := ret;
end;
function NextLZW: integer;
var
code, incode : integer;
i : integer;
b : BYTE;
begin
code := nextCode(BitsPerCode);
while (code >= 0) do
begin
if (code = ClearCode) then
begin
ASSERT(ClearCode < TableSize, 'ClearCode too large');
for i := 0 to ClearCode-1 do
begin
table0[i] := 0;
table1[i] := i;
end;
for i := ClearCode to TableSize-1 do
begin
table0[i] := 0;
table1[i] := 0;
end;
BitsPerCode := InitialBitsPerCode+1;
MaxCodeSize := 2 * ClearCode;
MaxCode := ClearCode + 2;
Source := @stack;
repeat
firstcode := nextCode(BitsPerCode);
oldcode := firstcode;
until (firstcode <> ClearCode);
Result := firstcode;
exit;
end;
if (code = EOFCode) then
begin
Result := -2;
if (ZeroBlock) then
exit;
// Eat rest of data blocks
if (Stream.Read(b, 1) <> 1) then
exit;
while (b <> 0) do
begin
Stream.Seek(b, soFromCurrent);
if (Stream.Read(b, 1) <> 1) then
exit;
end;
exit;
end;
incode := code;
if (code >= MaxCode) then
begin
Source^ := firstcode;
Inc(Source);
code := oldcode;
end;
ASSERT(Code < TableSize, 'Code too large');
while (code >= ClearCode) do
begin
Source^ := table1[code];
Inc(Source);
if (code = table0[code]) then
Error(sDecodeCircular);
code := table0[code];
ASSERT(Code < TableSize, 'Code too large');
end;
firstcode := table1[code];
Source^ := firstcode;
Inc(Source);
code := MaxCode;
if (code <= GIFCodeMax) then
begin
table0[code] := oldcode;
table1[code] := firstcode;
Inc(MaxCode);
if ((MaxCode >= MaxCodeSize) and (MaxCodeSize <= GIFCodeMax)) then
begin
MaxCodeSize := MaxCodeSize * 2;
Inc(BitsPerCode);
end;
end;
oldcode := incode;
if (longInt(Source) > longInt(@stack)) then
begin
Dec(Source);
Result := Source^;
exit;
end
end;
Result := code;
end;
function readLZW: integer;
begin
if (longInt(Source) > longInt(@stack)) then
begin
Dec(Source);
Result := Source^;
end else
Result := NextLZW;
end;
begin
NewImage;
// Clear image data in case decompress doesn't complete
if (Transparent) then
// Clear to transparent color
ClearValue := GraphicControlExtension.GetTransparentColorIndex
else
// Clear to first color
ClearValue := 0;
FillChar(FData^, FDataSize, ClearValue);
{$ifdef DEBUG_DECOMPRESSPERFORMANCE}
TimeStartDecompress := timeGetTime;
{$endif}
(*
** Read initial code size in bits from stream
*)
if (Stream.Read(InitialBitsPerCode, 1) <> 1) then
exit;
(*
** Initialize the Compression routines
*)
BitsPerCode := InitialBitsPerCode + 1;
ClearCode := 1 SHL InitialBitsPerCode;
EOFCode := ClearCode + 1;
MaxCodeSize := 2 * ClearCode;
MaxCode := ClearCode + 2;
StartBit := 0;
LastBit := 0;
LastByte := 2;
ZeroBlock := False;
get_done := False;
return_clear := TRUE;
Source := @stack;
try
if (Interlaced) then
begin
ypos := 0;
pass := 0;
step := 8;
for i := 0 to Height-1 do
begin
Dest := FData + Width * ypos;
for xpos := 0 to width-1 do
begin
v := readLZW;
if (v < 0) then
exit;
Dest^ := char(v);
Inc(Dest);
end;
Inc(ypos, step);
if (ypos >= height) then
repeat
if (pass > 0) then
step := step DIV 2;
Inc(pass);
ypos := step DIV 2;
until (ypos < height);
end;
end else
begin
Dest := FData;
for ypos := 0 to (height * width)-1 do
begin
v := readLZW;
if (v < 0) then
exit;
Dest^ := char(v);
Inc(Dest);
end;
end;
finally
if (readLZW >= 0) then
;
// raise GIFException.Create('Too much input data, ignoring extra...');
end;
{$ifdef DEBUG_DECOMPRESSPERFORMANCE}
TimeStopDecompress := timeGetTime;
ShowMessage(format('Decompressed %d pixels in %d mS, Rate %d pixels/mS',
[Height*Width, TimeStopDecompress-TimeStartDecompress,
(Height*Width) DIV (TimeStopDecompress-TimeStartDecompress+1)]));
{$endif}
end;
////////////////////////////////////////////////////////////////////////////////
//
// LZW Encoder stuff
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// LZW Encoder THashTable
////////////////////////////////////////////////////////////////////////////////
const
HashKeyBits = 13; // Max number of bits per Hash Key
HashSize = 8009; // Size of hash table
// Must be prime
// Must be > than HashMaxCode
// Must be < than HashMaxKey
HashKeyMax = (1 SHL HashKeyBits)-1;// Max hash key value
// 13 bits = 8191
HashKeyMask = HashKeyMax; // $1FFF
GIFCodeMask = GIFCodeMax; // $0FFF
HashEmpty = $000FFFFF; // 20 bits
type
// A Hash Key is 20 bits wide.
// - The lower 8 bits are the postfix character (the new pixel).
// - The upper 12 bits are the prefix code (the GIF token).
// A KeyInt must be able to represent the integer values -1..(2^20)-1
KeyInt = longInt; // 32 bits
CodeInt = SmallInt; // 16 bits
THashArray = array[0..HashSize-1] of KeyInt;
PHashArray = ^THashArray;
THashTable = class
{$ifdef DEBUG_HASHPERFORMANCE}
CountLookupFound : longInt;
CountMissFound : longInt;
CountLookupNotFound : longInt;
CountMissNotFound : longInt;
{$endif}
HashTable: PHashArray;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure Insert(Key: KeyInt; Code: CodeInt);
function Lookup(Key: KeyInt): CodeInt;
end;
function HashKey(Key: KeyInt): CodeInt;
begin
Result := ((Key SHR (GIFCodeBits-8)) XOR Key) MOD HashSize;
end;
function NextHashKey(HKey: CodeInt): CodeInt;
var
disp : CodeInt;
begin
(*
** secondary hash (after G. Knott)
*)
disp := HashSize - HKey;
if (HKey = 0) then
disp := 1;
// disp := 13; // disp should be prime relative to HashSize, but
// it doesn't seem to matter here...
dec(HKey, disp);
if (HKey < 0) then
inc(HKey, HashSize);
Result := HKey;
end;
constructor THashTable.Create;
begin
ASSERT(longInt($FFFFFFFF) = -1, 'TGIFImage implementation assumes $FFFFFFFF = -1');
inherited Create;
GetMem(HashTable, sizeof(THashArray));
Clear;
{$ifdef DEBUG_HASHPERFORMANCE}
CountLookupFound := 0;
CountMissFound := 0;
CountLookupNotFound := 0;
CountMissNotFound := 0;
{$endif}
end;
destructor THashTable.Destroy;
begin
{$ifdef DEBUG_HASHPERFORMANCE}
ShowMessage(
Format('Found: %d HitRate: %.2f',
[CountLookupFound, (CountLookupFound+1)/(CountMissFound+1)])+#13+
Format('Not found: %d HitRate: %.2f',
[CountLookupNotFound, (CountLookupNotFound+1)/(CountMissNotFound+1)]));
{$endif}
FreeMem(HashTable);
inherited Destroy;
end;
// Clear hash table and fill with empty slots (doh!)
procedure THashTable.Clear;
{$ifdef DEBUG_HASHFILLFACTOR}
var
i ,
Count : longInt;
{$endif}
begin
{$ifdef DEBUG_HASHFILLFACTOR}
Count := 0;
for i := 0 to HashSize-1 do
if (HashTable[i] SHR GIFCodeBits <> HashEmpty) then
inc(Count);
ShowMessage(format('Size: %d, Filled: %d, Rate %.4f',
[HashSize, Count, Count/HashSize]));
{$endif}
FillChar(HashTable^, sizeof(THashArray), $FF);
end;
// Insert new key/value pair into hash table
procedure THashTable.Insert(Key: KeyInt; Code: CodeInt);
var
HKey : CodeInt;
begin
// Create hash key from prefix string
HKey := HashKey(Key);
// Scan for empty slot
// while (HashTable[HKey] SHR GIFCodeBits <> HashEmpty) do { Unoptimized }
while (HashTable[HKey] AND (HashEmpty SHL GIFCodeBits) <> (HashEmpty SHL GIFCodeBits)) do { Optimized }
HKey := NextHashKey(HKey);
// Fill slot with key/value pair
HashTable[HKey] := (Key SHL GIFCodeBits) OR (Code AND GIFCodeMask);
end;
// Search for key in hash table.
// Returns value if found or -1 if not
function THashTable.Lookup(Key: KeyInt): CodeInt;
var
HKey : CodeInt;
HTKey : KeyInt;
{$ifdef DEBUG_HASHPERFORMANCE}
n : LongInt;
{$endif}
begin
// Create hash key from prefix string
HKey := HashKey(Key);
{$ifdef DEBUG_HASHPERFORMANCE}
n := 0;
{$endif}
// Scan table for key
// HTKey := HashTable[HKey] SHR GIFCodeBits; { Unoptimized }
Key := Key SHL GIFCodeBits; { Optimized }
HTKey := HashTable[HKey] AND (HashEmpty SHL GIFCodeBits); { Optimized }
// while (HTKey <> HashEmpty) do { Unoptimized }
while (HTKey <> HashEmpty SHL GIFCodeBits) do { Optimized }
begin
if (Key = HTKey) then
begin
// Extract and return value
Result := HashTable[HKey] AND GIFCodeMask;
{$ifdef DEBUG_HASHPERFORMANCE}
inc(CountLookupFound);
inc(CountMissFound, n);
{$endif}
exit;
end;
{$ifdef DEBUG_HASHPERFORMANCE}
inc(n);
{$endif}
// Try next slot
HKey := NextHashKey(HKey);
// HTKey := HashTable[HKey] SHR GIFCodeBits; { Unoptimized }
HTKey := HashTable[HKey] AND (HashEmpty SHL GIFCodeBits); { Optimized }
end;
// Found empty slot - key doesn't exist
Result := -1;
{$ifdef DEBUG_HASHPERFORMANCE}
inc(CountLookupNotFound);
inc(CountMissNotFound, n);
{$endif}
end;
////////////////////////////////////////////////////////////////////////////////
// TGIFStream - Abstract GIF block stream
//
// Descendants from TGIFStream either reads or writes data in blocks
// of up to 255 bytes. These blocks are organized as a leading byte
// containing the number of bytes in the block (exclusing the count
// byte itself), followed by the data (up to 254 bytes of data).
////////////////////////////////////////////////////////////////////////////////
type
TGIFStream = class(TStream)
private
FOnWarning : TGIFWarning;
FStream : TStream;
FOnProgress : TNotifyEvent;
FBuffer : array [BYTE] of Char;
FBufferCount : integer;
protected
constructor Create(Stream: TStream);
function Read(var Buffer; Count: Longint): Longint; override;
function Write(const Buffer; Count: Longint): Longint; override;
function Seek(Offset: Longint; Origin: Word): Longint; override;
procedure Progress(Sender: TObject); dynamic;
property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
public
property Warning: TGIFWarning read FOnWarning write FOnWarning;
end;
constructor TGIFStream.Create(Stream: TStream);
begin
inherited Create;
FStream := Stream;
FBufferCount := 1; // Reserve first byte of buffer for length
end;
procedure TGIFStream.Progress(Sender: TObject);
begin
if Assigned(FOnProgress) then
FOnProgress(Sender);
end;
function TGIFStream.Write(const Buffer; Count: Longint): Longint;
begin
raise Exception.Create(sInvalidStream);
end;
function TGIFStream.Read(var Buffer; Count: Longint): Longint;
begin
raise Exception.Create(sInvalidStream);
end;
function TGIFStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
raise Exception.Create(sInvalidStream);
end;
////////////////////////////////////////////////////////////////////////////////
// TGIFReader - GIF block reader
////////////////////////////////////////////////////////////////////////////////
type
TGIFReader = class(TGIFStream)
public
constructor Create(Stream: TStream);
function Read(var Buffer; Count: Longint): Longint; override;
end;
constructor TGIFReader.Create(Stream: TStream);
begin
inherited Create(Stream);
FBufferCount := 0;
end;
function TGIFReader.Read(var Buffer; Count: Longint): Longint;
var
n : integer;
Dst : PChar;
size : BYTE;
begin
Dst := @Buffer;
Result := 0;
while (Count > 0) do
begin
// Get data from buffer
while (FBufferCount > 0) and (Count > 0) do
begin
if (FBufferCount > Count) then
n := Count
else
n := FBufferCount;
Move(FBuffer, Dst^, n);
dec(FBufferCount, n);
dec(Count, n);
inc(Result, n);
inc(Dst, n);
end;
// Refill buffer when it becomes empty
if (FBufferCount <= 0) then
begin
FStream.Read(size, 1);
{ TODO -oanme -cImprovement : Should be handled as a warning instead of an error. }
if (size >= 255) then
Error('GIF block too large');
FBufferCount := size;
if (FBufferCount > 0) then
begin
n := FStream.Read(FBuffer, size);
if (n = FBufferCount) then
begin
Warning(self, gsWarning, sOutOfData);
break;
end;
end else
break;
end;
end;
end;
////////////////////////////////////////////////////////////////////////////////
// TGIFWriter - GIF block writer
////////////////////////////////////////////////////////////////////////////////
type
TGIFWriter = class(TGIFStream)
private
FOutputDirty : boolean;
protected
procedure FlushBuffer;
public
constructor Create(Stream: TStream);
destructor Destroy; override;
function Write(const Buffer; Count: Longint): Longint; override;
function WriteByte(Value: BYTE): Longint;
end;
constructor TGIFWriter.Create(Stream: TStream);
begin
inherited Create(Stream);
FBufferCount := 1; // Reserve first byte of buffer for length
FOutputDirty := False;
end;
destructor TGIFWriter.Destroy;
begin
inherited Destroy;
if (FOutputDirty) then
FlushBuffer;
end;
procedure TGIFWriter.FlushBuffer;
begin
if (FBufferCount <= 0) then
exit;
FBuffer[0] := char(FBufferCount-1); // Block size excluding the count
FStream.WriteBuffer(FBuffer, FBufferCount);
FBufferCount := 1; // Reserve first byte of buffer for length
FOutputDirty := False;
end;
function TGIFWriter.Write(const Buffer; Count: Longint): Longint;
var
n : integer;
Src : PChar;
begin
Result := Count;
FOutputDirty := True;
Src := @Buffer;
while (Count > 0) do
begin
// Move data to the internal buffer in 255 byte chunks
while (FBufferCount < sizeof(FBuffer)) and (Count > 0) do
begin
n := sizeof(FBuffer) - FBufferCount;
if (n > Count) then
n := Count;
Move(Src^, FBuffer[FBufferCount], n);
inc(Src, n);
inc(FBufferCount, n);
dec(Count, n);
end;
// Flush the buffer when it is full
if (FBufferCount >= sizeof(FBuffer)) then
FlushBuffer;
end;
end;
function TGIFWriter.WriteByte(Value: BYTE): Longint;
begin
Result := Write(Value, 1);
end;
////////////////////////////////////////////////////////////////////////////////
// TGIFEncoder - Abstract encoder
////////////////////////////////////////////////////////////////////////////////
type
TGIFEncoder = class(TObject)
protected
FOnWarning : TGIFWarning;
MaxColor : integer;
BitsPerPixel : BYTE; // Bits per pixel of image
Stream : TStream; // Output stream
Width , // Width of image in pixels
Height : integer; // height of image in pixels
Interlace : boolean; // Interlace flag (True = interlaced image)
Data : PChar; // Pointer to pixel data
GIFStream : TGIFWriter; // Output buffer
OutputBucket : longInt; // Output bit bucket
OutputBits : integer; // Current # of bits in bucket
ClearFlag : Boolean; // True if dictionary has just been cleared
BitsPerCode , // Current # of bits per code
InitialBitsPerCode : integer; // Initial # of bits per code after
// dictionary has been cleared
MaxCode : CodeInt; // maximum code, given BitsPerCode
ClearCode : CodeInt; // Special output code to signal "Clear table"
EOFCode : CodeInt; // Special output code to signal EOF
BaseCode : CodeInt; // ...
Pixel : PChar; // Pointer to current pixel
cX , // Current X counter (Width - X)
Y : integer; // Current Y
Pass : integer; // Interlace pass
function MaxCodesFromBits(Bits: integer): CodeInt;
procedure Output(Value: integer); virtual;
procedure Clear; virtual;
function BumpPixel: boolean;
procedure DoCompress; virtual; abstract;
public
procedure Compress(AStream: TStream; ABitsPerPixel: integer;
AWidth, AHeight: integer; AInterlace: boolean; AData: PChar; AMaxColor: integer);
property Warning: TGIFWarning read FOnWarning write FOnWarning;
end;
// Calculate the maximum number of codes that a given number of bits can represent
// MaxCodes := (1^bits)-1
function TGIFEncoder.MaxCodesFromBits(Bits: integer): CodeInt;
begin
Result := (CodeInt(1) SHL Bits) - 1;
end;
// Stuff bits (variable sized codes) into a buffer and output them
// a byte at a time
procedure TGIFEncoder.Output(Value: integer);
const
BitBucketMask: array[0..16] of longInt =
($0000,
$0001, $0003, $0007, $000F,
$001F, $003F, $007F, $00FF,
$01FF, $03FF, $07FF, $0FFF,
$1FFF, $3FFF, $7FFF, $FFFF);
begin
if (OutputBits > 0) then
OutputBucket :=
(OutputBucket AND BitBucketMask[OutputBits]) OR (longInt(Value) SHL OutputBits)
else
OutputBucket := Value;
inc(OutputBits, BitsPerCode);
while (OutputBits >= 8) do
begin
GIFStream.WriteByte(OutputBucket AND $FF);
OutputBucket := OutputBucket SHR 8;
dec(OutputBits, 8);
end;
if (Value = EOFCode) then
begin
// At EOF, write the rest of the buffer.
while (OutputBits > 0) do
begin
GIFStream.WriteByte(OutputBucket AND $FF);
OutputBucket := OutputBucket SHR 8;
dec(OutputBits, 8);
end;
end;
end;
procedure TGIFEncoder.Clear;
begin
// just_cleared = 1;
ClearFlag := TRUE;
Output(ClearCode);
end;
// Bump (X,Y) and data pointer to point to the next pixel
function TGIFEncoder.BumpPixel: boolean;
begin
// Bump the current X position
dec(cX);
// If we are at the end of a scan line, set cX back to the beginning
// If we are interlaced, bump Y to the appropriate spot, otherwise,
// just increment it.
if (cX <= 0) then
begin
if not(Interlace) then
begin
// Done - no more data
Result := False;
exit;
end;
cX := Width;
case (Pass) of
0:
begin
inc(Y, 8);
if (Y >= Height) then
begin
inc(Pass);
Y := 4;
end;
end;
1:
begin
inc(Y, 8);
if (Y >= Height) then
begin
inc(Pass);
Y := 2;
end;
end;
2:
begin
inc(Y, 4);
if (Y >= Height) then
begin
inc(Pass);
Y := 1;
end;
end;
3:
inc(Y, 2);
end;
if (Y >= height) then
begin
// Done - No more data
Result := False;
exit;
end;
Pixel := Data + (Y * Width);
end;
Result := True;
end;
procedure TGIFEncoder.Compress(AStream: TStream; ABitsPerPixel: integer;
AWidth, AHeight: integer; AInterlace: boolean; AData: PChar; AMaxColor: integer);
const
EndBlockByte = $00; // End of block marker
{$ifdef DEBUG_COMPRESSPERFORMANCE}
var
TimeStartCompress ,
TimeStopCompress : DWORD;
{$endif}
begin
MaxColor := AMaxColor;
Stream := AStream;
BitsPerPixel := ABitsPerPixel;
Width := AWidth;
Height := AHeight;
Interlace := AInterlace;
Data := AData;
if (BitsPerPixel <= 1) then
BitsPerPixel := 2;
InitialBitsPerCode := BitsPerPixel + 1;
Stream.Write(BitsPerPixel, 1);
// out_bits_init = init_bits;
BitsPerCode := InitialBitsPerCode;
MaxCode := MaxCodesFromBits(BitsPerCode);
ClearCode := (1 SHL (InitialBitsPerCode - 1));
EOFCode := ClearCode + 1;
BaseCode := EOFCode + 1;
// Clear bit bucket
OutputBucket := 0;
OutputBits := 0;
// Reset pixel counter
if (Interlace) then
cX := Width
else
cX := Width*Height;
// Reset row counter
Y := 0;
Pass := 0;
GIFStream := TGIFWriter.Create(AStream);
try
GIFStream.Warning := Warning;
if (Data <> nil) and (Height > 0) and (Width > 0) then
begin
{$ifdef DEBUG_COMPRESSPERFORMANCE}
TimeStartCompress := timeGetTime;
{$endif}
// Call compress implementation
DoCompress;
{$ifdef DEBUG_COMPRESSPERFORMANCE}
TimeStopCompress := timeGetTime;
ShowMessage(format('Compressed %d pixels in %d mS, Rate %d pixels/mS',
[Height*Width, TimeStopCompress-TimeStartCompress,
DWORD(Height*Width) DIV (TimeStopCompress-TimeStartCompress+1)]));
{$endif}
// Output the final code.
Output(EOFCode);
end else
// Output the final code (and nothing else).
TGIFEncoder(self).Output(EOFCode);
finally
GIFStream.Free;
end;
WriteByte(Stream, EndBlockByte);
end;
////////////////////////////////////////////////////////////////////////////////
// TRLEEncoder - RLE encoder
////////////////////////////////////////////////////////////////////////////////
type
TRLEEncoder = class(TGIFEncoder)
private
MaxCodes : integer;
OutBumpInit ,
OutClearInit : integer;
Prefix : integer; // Current run color
RunLengthTableMax ,
RunLengthTablePixel ,
OutCount ,
OutClear ,
OutBump : integer;
protected
function ComputeTriangleCount(count: integer; nrepcodes: integer): integer;
procedure MaxOutClear;
procedure ResetOutClear;
procedure FlushFromClear(Count: integer);
procedure FlushClearOrRepeat(Count: integer);
procedure FlushWithTable(Count: integer);
procedure Flush(RunLengthCount: integer);
procedure OutputPlain(Value: integer);
procedure Clear; override;
procedure DoCompress; override;
end;
procedure TRLEEncoder.Clear;
begin
OutBump := OutBumpInit;
OutClear := OutClearInit;
OutCount := 0;
RunLengthTableMax := 0;
inherited Clear;
BitsPerCode := InitialBitsPerCode;
end;
procedure TRLEEncoder.OutputPlain(Value: integer);
begin
ClearFlag := False;
Output(Value);
inc(OutCount);
if (OutCount >= OutBump) then
begin
inc(BitsPerCode);
inc(OutBump, 1 SHL (BitsPerCode - 1));
end;
if (OutCount >= OutClear) then
Clear;
end;
function TRLEEncoder.ComputeTriangleCount(count: integer; nrepcodes: integer): integer;
var
PerRepeat : integer;
n : integer;
function iSqrt(x: integer): integer;
var
r, v : integer;
begin
if (x < 2) then
begin
Result := x;
exit;
end else
begin
v := x;
r := 1;
while (v > 0) do
begin
v := v DIV 4;
r := r * 2;
end;
end;
while (True) do
begin
v := ((x DIV r) + r) DIV 2;
if ((v = r) or (v = r+1)) then
begin
Result := r;
exit;
end;
r := v;
end;
end;
begin
Result := 0;
PerRepeat := (nrepcodes * (nrepcodes+1)) DIV 2;
while (Count >= PerRepeat) do
begin
inc(Result, nrepcodes);
dec(Count, PerRepeat);
end;
if (Count > 0) then
begin
n := iSqrt(Count);
while ((n * (n+1)) >= 2*Count) do
dec(n);
while ((n * (n+1)) < 2*Count) do
inc(n);
inc(Result, n);
end;
end;
procedure TRLEEncoder.MaxOutClear;
begin
OutClear := MaxCodes;
end;
procedure TRLEEncoder.ResetOutClear;
begin
OutClear := OutClearInit;
if (OutCount >= OutClear) then
Clear;
end;
procedure TRLEEncoder.FlushFromClear(Count: integer);
var
n : integer;
begin
MaxOutClear;
RunLengthTablePixel := Prefix;
n := 1;
while (Count > 0) do
begin
if (n = 1) then
begin
RunLengthTableMax := 1;
OutputPlain(Prefix);
dec(Count);
end else
if (Count >= n) then
begin
RunLengthTableMax := n;
OutputPlain(BaseCode + n - 2);
dec(Count, n);
end else
if (Count = 1) then
begin
inc(RunLengthTableMax);
OutputPlain(Prefix);
break;
end else
begin
inc(RunLengthTableMax);
OutputPlain(BaseCode + Count - 2);
break;
end;
if (OutCount = 0) then
n := 1
else
inc(n);
end;
ResetOutClear;
end;
procedure TRLEEncoder.FlushClearOrRepeat(Count: integer);
var
WithClear : integer;
begin
WithClear := 1 + ComputeTriangleCount(Count, MaxCodes);
if (WithClear < Count) then
begin
Clear;
FlushFromClear(Count);
end else
while (Count > 0) do
begin
OutputPlain(Prefix);
dec(Count);
end;
end;
procedure TRLEEncoder.FlushWithTable(Count: integer);
var
RepeatMax ,
RepeatLeft ,
LeftOver : integer;
begin
RepeatMax := Count DIV RunLengthTableMax;
LeftOver := Count MOD RunLengthTableMax;
if (LeftOver <> 0) then
RepeatLeft := 1
else
RepeatLeft := 0;
if (OutCount + RepeatMax + RepeatLeft > MaxCodes) then
begin
RepeatMax := MaxCodes - OutCount;
LeftOver := Count - (RepeatMax * RunLengthTableMax);
RepeatLeft := 1 + ComputeTriangleCount(LeftOver, MaxCodes);
end;
if (1 + ComputeTriangleCount(Count, MaxCodes) < RepeatMax + RepeatLeft) then
begin
Clear;
FlushFromClear(Count);
exit;
end;
MaxOutClear;
while (RepeatMax > 0) do
begin
OutputPlain(BaseCode + RunLengthTableMax-2);
dec(RepeatMax);
end;
if (LeftOver > 0) then
begin
if (ClearFlag) then
FlushFromClear(LeftOver)
else if (LeftOver = 1) then
OutputPlain(Prefix)
else
OutputPlain(BaseCode + LeftOver - 2);
end;
ResetOutClear;
end;
procedure TRLEEncoder.Flush(RunLengthCount: integer);
begin
if (RunLengthCount = 1) then
begin
OutputPlain(Prefix);
exit;
end;
if (ClearFlag) then
FlushFromClear(RunLengthCount)
else if ((RunLengthTableMax < 2) or (RunLengthTablePixel <> Prefix)) then
FlushClearOrRepeat(RunLengthCount)
else
FlushWithTable(RunLengthCount);
end;
procedure TRLEEncoder.DoCompress;
var
Color : CodeInt;
RunLengthCount : integer;
begin
OutBumpInit := ClearCode - 1;
// For images with a lot of runs, making OutClearInit larger will
// give better compression.
if (BitsPerPixel <= 3) then
OutClearInit := 9
else
OutClearInit := OutBumpInit - 1;
// max_ocodes = (1 << GIFBITS) - ((1 << (out_bits_init - 1)) + 3);
// <=> MaxCodes := (1 SHL GIFCodeBits) - ((1 SHL (BitsPerCode - 1)) + 3);
// <=> MaxCodes := (1 SHL GIFCodeBits) - ((1 SHL (InitialBitsPerCode - 1)) + 3);
// <=> MaxCodes := (1 SHL GIFCodeBits) - (ClearCode + 3);
// <=> MaxCodes := (1 SHL GIFCodeBits) - (EOFCode + 2);
// <=> MaxCodes := (1 SHL GIFCodeBits) - (BaseCode + 1);
// <=> MaxCodes := MaxCodesFromBits(GIFCodeBits) - BaseCode;
MaxCodes := MaxCodesFromBits(GIFCodeBits) - BaseCode;
Clear;
RunLengthCount := 0;
Pixel := Data;
Prefix := -1; // Dummy value to make Color <> Prefix
repeat
// Fetch the next pixel
Color := CodeInt(Pixel^);
inc(Pixel);
if (Color >= MaxColor) then
Error(sInvalidColor);
if (RunLengthCount > 0) and (Color <> Prefix) then
begin
// End of current run
Flush(RunLengthCount);
RunLengthCount := 0;
end;
if (Color = Prefix) then
// Increment run length
inc(RunLengthCount)
else
begin
// Start new run
Prefix := Color;
RunLengthCount := 1;
end;
until not(BumpPixel);
Flush(RunLengthCount);
end;
////////////////////////////////////////////////////////////////////////////////
// TLZWEncoder - LZW encoder
////////////////////////////////////////////////////////////////////////////////
const
TableMaxMaxCode = (1 SHL GIFCodeBits); //
TableMaxFill = TableMaxMaxCode-1; // Clear table when it fills to
// this point.
// Note: Must be <= GIFCodeMax
type
TLZWEncoder = class(TGIFEncoder)
private
Prefix : CodeInt; // Current run color
FreeEntry : CodeInt; // next unused code in table
HashTable : THashTable;
protected
procedure Output(Value: integer); override;
procedure Clear; override;
procedure DoCompress; override;
end;
procedure TLZWEncoder.Output(Value: integer);
begin
inherited Output(Value);
// If the next entry is going to be too big for the code size,
// then increase it, if possible.
if (FreeEntry > MaxCode) or (ClearFlag) then
begin
if (ClearFlag) then
begin
BitsPerCode := InitialBitsPerCode;
MaxCode := MaxCodesFromBits(BitsPerCode);
ClearFlag := False;
end else
begin
inc(BitsPerCode);
if (BitsPerCode = GIFCodeBits) then
MaxCode := TableMaxMaxCode
else
MaxCode := MaxCodesFromBits(BitsPerCode);
end;
end;
end;
procedure TLZWEncoder.Clear;
begin
inherited Clear;
HashTable.Clear;
FreeEntry := ClearCode + 2;
end;
procedure TLZWEncoder.DoCompress;
var
Color : char;
NewKey : KeyInt;
NewCode : CodeInt;
begin
HashTable := THashTable.Create;
try
// clear hash table and sync decoder
Clear;
Pixel := Data;
Prefix := CodeInt(Pixel^);
inc(Pixel);
if (Prefix >= MaxColor) then
Error(sInvalidColor);
while (BumpPixel) do
begin
// Fetch the next pixel
Color := Pixel^;
inc(Pixel);
if (ord(Color) >= MaxColor) then
Error(sInvalidColor);
// Append Postfix to Prefix and lookup in table...
NewKey := (KeyInt(Prefix) SHL 8) OR ord(Color);
NewCode := HashTable.Lookup(NewKey);
if (NewCode >= 0) then
begin
// ...if found, get next pixel
Prefix := NewCode;
continue;
end;
// ...if not found, output and start over
Output(Prefix);
Prefix := CodeInt(Color);
if (FreeEntry < TableMaxFill) then
begin
HashTable.Insert(NewKey, FreeEntry);
inc(FreeEntry);
end else
Clear;
end;
Output(Prefix);
finally
HashTable.Free;
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFSubImage
//
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// TGIFSubImage.Compress
/////////////////////////////////////////////////////////////////////////
procedure TGIFSubImage.Compress(Stream: TStream);
var
Encoder : TGIFEncoder;
BitsPerPixel : BYTE;
MaxColors : integer;
begin
if (ColorMap.Count > 0) then
begin
MaxColors := ColorMap.Count;
BitsPerPixel := ColorMap.BitsPerPixel
end else
begin
BitsPerPixel := Image.BitsPerPixel;
MaxColors := 1 SHL BitsPerPixel;
end;
// Create a RLE or LZW GIF encoder
if (Image.Compression = gcRLE) then
Encoder := TRLEEncoder.Create
else
Encoder := TLZWEncoder.Create;
try
Encoder.Warning := Image.Warning;
Encoder.Compress(Stream, BitsPerPixel, Width, Height, Interlaced, FData, MaxColors);
finally
Encoder.Free;
end;
end;
function TGIFExtensionList.GetExtension(Index: Integer): TGIFExtension;
begin
Result := TGIFExtension(Items[Index]);
end;
procedure TGIFExtensionList.SetExtension(Index: Integer; Extension: TGIFExtension);
begin
Items[Index] := Extension;
end;
procedure TGIFExtensionList.LoadFromStream(Stream: TStream; Parent: TObject);
var
b : BYTE;
Extension : TGIFExtension;
ExtensionClass : TGIFExtensionClass;
begin
// Peek ahead to determine block type
if (Stream.Read(b, 1) <> 1) then
exit;
while not(b in [bsTrailer, bsImageDescriptor]) do
begin
if (b = bsExtensionIntroducer) then
begin
ExtensionClass := TGIFExtension.FindExtension(Stream);
if (ExtensionClass = nil) then
Error(sUnknownExtension);
Stream.Seek(-1, soFromCurrent);
Extension := ExtensionClass.Create(Parent as TGIFSubImage);
try
Extension.LoadFromStream(Stream);
Add(Extension);
except
Extension.Free;
raise;
end;
end else
begin
Warning(gsWarning, sBadExtensionLabel);
break;
end;
if (Stream.Read(b, 1) <> 1) then
exit;
end;
Stream.Seek(-1, soFromCurrent);
end;
const
{ image descriptor bit masks }
idLocalColorTable = $80; { set if a local color table follows }
idInterlaced = $40; { set if image is interlaced }
idSort = $20; { set if color table is sorted }
idReserved = $0C; { reserved - must be set to $00 }
idColorTableSize = $07; { size of color table as above }
constructor TGIFSubImage.Create(GIFImage: TGIFImage);
begin
inherited Create(GIFImage);
FExtensions := TGIFExtensionList.Create(GIFImage);
FColorMap := TGIFLocalColorMap.Create(self);
FImageDescriptor.Separator := bsImageDescriptor;
FImageDescriptor.Left := 0;
FImageDescriptor.Top := 0;
FImageDescriptor.Width := 0;
FImageDescriptor.Height := 0;
FImageDescriptor.PackedFields := 0;
FBitmap := nil;
FMask := 0;
FNeedMask := True;
FData := nil;
FDataSize := 0;
FTransparent := False;
FGCE := nil;
// Remember to synchronize with TGIFSubImage.Clear
end;
destructor TGIFSubImage.Destroy;
begin
if (FGIFImage <> nil) then
FGIFImage.Images.Remove(self);
Clear;
FExtensions.Free;
FColorMap.Free;
if (FLocalPalette <> 0) then
DeleteObject(FLocalPalette);
inherited Destroy;
end;
procedure TGIFSubImage.Clear;
begin
FExtensions.Clear;
FColorMap.Clear;
FreeImage;
Height := 0;
Width := 0;
FTransparent := False;
FGCE := nil;
FreeBitmap;
FreeMask;
// Remember to synchronize with TGIFSubImage.Create
end;
function TGIFSubImage.GetEmpty: Boolean;
begin
Result := ((FData = nil) or (FDataSize = 0) or (Height = 0) or (Width = 0));
end;
function TGIFSubImage.GetPalette: HPALETTE;
begin
if (FBitmap <> nil) and (FBitmap.Palette <> 0) then
// Use bitmaps own palette if possible
Result := FBitmap.Palette
else if (FLocalPalette <> 0) then
// Or a previously exported local palette
Result := FLocalPalette
else if (Image.DoDither) then
begin
// or create a new dither palette
FLocalPalette := WebPalette;
Result := FLocalPalette;
end
else if (ColorMap.Count > 0) then
begin
// or create a new if first time
FLocalPalette := ColorMap.ExportPalette;
Result := FLocalPalette;
end else
// Use global palette if everything else fails
Result := Image.Palette;
end;
procedure TGIFSubImage.SetPalette(Value: HPalette);
var
NeedNewBitmap : boolean;
begin
if (Value <> FLocalPalette) then
begin
// Zap old palette
if (FLocalPalette <> 0) then
DeleteObject(FLocalPalette);
// Zap bitmap unless new palette is same as bitmaps own
NeedNewBitmap := (FBitmap <> nil) and (Value <> FBitmap.Palette);
// Use new palette
FLocalPalette := Value;
if (NeedNewBitmap) then
begin
// Need to create new bitmap and repaint
FreeBitmap;
Image.PaletteModified := True;
Image.Changed(Self);
end;
end;
end;
procedure TGIFSubImage.NeedImage;
begin
if (FData = nil) then
NewImage;
if (FDataSize = 0) then
Error(sEmptyImage);
end;
procedure TGIFSubImage.NewImage;
var
NewSize : longInt;
begin
FreeImage;
NewSize := Height * Width;
if (NewSize <> 0) then
begin
GetMem(FData, NewSize);
FillChar(FData^, NewSize, 0);
end else
FData := nil;
FDataSize := NewSize;
end;
procedure TGIFSubImage.FreeImage;
begin
if (FData <> nil) then
FreeMem(FData);
FDataSize := 0;
FData := nil;
end;
function TGIFSubImage.GetHasBitmap: boolean;
begin
Result := (FBitmap <> nil);
end;
procedure TGIFSubImage.SetHasBitmap(Value: boolean);
begin
if (Value <> (FBitmap <> nil)) then
begin
if (Value) then
Bitmap // Referencing Bitmap will automatically create it
else
FreeBitmap;
end;
end;
procedure TGIFSubImage.NewBitmap;
begin
FreeBitmap;
FBitmap := TBitmap.Create;
end;
procedure TGIFSubImage.FreeBitmap;
begin
if (FBitmap <> nil) then
begin
FBitmap.Free;
FBitmap := nil;
end;
end;
procedure TGIFSubImage.FreeMask;
begin
if (FMask <> 0) then
begin
DeleteObject(FMask);
FMask := 0;
end;
FNeedMask := True;
end;
function TGIFSubImage.HasMask: boolean;
begin
if (FNeedMask) and (Transparent) then
begin
// Zap old bitmap
FreeBitmap;
// Create new bitmap and mask
GetBitmap;
end;
Result := (FMask <> 0);
end;
function TGIFSubImage.GetBounds(Index: integer): WORD;
begin
case (Index) of
1: Result := FImageDescriptor.Left;
2: Result := FImageDescriptor.Top;
3: Result := FImageDescriptor.Width;
4: Result := FImageDescriptor.Height;
else
Result := 0; // To avoid compiler warnings
end;
end;
procedure TGIFSubImage.SetBounds(Index: integer; Value: WORD);
begin
case (Index) of
1: DoSetBounds(Value, FImageDescriptor.Top, FImageDescriptor.Width, FImageDescriptor.Height);
2: DoSetBounds(FImageDescriptor.Left, Value, FImageDescriptor.Width, FImageDescriptor.Height);
3: DoSetBounds(FImageDescriptor.Left, FImageDescriptor.Top, Value, FImageDescriptor.Height);
4: DoSetBounds(FImageDescriptor.Left, FImageDescriptor.Top, FImageDescriptor.Width, Value);
end;
end;
{$IFOPT R+}
{$DEFINE R_PLUS}
{$RANGECHECKS OFF}
{$ENDIF}
function TGIFSubImage.DoGetDitherBitmap: TBitmap;
var
ColorLookup : TColorLookup;
Ditherer : TDitherEngine;
DIBResult : TDIB;
Src : PChar;
Dst : PChar;
Row : integer;
Color : TGIFColor;
ColMap : PColorMap;
Index : byte;
TransparentIndex : byte;
IsTransparent : boolean;
WasTransparent : boolean;
MappedTransparentIndex: char;
MaskBits : PChar;
MaskDest : PChar;
MaskRow : PChar;
MaskRowWidth ,
MaskRowBitWidth : integer;
Bit ,
RightBit : BYTE;
begin
Result := TBitmap.Create;
try
{$IFNDEF VER9x}
if (Width*Height > BitmapAllocationThreshold) then
SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize
{$ENDIF}
if (Empty) then
begin
// Set bitmap width and height
Result.Width := Width;
Result.Height := Height;
// Build and copy palette to bitmap
Result.Palette := CopyPalette(Palette);
exit;
end;
ColorLookup := nil;
Ditherer := nil;
DIBResult := nil;
try // Protect above resources
ColorLookup := TNetscapeColorLookup.Create(Palette);
Ditherer := TFloydSteinbergDitherer.Create(Width, ColorLookup);
// Get DIB buffer for scanline operations
// It is assumed that the source palette is the 216 color Netscape palette
DIBResult := TDIBWriter.Create(Result, pf8bit, Width, Height, Palette);
// Determine if this image is transparent
ColMap := ActiveColorMap.Data;
IsTransparent := FNeedMask and Transparent;
WasTransparent := False;
FNeedMask := False;
TransparentIndex := 0;
MappedTransparentIndex := #0;
if (FMask = 0) and (IsTransparent) then
begin
IsTransparent := True;
TransparentIndex := GraphicControlExtension.TransparentColorIndex;
Color := ColMap[ord(TransparentIndex)];
MappedTransparentIndex := char(Color.Blue DIV 51 +
MulDiv(6, Color.Green, 51) + MulDiv(36, Color.Red, 51)+1);
end;
// Allocate bit buffer for transparency mask
MaskDest := nil;
Bit := $00;
if (IsTransparent) then
begin
MaskRowWidth := ((Width+15) DIV 16) * 2;
MaskRowBitWidth := (Width+7) DIV 8;
RightBit := $01 SHL ((8 - (Width AND $0007)) AND $0007);
GetMem(MaskBits, MaskRowWidth * Height);
FillChar(MaskBits^, MaskRowWidth * Height, 0);
end else
begin
MaskBits := nil;
MaskRowWidth := 0;
MaskRowBitWidth := 0;
RightBit := $00;
end;
try
// Process the image
Row := 0;
MaskRow := MaskBits;
Src := FData;
while (Row < Height) do
begin
if ((Row AND $1F) = 0) then
Image.Progress(Self, psRunning, MulDiv(Row, 100, Height),
False, Rect(0,0,0,0), sProgressRendering);
Dst := DIBResult.ScanLine[Row];
if (IsTransparent) then
begin
// Preset all pixels to transparent
FillChar(Dst^, Width, ord(MappedTransparentIndex));
if (Ditherer.Direction = 1) then
begin
MaskDest := MaskRow;
Bit := $80;
end else
begin
MaskDest := MaskRow + MaskRowBitWidth-1;
Bit := RightBit;
end;
end;
inc(Dst, Ditherer.Column);
while (Ditherer.Column < Ditherer.Width) and (Ditherer.Column >= 0) do
begin
Index := ord(Src^);
Color := ColMap[ord(Index)];
if (IsTransparent) and (Index = TransparentIndex) then
begin
MaskDest^ := char(byte(MaskDest^) OR Bit);
WasTransparent := True;
Ditherer.NextColumn;
end else
begin
// Dither and map a single pixel
Dst^ := Ditherer.Dither(Color.Red, Color.Green, Color.Blue,
Color.Red, Color.Green, Color.Blue);
end;
if (IsTransparent) then
begin
if (Ditherer.Direction = 1) then
begin
Bit := Bit SHR 1;
if (Bit = $00) then
begin
Bit := $80;
inc(MaskDest, 1);
end;
end else
begin
Bit := Bit SHL 1;
if (Bit = $00) then
begin
Bit := $01;
dec(MaskDest, 1);
end;
end;
end;
inc(Src, Ditherer.Direction);
inc(Dst, Ditherer.Direction);
end;
if (IsTransparent) then
Inc(MaskRow, MaskRowWidth);
Inc(Row);
inc(Src, Width-Ditherer.Direction);
Ditherer.NextLine;
end;
// Transparent paint needs a mask bitmap
if (IsTransparent) and (WasTransparent) then
FMask := CreateBitmap(Width, Height, 1, 1, MaskBits);
finally
if (MaskBits <> nil) then
FreeMem(MaskBits);
end;
finally
if (ColorLookup <> nil) then
ColorLookup.Free;
if (Ditherer <> nil) then
Ditherer.Free;
if (DIBResult <> nil) then
DIBResult.Free;
end;
except
Result.Free;
raise;
end;
end;
{$IFDEF R_PLUS}
{$RANGECHECKS ON}
{$UNDEF R_PLUS}
{$ENDIF}
function TGIFSubImage.DoGetBitmap: TBitmap;
var
ScanLineRow : Integer;
DIBResult : TDIB;
DestScanLine ,
Src : PChar;
TransparentIndex : byte;
IsTransparent : boolean;
WasTransparent : boolean;
MaskBits : PChar;
MaskDest : PChar;
MaskRow : PChar;
MaskRowWidth : integer;
Col : integer;
MaskByte : byte;
Bit : byte;
begin
Result := TBitmap.Create;
try
{$IFNDEF VER9x}
if (Width*Height > BitmapAllocationThreshold) then
SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize
{$ENDIF}
if (Empty) then
begin
// Set bitmap width and height
Result.Width := Width;
Result.Height := Height;
// Build and copy palette to bitmap
Result.Palette := CopyPalette(Palette);
exit;
end;
// Get DIB buffer for scanline operations
DIBResult := TDIBWriter.Create(Result, pf8bit, Width, Height, Palette);
try
// Determine if this image is transparent
IsTransparent := FNeedMask and Transparent;
WasTransparent := False;
FNeedMask := False;
TransparentIndex := 0;
if (FMask = 0) and (IsTransparent) then
begin
IsTransparent := True;
TransparentIndex := GraphicControlExtension.TransparentColorIndex;
end;
// Allocate bit buffer for transparency mask
if (IsTransparent) then
begin
MaskRowWidth := ((Width+15) DIV 16) * 2;
GetMem(MaskBits, MaskRowWidth * Height);
FillChar(MaskBits^, MaskRowWidth * Height, 0);
IsTransparent := (MaskBits <> nil);
end else
begin
MaskBits := nil;
MaskRowWidth := 0;
end;
try
ScanLineRow := 0;
Src := FData;
MaskRow := MaskBits;
while (ScanLineRow < Height) do
begin
DestScanline := DIBResult.ScanLine[ScanLineRow];
if ((ScanLineRow AND $1F) = 0) then
Image.Progress(Self, psRunning, MulDiv(ScanLineRow, 100, Height),
False, Rect(0,0,0,0), sProgressRendering);
Move(Src^, DestScanline^, Width);
Inc(ScanLineRow);
if (IsTransparent) then
begin
Bit := $80;
MaskDest := MaskRow;
MaskByte := 0;
for Col := 0 to Width-1 do
begin
// Set a bit in the mask if the pixel is transparent
if (Src^ = char(TransparentIndex)) then
MaskByte := MaskByte OR Bit;
Bit := Bit SHR 1;
if (Bit = $00) then
begin
// Store a mask byte for each 8 pixels
Bit := $80;
WasTransparent := WasTransparent or (MaskByte <> 0);
MaskDest^ := char(MaskByte);
inc(MaskDest);
MaskByte := 0;
end;
Inc(Src);
end;
// Save the last mask byte in case the width isn't divisable by 8
if (MaskByte <> 0) then
begin
WasTransparent := True;
MaskDest^ := char(MaskByte);
end;
Inc(MaskRow, MaskRowWidth);
end else
Inc(Src, Width);
end;
// Transparent paint needs a mask bitmap
if (IsTransparent) and (WasTransparent) then
FMask := CreateBitmap(Width, Height, 1, 1, MaskBits);
finally
if (MaskBits <> nil) then
FreeMem(MaskBits);
end;
finally
// Free DIB buffer used for scanline operations
DIBResult.Free;
end;
except
Result.Free;
raise;
end;
end;
{$ifdef DEBUG_RENDERPERFORMANCE}
var
ImageCount : DWORD = 0;
RenderTime : DWORD = 0;
{$endif}
function TGIFSubImage.GetBitmap: TBitmap;
var
n : integer;
{$ifdef DEBUG_RENDERPERFORMANCE}
RenderStartTime : DWORD;
{$endif}
begin
{$ifdef DEBUG_RENDERPERFORMANCE}
if (GetAsyncKeyState(VK_CONTROL) <> 0) then
begin
ShowMessage(format('Render %d images in %d mS, Rate %d mS/image (%d images/S)',
[ImageCount, RenderTime,
RenderTime DIV (ImageCount+1),
MulDiv(ImageCount, 1000, RenderTime+1)]));
end;
{$endif}
Result := FBitmap;
if (Result <> nil) or (Empty) then
Exit;
{$ifdef DEBUG_RENDERPERFORMANCE}
inc(ImageCount);
RenderStartTime := timeGetTime;
{$endif}
try
Image.Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressRendering);
try
if (Image.DoDither) then
// Create dithered bitmap
FBitmap := DoGetDitherBitmap
else
// Create "regular" bitmap
FBitmap := DoGetBitmap;
Result := FBitmap;
finally
if ExceptObject = nil then
n := 100
else
n := 0;
Image.Progress(Self, psEnding, n, Image.PaletteModified, Rect(0,0,0,0),
sProgressRendering);
// Make sure new palette gets realized, in case OnProgress event didn't.
if Image.PaletteModified then
Image.Changed(Self);
end;
except
on EAbort do ; // OnProgress can raise EAbort to cancel image load
end;
{$ifdef DEBUG_RENDERPERFORMANCE}
inc(RenderTime, timeGetTime-RenderStartTime);
{$endif}
end;
procedure TGIFSubImage.SetBitmap(Value: TBitmap);
begin
FreeBitmap;
if (Value <> nil) then
Assign(Value);
end;
function TGIFSubImage.GetActiveColorMap: TGIFColorMap;
begin
if (ColorMap.Count > 0) or (Image.GlobalColorMap.Count = 0) then
Result := ColorMap
else
Result := Image.GlobalColorMap;
end;
function TGIFSubImage.GetInterlaced: boolean;
begin
Result := (FImageDescriptor.PackedFields AND idInterlaced) <> 0;
end;
procedure TGIFSubImage.SetInterlaced(Value: boolean);
begin
if (Value) then
FImageDescriptor.PackedFields := FImageDescriptor.PackedFields OR idInterlaced
else
FImageDescriptor.PackedFields := FImageDescriptor.PackedFields AND NOT(idInterlaced);
end;
function TGIFSubImage.GetVersion: TGIFVersion;
var
v : TGIFVersion;
i : integer;
begin
if (ColorMap.Optimized) then
Result := gv89a
else
Result := inherited GetVersion;
i := 0;
while (Result < high(TGIFVersion)) and (i < FExtensions.Count) do
begin
v := FExtensions[i].Version;
if (v > Result) then
Result := v;
end;
end;
function TGIFSubImage.GetColorResolution: integer;
begin
Result := ColorMap.BitsPerPixel-1;
end;
function TGIFSubImage.GetBitsPerPixel: integer;
begin
Result := ColorMap.BitsPerPixel;
end;
function TGIFSubImage.GetBoundsRect: TRect;
begin
Result := Rect(FImageDescriptor.Left,
FImageDescriptor.Top,
FImageDescriptor.Left+FImageDescriptor.Width,
FImageDescriptor.Top+FImageDescriptor.Height);
end;
procedure TGIFSubImage.DoSetBounds(ALeft, ATop, AWidth, AHeight: integer);
var
TooLarge : boolean;
Zap : boolean;
begin
Zap := (FImageDescriptor.Width <> Width) or (FImageDescriptor.Height <> AHeight);
FImageDescriptor.Left := ALeft;
FImageDescriptor.Top := ATop;
FImageDescriptor.Width := AWidth;
FImageDescriptor.Height := AHeight;
// Delete existing image and bitmaps if size has changed
if (Zap) then
begin
FreeBitmap;
FreeMask;
FreeImage;
// ...and allocate a new image
NewImage;
end;
TooLarge := False;
// Set width & height if added image is larger than existing images
{$IFDEF STRICT_MOZILLA}
// From Mozilla source:
// Work around broken GIF files where the logical screen
// size has weird width or height. [...]
if (Image.Width < AWidth) or (Image.Height < AHeight) then
begin
TooLarge := True;
Image.Width := AWidth;
Image.Height := AHeight;
Left := 0;
Top := 0;
end;
{$ELSE}
if (Image.Width < ALeft+AWidth) then
begin
if (Image.Width > 0) then
begin
TooLarge := True;
Warning(gsWarning, sBadWidth)
end;
Image.Width := ALeft+AWidth;
end;
if (Image.Height < ATop+AHeight) then
begin
if (Image.Height > 0) then
begin
TooLarge := True;
Warning(gsWarning, sBadHeight)
end;
Image.Height := ATop+AHeight;
end;
{$ENDIF}
if (TooLarge) then
Warning(gsWarning, sScreenSizeExceeded);
end;
procedure TGIFSubImage.SetBoundsRect(const Value: TRect);
begin
DoSetBounds(Value.Left, Value.Top, Value.Right-Value.Left+1, Value.Bottom-Value.Top+1);
end;
function TGIFSubImage.GetClientRect: TRect;
begin
Result := Rect(0, 0, FImageDescriptor.Width, FImageDescriptor.Height);
end;
function TGIFSubImage.GetPixel(x, y: integer): BYTE;
begin
if (x < 0) or (x > Width-1) then
Error(sBadPixelCoordinates);
Result := BYTE(PChar(longInt(Scanline[y]) + x)^);
end;
function TGIFSubImage.GetScanline(y: integer): pointer;
begin
if (y < 0) or (y > Height-1) then
Error(sBadPixelCoordinates);
NeedImage;
Result := pointer(longInt(FData) + y * Width);
end;
procedure TGIFSubImage.Prepare;
var
Pack : BYTE;
begin
Pack := FImageDescriptor.PackedFields;
if (ColorMap.Count > 0) then
begin
Pack := idLocalColorTable;
if (ColorMap.Optimized) then
Pack := Pack OR idSort;
Pack := (Pack AND NOT(idColorTableSize)) OR (ColorResolution AND idColorTableSize);
end else
Pack := Pack AND NOT(idLocalColorTable OR idSort OR idColorTableSize);
FImageDescriptor.PackedFields := Pack;
end;
procedure TGIFSubImage.SaveToStream(Stream: TStream);
begin
FExtensions.SaveToStream(Stream);
if (Empty) then
exit;
Prepare;
Stream.Write(FImageDescriptor, sizeof(TImageDescriptor));
ColorMap.SaveToStream(Stream);
Compress(Stream);
end;
procedure TGIFSubImage.LoadFromStream(Stream: TStream);
var
ColorCount : integer;
b : BYTE;
begin
Clear;
FExtensions.LoadFromStream(Stream, self);
// Check for extension without image
if (Stream.Read(b, 1) <> 1) then
exit;
Stream.Seek(-1, soFromCurrent);
if (b = bsTrailer) or (b = 0) then
exit;
ReadCheck(Stream, FImageDescriptor, sizeof(TImageDescriptor));
// From Mozilla source:
// Work around more broken GIF files that have zero image
// width or height
if (FImageDescriptor.Height = 0) or (FImageDescriptor.Width = 0) then
begin
FImageDescriptor.Height := Image.Height;
FImageDescriptor.Width := Image.Width;
Warning(gsWarning, sScreenSizeExceeded);
end;
if (FImageDescriptor.PackedFields AND idLocalColorTable = idLocalColorTable) then
begin
ColorCount := 2 SHL (FImageDescriptor.PackedFields AND idColorTableSize);
if (ColorCount < 2) or (ColorCount > 256) then
Error(sImageBadColorSize);
ColorMap.LoadFromStream(Stream, ColorCount);
end;
Decompress(Stream);
// On-load rendering
if (GIFImageRenderOnLoad) then
// Touch bitmap to force frame to be rendered
Bitmap;
end;
procedure TGIFSubImage.AssignTo(Dest: TPersistent);
begin
if (Dest is TBitmap) then
Dest.Assign(Bitmap)
else
inherited AssignTo(Dest);
end;
procedure TGIFSubImage.Assign(Source: TPersistent);
var
MemoryStream : TMemoryStream;
i : integer;
PixelFormat : TPixelFormat;
DIBSource : TDIB;
ABitmap : TBitmap;
procedure Import8Bit(Dest: PChar);
var
y : integer;
begin
// Copy colormap
{$ifdef VER10_PLUS}
if (FBitmap.HandleType = bmDIB) then
FColorMap.ImportDIBColors(FBitmap.Canvas.Handle)
else
{$ENDIF}
FColorMap.ImportPalette(FBitmap.Palette);
// Copy pixels
for y := 0 to Height-1 do
begin
if ((y AND $1F) = 0) then
Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting);
Move(DIBSource.Scanline[y]^, Dest^, Width);
inc(Dest, Width);
end;
end;
procedure Import4Bit(Dest: PChar);
var
x, y : integer;
Scanline : PChar;
begin
// Copy colormap
FColorMap.ImportPalette(FBitmap.Palette);
// Copy pixels
for y := 0 to Height-1 do
begin
if ((y AND $1F) = 0) then
Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting);
ScanLine := DIBSource.Scanline[y];
for x := 0 to Width-1 do
begin
if (x AND $01 = 0) then
Dest^ := chr(ord(ScanLine^) SHR 4)
else
begin
Dest^ := chr(ord(ScanLine^) AND $0F);
inc(ScanLine);
end;
inc(Dest);
end;
end;
end;
procedure Import1Bit(Dest: PChar);
var
x, y : integer;
Scanline : PChar;
Bit : integer;
Byte : integer;
begin
// Copy colormap
FColorMap.ImportPalette(FBitmap.Palette);
// Copy pixels
for y := 0 to Height-1 do
begin
if ((y AND $1F) = 0) then
Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting);
ScanLine := DIBSource.Scanline[y];
x := Width;
Bit := 0;
Byte := 0; // To avoid compiler warning
while (x > 0) do
begin
if (Bit = 0) then
begin
Bit := 8;
Byte := ord(ScanLine^);
inc(Scanline);
end;
Dest^ := chr((Byte AND $80) SHR 7);
Byte := Byte SHL 1;
inc(Dest);
dec(Bit);
dec(x);
end;
end;
end;
procedure Import24Bit(Dest: PChar);
type
TCacheEntry = record
Color : TColor;
Index : integer;
end;
const
// Size of palette cache. Must be 2^n.
// The cache holds the palette index of the last "CacheSize" colors
// processed. Hopefully the cache can speed things up a bit... Initial
// testing shows that this is indeed the case at least for non-dithered
// bitmaps.
// All the same, a small hash table would probably be much better.
CacheSize = 8;
var
i : integer;
Cache : array[0..CacheSize-1] of TCacheEntry;
LastEntry : integer;
Scanline : PRGBTriple;
Pixel : TColor;
RGBTriple : TRGBTriple absolute Pixel;
x, y : integer;
ColorMap : PColorMap;
t : byte;
label
NextPixel;
begin
for i := 0 to CacheSize-1 do
Cache[i].Index := -1;
LastEntry := 0;
// Copy all pixels and build colormap
for y := 0 to Height-1 do
begin
if ((y AND $1F) = 0) then
Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting);
ScanLine := DIBSource.Scanline[y];
for x := 0 to Width-1 do
begin
Pixel := 0;
RGBTriple := Scanline^;
// Scan cache for color from most recently processed color to last
// recently processed. This is done because TColorMap.AddUnique is very slow.
i := LastEntry;
repeat
if (Cache[i].Index = -1) then
break;
if (Cache[i].Color = Pixel) then
begin
Dest^ := chr(Cache[i].Index);
LastEntry := i;
goto NextPixel;
end;
if (i = 0) then
i := CacheSize-1
else
dec(i);
until (i = LastEntry);
// Color not found in cache, do it the slow way instead
Dest^ := chr(FColorMap.AddUnique(Pixel));
// Add color and index to cache
LastEntry := (LastEntry + 1) AND (CacheSize-1);
Cache[LastEntry].Color := Pixel;
Cache[LastEntry].Index := ord(Dest^);
NextPixel:
Inc(Dest);
Inc(Scanline);
end;
end;
// Convert colors in colormap from BGR to RGB
ColorMap := FColorMap.Data;
i := FColorMap.Count;
while (i > 0) do
begin
t := ColorMap^[0].Red;
ColorMap^[0].Red := ColorMap^[0].Blue;
ColorMap^[0].Blue := t;
inc(integer(ColorMap), sizeof(TGIFColor));
dec(i);
end;
end;
procedure ImportViaDraw(ABitmap: TBitmap; Graphic: TGraphic);
begin
ABitmap.Height := Graphic.Height;
ABitmap.Width := Graphic.Width;
// Note: Disable the call to SafeSetPixelFormat below to import
// in max number of colors with the risk of having to use
// TCanvas.Pixels to do it (very slow).
// Make things a little easier for TGIFSubImage.Assign by converting
// pfDevice to a more import friendly format
{$ifdef SLOW_BUT_SAFE}
SafeSetPixelFormat(ABitmap, pf8bit);
{$else}
{$ifndef VER9x}
SetPixelFormat(ABitmap, pf24bit);
{$endif}
{$endif}
ABitmap.Canvas.Draw(0, 0, Graphic);
end;
procedure AddMask(Mask: TBitmap);
var
DIBReader : TDIBReader;
TransparentIndex : integer;
i ,
j : integer;
GIFPixel ,
MaskPixel : PChar;
WasTransparent : boolean;
GCE : TGIFGraphicControlExtension;
begin
// Optimize colormap to make room for transparent color
ColorMap.Optimize;
// Can't make transparent if no color or colormap full
if (ColorMap.Count = 0) or (ColorMap.Count = 256) then
exit;
// Add the transparent color to the color map
TransparentIndex := ColorMap.Add(TColor(0));
WasTransparent := False;
DIBReader := TDIBReader.Create(Mask, pf8bit);
try
for i := 0 to Height-1 do
begin
MaskPixel := DIBReader.Scanline[i];
GIFPixel := Scanline[i];
for j := 0 to Width-1 do
begin
// Change all unmasked pixels to transparent
if (MaskPixel^ <> #0) then
begin
GIFPixel^ := chr(TransparentIndex);
WasTransparent := True;
end;
inc(MaskPixel);
inc(GIFPixel);
end;
end;
finally
DIBReader.Free;
end;
// Add a Graphic Control Extension if any part of the mask was transparent
if (WasTransparent) then
begin
GCE := TGIFGraphicControlExtension.Create(self);
GCE.Transparent := True;
GCE.TransparentColorIndex := TransparentIndex;
Extensions.Add(GCE);
end else
// Otherwise removed the transparency color since it wasn't used
ColorMap.Delete(TransparentIndex);
end;
procedure AddMaskOnly(hMask: hBitmap);
var
Mask : TBitmap;
begin
if (hMask = 0) then
exit;
// Encapsulate the mask
Mask := TBitmap.Create;
try
Mask.Handle := hMask;
AddMask(Mask);
finally
Mask.ReleaseHandle;
Mask.Free;
end;
end;
procedure AddIconMask(Icon: TIcon);
var
IconInfo : TIconInfo;
begin
if (not GetIconInfo(Icon.Handle, IconInfo)) then
exit;
// Extract the icon mask
AddMaskOnly(IconInfo.hbmMask);
end;
procedure AddMetafileMask(Metafile: TMetaFile);
var
Mask1 ,
Mask2 : TBitmap;
procedure DrawMetafile(ABitmap: TBitmap; Background: TColor);
begin
ABitmap.Width := Metafile.Width;
ABitmap.Height := Metafile.Height;
{$ifndef VER9x}
SetPixelFormat(ABitmap, pf24bit);
{$endif}
ABitmap.Canvas.Brush.Color := Background;
ABitmap.Canvas.Brush.Style := bsSolid;
ABitmap.Canvas.FillRect(ABitmap.Canvas.ClipRect);
ABitmap.Canvas.Draw(0,0, Metafile);
end;
begin
// Create the metafile mask
Mask1 := TBitmap.Create;
try
Mask2 := TBitmap.Create;
try
DrawMetafile(Mask1, clWhite);
DrawMetafile(Mask2, clBlack);
Mask1.Canvas.CopyMode := cmSrcInvert;
Mask1.Canvas.Draw(0,0, Mask2);
AddMask(Mask1);
finally
Mask2.Free;
end;
finally
Mask1.Free;
end;
end;
begin
if (Source = self) then
exit;
if (Source = nil) then
begin
Clear;
end else
//
// TGIFSubImage import
//
if (Source is TGIFSubImage) then
begin
// Zap existing colormap, extensions and bitmap
Clear;
if (TGIFSubImage(Source).Empty) then
exit;
// Copy source data
FImageDescriptor := TGIFSubImage(Source).FImageDescriptor;
FTransparent := TGIFSubImage(Source).Transparent;
// Copy image data
NewImage;
if (FData <> nil) and (TGIFSubImage(Source).Data <> nil) then
Move(TGIFSubImage(Source).Data^, FData^, FDataSize);
// Copy palette
FColorMap.Assign(TGIFSubImage(Source).ColorMap);
// Copy extensions
if (TGIFSubImage(Source).Extensions.Count > 0) then
begin
MemoryStream := TMemoryStream.Create;
try
TGIFSubImage(Source).Extensions.SaveToStream(MemoryStream);
MemoryStream.Seek(0, soFromBeginning);
Extensions.LoadFromStream(MemoryStream, Self);
finally
MemoryStream.Free;
end;
end;
// Copy bitmap representation
// (Not really nescessary but improves performance if the bitmap is needed
// later on)
if (TGIFSubImage(Source).HasBitmap) then
begin
NewBitmap;
FBitmap.Assign(TGIFSubImage(Source).Bitmap);
end;
end else
//
// Bitmap import
//
if (Source is TBitmap) then
begin
// Zap existing colormap, extensions and bitmap
Clear;
if (TBitmap(Source).Empty) then
exit;
Width := TBitmap(Source).Width;
Height := TBitmap(Source).Height;
PixelFormat := GetPixelFormat(TBitmap(Source));
{$ifdef VER9x}
// Delphi 2 TBitmaps are always DDBs. This means that if a 24 bit
// bitmap is loaded in 8 bit device mode, TBitmap.PixelFormat will
// be pf8bit, but TBitmap.Palette will be 0!
if (TBitmap(Source).Palette = 0) then
PixelFormat := pfDevice;
{$endif}
if (PixelFormat > pf8bit) or (PixelFormat = pfDevice) then
begin
// Convert image to 8 bits/pixel or less
FBitmap := ReduceColors(TBitmap(Source), Image.ColorReduction,
Image.DitherMode, Image.ReductionBits, 0);
PixelFormat := GetPixelFormat(FBitmap);
end else
begin
// Create new bitmap and copy
NewBitmap;
FBitmap.Assign(TBitmap(Source));
end;
// Allocate new buffer
NewImage;
Image.Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressConverting);
try
{$ifdef VER9x}
// This shouldn't happen, but better safe...
if (FBitmap.Palette = 0) then
PixelFormat := pf24bit;
{$endif}
if (not(PixelFormat in [pf1bit, pf4bit, pf8bit, pf24bit])) then
PixelFormat := pf24bit;
DIBSource := TDIBReader.Create(FBitmap, PixelFormat);
try
// Copy pixels
case (PixelFormat) of
pf8bit: Import8Bit(Fdata);
pf4bit: Import4Bit(Fdata);
pf1bit: Import1Bit(Fdata);
else
// Error(sUnsupportedBitmap);
Import24Bit(Fdata);
end;
finally
DIBSource.Free;
end;
{$ifdef VER10_PLUS}
// Add mask for transparent bitmaps
if (TBitmap(Source).Transparent) then
AddMaskOnly(TBitmap(Source).MaskHandle);
{$endif}
finally
if ExceptObject = nil then
i := 100
else
i := 0;
Image.Progress(Self, psEnding, i, Image.PaletteModified, Rect(0,0,0,0), sProgressConverting);
end;
end else
//
// TGraphic import
//
if (Source is TGraphic) then
begin
// Zap existing colormap, extensions and bitmap
Clear;
if (TGraphic(Source).Empty) then
exit;
ABitmap := TBitmap.Create;
try
// Import TIcon and TMetafile by drawing them onto a bitmap...
// ...and then importing the bitmap recursively
if (Source is TIcon) or (Source is TMetafile) then
begin
try
ImportViaDraw(ABitmap, TGraphic(Source))
except
// If import via TCanvas.Draw fails (which it shouldn't), we try the
// Assign mechanism instead
ABitmap.Assign(Source);
end;
end else
try
ABitmap.Assign(Source);
except
// If automatic conversion to bitmap fails, we try and draw the
// graphic on the bitmap instead
ImportViaDraw(ABitmap, TGraphic(Source));
end;
// Convert the bitmap to a GIF frame recursively
Assign(ABitmap);
finally
ABitmap.Free;
end;
// Import transparency mask
if (Source is TIcon) then
AddIconMask(TIcon(Source));
if (Source is TMetaFile) then
AddMetafileMask(TMetaFile(Source));
end else
//
// TPicture import
//
if (Source is TPicture) then
begin
// Recursively import TGraphic
Assign(TPicture(Source).Graphic);
end else
// Unsupported format - fall back to Source.AssignTo
inherited Assign(Source);
end;
// Copied from D3 graphics.pas
// Fixed by Brian Lowe of Acro Technology Inc. 30Jan98
function TransparentStretchBlt(DstDC: HDC; DstX, DstY, DstW, DstH: Integer;
SrcDC: HDC; SrcX, SrcY, SrcW, SrcH: Integer; MaskDC: HDC; MaskX,
MaskY: Integer): Boolean;
const
ROP_DstCopy = $00AA0029;
var
MemDC ,
OrMaskDC : HDC;
MemBmp ,
OrMaskBmp : HBITMAP;
Save ,
OrMaskSave : THandle;
crText, crBack : TColorRef;
SavePal : HPALETTE;
begin
Result := True;
if (Win32Platform = VER_PLATFORM_WIN32_NT) and (SrcW = DstW) and (SrcH = DstH) then
begin
MemBmp := GDICheck(CreateCompatibleBitmap(SrcDC, 1, 1));
MemBmp := SelectObject(MaskDC, MemBmp);
try
MaskBlt(DstDC, DstX, DstY, DstW, DstH, SrcDC, SrcX, SrcY, MemBmp, MaskX,
MaskY, MakeRop4(ROP_DstCopy, SrcCopy));
finally
MemBmp := SelectObject(MaskDC, MemBmp);
DeleteObject(MemBmp);
end;
Exit;
end;
SavePal := 0;
MemDC := GDICheck(CreateCompatibleDC(DstDC));
try
{ Color bitmap for combining OR mask with source bitmap }
MemBmp := GDICheck(CreateCompatibleBitmap(DstDC, SrcW, SrcH));
try
Save := SelectObject(MemDC, MemBmp);
try
{ This bitmap needs the size of the source but DC of the dest }
OrMaskDC := GDICheck(CreateCompatibleDC(DstDC));
try
{ Need a monochrome bitmap for OR mask!! }
OrMaskBmp := GDICheck(CreateBitmap(SrcW, SrcH, 1, 1, nil));
try
OrMaskSave := SelectObject(OrMaskDC, OrMaskBmp);
try
// OrMask := 1
// Original: BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, OrMaskDC, SrcX, SrcY, WHITENESS);
// Replacement, but not needed: PatBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, WHITENESS);
// OrMask := OrMask XOR Mask
// Not needed: BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, MaskDC, SrcX, SrcY, SrcInvert);
// OrMask := NOT Mask
BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, MaskDC, SrcX, SrcY, NotSrcCopy);
// Retrieve source palette (with dummy select)
SavePal := SelectPalette(SrcDC, SystemPalette16, False);
// Restore source palette
SelectPalette(SrcDC, SavePal, False);
// Select source palette into memory buffer
if SavePal <> 0 then
SavePal := SelectPalette(MemDC, SavePal, True)
else
SavePal := SelectPalette(MemDC, SystemPalette16, True);
RealizePalette(MemDC);
// Mem := OrMask
BitBlt(MemDC, SrcX, SrcY, SrcW, SrcH, OrMaskDC, SrcX, SrcY, SrcCopy);
// Mem := Mem AND Src
{$IFNDEF GIF_TESTMASK} // Define GIF_TESTMASK if you want to know what it does...
BitBlt(MemDC, SrcX, SrcY, SrcW, SrcH, SrcDC, SrcX, SrcY, SrcAnd);
{$ELSE}
StretchBlt(DstDC, DstX, DstY, DstW DIV 2, DstH, MemDC, SrcX, SrcY, SrcW, SrcH, SrcCopy);
StretchBlt(DstDC, DstX+DstW DIV 2, DstY, DstW DIV 2, DstH, SrcDC, SrcX, SrcY, SrcW, SrcH, SrcCopy);
exit;
{$ENDIF}
finally
if (OrMaskSave <> 0) then
SelectObject(OrMaskDC, OrMaskSave);
end;
finally
DeleteObject(OrMaskBmp);
end;
finally
DeleteDC(OrMaskDC);
end;
crText := SetTextColor(DstDC, $00000000);
crBack := SetBkColor(DstDC, $00FFFFFF);
{ All color rendering is done at 1X (no stretching),
then final 2 masks are stretched to dest DC }
// Neat trick!
// Dst := Dst AND Mask
StretchBlt(DstDC, DstX, DstY, DstW, DstH, MaskDC, SrcX, SrcY, SrcW, SrcH, SrcAnd);
// Dst := Dst OR Mem
StretchBlt(DstDC, DstX, DstY, DstW, DstH, MemDC, SrcX, SrcY, SrcW, SrcH, SrcPaint);
SetTextColor(DstDC, crText);
SetTextColor(DstDC, crBack);
finally
if (Save <> 0) then
SelectObject(MemDC, Save);
end;
finally
DeleteObject(MemBmp);
end;
finally
if (SavePal <> 0) then
SelectPalette(MemDC, SavePal, False);
DeleteDC(MemDC);
end;
end;
procedure TGIFSubImage.Draw(ACanvas: TCanvas; const Rect: TRect;
DoTransparent, DoTile: boolean);
begin
if (DoTile) then
StretchDraw(ACanvas, Rect, DoTransparent, DoTile)
else
StretchDraw(ACanvas, ScaleRect(Rect), DoTransparent, DoTile);
end;
type
// Dummy class used to gain access to protected method TCanvas.Changed
TChangableCanvas = class(TCanvas)
end;
procedure TGIFSubImage.StretchDraw(ACanvas: TCanvas; const Rect: TRect;
DoTransparent, DoTile: boolean);
var
MaskDC : HDC;
Save : THandle;
Tile : TRect;
{$ifdef DEBUG_DRAWPERFORMANCE}
ImageCount ,
TimeStart ,
TimeStop : DWORD;
{$endif}
begin
{$ifdef DEBUG_DRAWPERFORMANCE}
TimeStart := timeGetTime;
ImageCount := 0;
{$endif}
if (DoTransparent) and (Transparent) and (HasMask) then
begin
// Draw transparent using mask
Save := 0;
MaskDC := 0;
try
MaskDC := GDICheck(CreateCompatibleDC(0));
Save := SelectObject(MaskDC, FMask);
if (DoTile) then
begin
Tile.Left := Rect.Left+Left;
Tile.Right := Tile.Left + Width;
while (Tile.Left < Rect.Right) do
begin
Tile.Top := Rect.Top+Top;
Tile.Bottom := Tile.Top + Height;
while (Tile.Top < Rect.Bottom) do
begin
TransparentStretchBlt(ACanvas.Handle, Tile.Left, Tile.Top, Width, Height,
Bitmap.Canvas.Handle, 0, 0, Width, Height, MaskDC, 0, 0);
Tile.Top := Tile.Top + Image.Height;
Tile.Bottom := Tile.Bottom + Image.Height;
{$ifdef DEBUG_DRAWPERFORMANCE}
inc(ImageCount);
{$endif}
end;
Tile.Left := Tile.Left + Image.Width;
Tile.Right := Tile.Right + Image.Width;
end;
end else
TransparentStretchBlt(ACanvas.Handle, Rect.Left, Rect.Top,
Rect.Right - Rect.Left, Rect.Bottom - Rect.Top,
Bitmap.Canvas.Handle, 0, 0, Width, Height, MaskDC, 0, 0);
// Since we are not using any of the TCanvas functions (only handle)
// we need to fire the TCanvas.Changed method "manually".
TChangableCanvas(ACanvas).Changed;
finally
if (Save <> 0) then
SelectObject(MaskDC, Save);
if (MaskDC <> 0) then
DeleteDC(MaskDC);
end;
end else
begin
if (DoTile) then
begin
Tile.Left := Rect.Left+Left;
Tile.Right := Tile.Left + Width;
while (Tile.Left < Rect.Right) do
begin
Tile.Top := Rect.Top+Top;
Tile.Bottom := Tile.Top + Height;
while (Tile.Top < Rect.Bottom) do
begin
ACanvas.StretchDraw(Tile, Bitmap);
Tile.Top := Tile.Top + Image.Height;
Tile.Bottom := Tile.Bottom + Image.Height;
{$ifdef DEBUG_DRAWPERFORMANCE}
inc(ImageCount);
{$endif}
end;
Tile.Left := Tile.Left + Image.Width;
Tile.Right := Tile.Right + Image.Width;
end;
end else
ACanvas.StretchDraw(Rect, Bitmap);
end;
{$ifdef DEBUG_DRAWPERFORMANCE}
if (GetAsyncKeyState(VK_CONTROL) <> 0) then
begin
TimeStop := timeGetTime;
ShowMessage(format('Draw %d images in %d mS, Rate %d images/mS (%d images/S)',
[ImageCount, TimeStop-TimeStart,
ImageCount DIV (TimeStop-TimeStart+1),
MulDiv(ImageCount, 1000, TimeStop-TimeStart+1)]));
end;
{$endif}
end;
// Given a destination rect (DestRect) calculates the
// area covered by this sub image
function TGIFSubImage.ScaleRect(DestRect: TRect): TRect;
var
HeightMul ,
HeightDiv : integer;
WidthMul ,
WidthDiv : integer;
begin
HeightDiv := Image.Height;
HeightMul := DestRect.Bottom-DestRect.Top;
WidthDiv := Image.Width;
WidthMul := DestRect.Right-DestRect.Left;
Result.Left := DestRect.Left + muldiv(Left, WidthMul, WidthDiv);
Result.Top := DestRect.Top + muldiv(Top, HeightMul, HeightDiv);
Result.Right := DestRect.Left + muldiv(Left+Width, WidthMul, WidthDiv);
Result.Bottom := DestRect.Top + muldiv(Top+Height, HeightMul, HeightDiv);
end;
procedure TGIFSubImage.Crop;
var
TransparentColorIndex : byte;
CropLeft ,
CropTop ,
CropRight ,
CropBottom : integer;
WasTransparent : boolean;
i : integer;
NewSize : integer;
NewData : PChar;
NewWidth ,
NewHeight : integer;
pSource ,
pDest : PChar;
begin
if (Empty) or (not Transparent) then
exit;
TransparentColorIndex := GraphicControlExtension.TransparentColorIndex;
CropLeft := 0;
CropRight := Width - 1;
CropTop := 0;
CropBottom := Height - 1;
// Find left edge
WasTransparent := True;
while (CropLeft <= CropRight) and (WasTransparent) do
begin
for i := CropTop to CropBottom do
if (Pixels[CropLeft, i] <> TransparentColorIndex) then
begin
WasTransparent := False;
break;
end;
if (WasTransparent) then
inc(CropLeft);
end;
// Find right edge
WasTransparent := True;
while (CropLeft <= CropRight) and (WasTransparent) do
begin
for i := CropTop to CropBottom do
if (pixels[CropRight, i] <> TransparentColorIndex) then
begin
WasTransparent := False;
break;
end;
if (WasTransparent) then
dec(CropRight);
end;
if (CropLeft <= CropRight) then
begin
// Find top edge
WasTransparent := True;
while (CropTop <= CropBottom) and (WasTransparent) do
begin
for i := CropLeft to CropRight do
if (pixels[i, CropTop] <> TransparentColorIndex) then
begin
WasTransparent := False;
break;
end;
if (WasTransparent) then
inc(CropTop);
end;
// Find bottom edge
WasTransparent := True;
while (CropTop <= CropBottom) and (WasTransparent) do
begin
for i := CropLeft to CropRight do
if (pixels[i, CropBottom] <> TransparentColorIndex) then
begin
WasTransparent := False;
break;
end;
if (WasTransparent) then
dec(CropBottom);
end;
end;
if (CropLeft > CropRight) or (CropTop > CropBottom) then
begin
// Cropped to nothing - frame is invisible
Clear;
end else
begin
// Crop frame - move data
NewWidth := CropRight - CropLeft + 1;
Newheight := CropBottom - CropTop + 1;
NewSize := NewWidth * NewHeight;
GetMem(NewData, NewSize);
pSource := PChar(integer(FData) + CropTop * Width + CropLeft);
pDest := NewData;
for i := 0 to NewHeight-1 do
begin
Move(pSource^, pDest^, NewWidth);
inc(pSource, Width);
inc(pDest, NewWidth);
end;
FreeImage;
FData := NewData;
FDataSize := NewSize;
inc(FImageDescriptor.Left, CropLeft);
inc(FImageDescriptor.Top, CropTop);
FImageDescriptor.Width := NewWidth;
FImageDescriptor.Height := NewHeight;
FreeBitmap;
FreeMask
end;
end;
procedure TGIFSubImage.Merge(Previous: TGIFSubImage);
var
SourceIndex ,
DestIndex : byte;
SourceTransparent : boolean;
NeedTransparentColorIndex: boolean;
PreviousRect ,
ThisRect ,
MergeRect : TRect;
PreviousY ,
X ,
Y : integer;
pSource ,
pDest : PChar;
pSourceMap ,
pDestMap : PColorMap;
GCE : TGIFGraphicControlExtension;
function CanMakeTransparent: boolean;
begin
// Is there a local color map...
if (ColorMap.Count > 0) then
// ...and is there room in it?
Result := (ColorMap.Count < 256)
// Is there a global color map...
else if (Image.GlobalColorMap.Count > 0) then
// ...and is there room in it?
Result := (Image.GlobalColorMap.Count < 256)
else
Result := False;
end;
function GetTransparentColorIndex: byte;
var
i : integer;
begin
if (ColorMap.Count > 0) then
begin
// Get the transparent color from the local color map
Result := ColorMap.Add(TColor(0));
end else
begin
// Are any other frames using the global color map for transparency
for i := 0 to Image.Images.Count-1 do
if (Image.Images[i] <> self) and (Image.Images[i].Transparent) and
(Image.Images[i].ColorMap.Count = 0) then
begin
// Use the same transparency color as the other frame
Result := Image.Images[i].GraphicControlExtension.TransparentColorIndex;
exit;
end;
// Get the transparent color from the global color map
Result := Image.GlobalColorMap.Add(TColor(0));
end;
end;
begin
// Determine if it is possible to merge this frame
if (Empty) or (Previous = nil) or (Previous.Empty) or
((Previous.GraphicControlExtension <> nil) and
(Previous.GraphicControlExtension.Disposal in [dmBackground, dmPrevious])) then
exit;
PreviousRect := Previous.BoundsRect;
ThisRect := BoundsRect;
// Cannot merge unless the frames intersect
if (not IntersectRect(MergeRect, PreviousRect, ThisRect)) then
exit;
// If the frame isn't already transparent, determine
// if it is possible to make it so
if (Transparent) then
begin
DestIndex := GraphicControlExtension.TransparentColorIndex;
NeedTransparentColorIndex := False;
end else
begin
if (not CanMakeTransparent) then
exit;
DestIndex := 0; // To avoid compiler warning
NeedTransparentColorIndex := True;
end;
SourceTransparent := Previous.Transparent;
if (SourceTransparent) then
SourceIndex := Previous.GraphicControlExtension.TransparentColorIndex
else
SourceIndex := 0; // To avoid compiler warning
PreviousY := MergeRect.Top - Previous.Top;
pSourceMap := Previous.ActiveColorMap.Data;
pDestMap := ActiveColorMap.Data;
for Y := MergeRect.Top - Top to MergeRect.Bottom - Top-1 do
begin
pSource := PChar(integer(Previous.Scanline[PreviousY]) + MergeRect.Left - Previous.Left);
pDest := PChar(integer(Scanline[Y]) + MergeRect.Left - Left);
for X := MergeRect.Left to MergeRect.Right-1 do
begin
// Ignore pixels if either this frame's or the previous frame's pixel is transparent
if (
not(
((not NeedTransparentColorIndex) and (pDest^ = char(DestIndex))) or
((SourceTransparent) and (pSource^ = char(SourceIndex)))
)
) and (
// Replace same colored pixels with transparency
((pDestMap = pSourceMap) and (pDest^ = pSource^)) or
(CompareMem(@(pDestMap^[ord(pDest^)]), @(pSourceMap^[ord(pSource^)]), sizeof(TGIFColor)))
) then
begin
if (NeedTransparentColorIndex) then
begin
NeedTransparentColorIndex := False;
DestIndex := GetTransparentColorIndex;
end;
pDest^ := char(DestIndex);
end;
inc(pDest);
inc(pSource);
end;
inc(PreviousY);
end;
(*
** Create a GCE if the frame wasn't already transparent and any
** pixels were made transparent
*)
if (not Transparent) and (not NeedTransparentColorIndex) then
begin
if (GraphicControlExtension = nil) then
begin
GCE := TGIFGraphicControlExtension.Create(self);
Extensions.Add(GCE);
end else
GCE := GraphicControlExtension;
GCE.Transparent := True;
GCE.TransparentColorIndex := DestIndex;
end;
FreeBitmap;
FreeMask
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFTrailer
//
////////////////////////////////////////////////////////////////////////////////
procedure TGIFTrailer.SaveToStream(Stream: TStream);
begin
WriteByte(Stream, bsTrailer);
end;
procedure TGIFTrailer.LoadFromStream(Stream: TStream);
var
b : BYTE;
begin
if (Stream.Read(b, 1) <> 1) then
exit;
if (b <> bsTrailer) then
Warning(gsWarning, sBadTrailer);
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFExtension registration database
//
////////////////////////////////////////////////////////////////////////////////
type
TExtensionLeadIn = packed record
Introducer: byte; { always $21 }
ExtensionLabel: byte;
end;
PExtRec = ^TExtRec;
TExtRec = record
ExtClass: TGIFExtensionClass;
ExtLabel: BYTE;
end;
TExtensionList = class(TList)
public
constructor Create;
destructor Destroy; override;
procedure Add(eLabel: BYTE; eClass: TGIFExtensionClass);
function FindExt(eLabel: BYTE): TGIFExtensionClass;
procedure Remove(eClass: TGIFExtensionClass);
end;
constructor TExtensionList.Create;
begin
inherited Create;
Add(bsPlainTextExtension, TGIFTextExtension);
Add(bsGraphicControlExtension, TGIFGraphicControlExtension);
Add(bsCommentExtension, TGIFCommentExtension);
Add(bsApplicationExtension, TGIFApplicationExtension);
end;
destructor TExtensionList.Destroy;
var
I: Integer;
begin
for I := 0 to Count-1 do
Dispose(PExtRec(Items[I]));
inherited Destroy;
end;
procedure TExtensionList.Add(eLabel: BYTE; eClass: TGIFExtensionClass);
var
NewRec: PExtRec;
begin
New(NewRec);
with NewRec^ do
begin
ExtLabel := eLabel;
ExtClass := eClass;
end;
inherited Add(NewRec);
end;
function TExtensionList.FindExt(eLabel: BYTE): TGIFExtensionClass;
var
I: Integer;
begin
for I := Count-1 downto 0 do
with PExtRec(Items[I])^ do
if ExtLabel = eLabel then
begin
Result := ExtClass;
Exit;
end;
Result := nil;
end;
procedure TExtensionList.Remove(eClass: TGIFExtensionClass);
var
I: Integer;
P: PExtRec;
begin
for I := Count-1 downto 0 do
begin
P := PExtRec(Items[I]);
if P^.ExtClass.InheritsFrom(eClass) then
begin
Dispose(P);
Delete(I);
end;
end;
end;
var
ExtensionList: TExtensionList = nil;
function GetExtensionList: TExtensionList;
begin
if (ExtensionList = nil) then
ExtensionList := TExtensionList.Create;
Result := ExtensionList;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFExtension
//
////////////////////////////////////////////////////////////////////////////////
function TGIFExtension.GetVersion: TGIFVersion;
begin
Result := gv89a;
end;
class procedure TGIFExtension.RegisterExtension(eLabel: BYTE; eClass: TGIFExtensionClass);
begin
GetExtensionList.Add(eLabel, eClass);
end;
class function TGIFExtension.FindExtension(Stream: TStream): TGIFExtensionClass;
var
eLabel : BYTE;
SubClass : TGIFExtensionClass;
Pos : LongInt;
begin
Pos := Stream.Position;
if (Stream.Read(eLabel, 1) <> 1) then
begin
Result := nil;
exit;
end;
Result := GetExtensionList.FindExt(eLabel);
while (Result <> nil) do
begin
SubClass := Result.FindSubExtension(Stream);
if (SubClass = Result) then
break;
Result := SubClass;
end;
Stream.Position := Pos;
end;
class function TGIFExtension.FindSubExtension(Stream: TStream): TGIFExtensionClass;
begin
Result := self;
end;
constructor TGIFExtension.Create(ASubImage: TGIFSubImage);
begin
inherited Create(ASubImage.Image);
FSubImage := ASubImage;
end;
destructor TGIFExtension.Destroy;
begin
if (FSubImage <> nil) then
FSubImage.Extensions.Remove(self);
inherited Destroy;
end;
procedure TGIFExtension.SaveToStream(Stream: TStream);
var
ExtensionLeadIn : TExtensionLeadIn;
begin
ExtensionLeadIn.Introducer := bsExtensionIntroducer;
ExtensionLeadIn.ExtensionLabel := ExtensionType;
Stream.Write(ExtensionLeadIn, sizeof(ExtensionLeadIn));
end;
function TGIFExtension.DoReadFromStream(Stream: TStream): TGIFExtensionType;
var
ExtensionLeadIn : TExtensionLeadIn;
begin
ReadCheck(Stream, ExtensionLeadIn, sizeof(ExtensionLeadIn));
if (ExtensionLeadIn.Introducer <> bsExtensionIntroducer) then
Error(sBadExtensionLabel);
Result := ExtensionLeadIn.ExtensionLabel;
end;
procedure TGIFExtension.LoadFromStream(Stream: TStream);
begin
// Seek past lead-in
// Stream.Seek(sizeof(TExtensionLeadIn), soFromCurrent);
if (DoReadFromStream(Stream) <> ExtensionType) then
Error(sBadExtensionInstance);
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFGraphicControlExtension
//
////////////////////////////////////////////////////////////////////////////////
const
{ Extension flag bit masks }
efInputFlag = $02; { 00000010 }
efDisposal = $1C; { 00011100 }
efTransparent = $01; { 00000001 }
efReserved = $E0; { 11100000 }
constructor TGIFGraphicControlExtension.Create(ASubImage: TGIFSubImage);
begin
inherited Create(ASubImage);
FGCExtension.BlockSize := 4;
FGCExtension.PackedFields := $00;
FGCExtension.DelayTime := 0;
FGCExtension.TransparentColorIndex := 0;
FGCExtension.Terminator := 0;
if (ASubImage.FGCE = nil) then
ASubImage.FGCE := self;
end;
destructor TGIFGraphicControlExtension.Destroy;
begin
// Clear transparent flag in sub image
if (Transparent) then
SubImage.FTransparent := False;
if (SubImage.FGCE = self) then
SubImage.FGCE := nil;
inherited Destroy;
end;
function TGIFGraphicControlExtension.GetExtensionType: TGIFExtensionType;
begin
Result := bsGraphicControlExtension;
end;
function TGIFGraphicControlExtension.GetTransparent: boolean;
begin
Result := (FGCExtension.PackedFields AND efTransparent) <> 0;
end;
procedure TGIFGraphicControlExtension.SetTransparent(Value: boolean);
begin
// Set transparent flag in sub image
SubImage.FTransparent := Value;
if (Value) then
FGCExtension.PackedFields := FGCExtension.PackedFields OR efTransparent
else
FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efTransparent);
end;
function TGIFGraphicControlExtension.GetTransparentColor: TColor;
begin
Result := SubImage.ActiveColorMap[TransparentColorIndex];
end;
procedure TGIFGraphicControlExtension.SetTransparentColor(Color: TColor);
begin
FGCExtension.TransparentColorIndex := Subimage.ActiveColorMap.AddUnique(Color);
end;
function TGIFGraphicControlExtension.GetTransparentColorIndex: BYTE;
begin
Result := FGCExtension.TransparentColorIndex;
end;
procedure TGIFGraphicControlExtension.SetTransparentColorIndex(Value: BYTE);
begin
if ((Value >= SubImage.ActiveColorMap.Count) and (SubImage.ActiveColorMap.Count > 0)) then
begin
Warning(gsWarning, sBadColorIndex);
Value := 0;
end;
FGCExtension.TransparentColorIndex := Value;
end;
function TGIFGraphicControlExtension.GetDelay: WORD;
begin
Result := FGCExtension.DelayTime;
end;
procedure TGIFGraphicControlExtension.SetDelay(Value: WORD);
begin
FGCExtension.DelayTime := Value;
end;
function TGIFGraphicControlExtension.GetUserInput: boolean;
begin
Result := (FGCExtension.PackedFields AND efInputFlag) <> 0;
end;
procedure TGIFGraphicControlExtension.SetUserInput(Value: boolean);
begin
if (Value) then
FGCExtension.PackedFields := FGCExtension.PackedFields OR efInputFlag
else
FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efInputFlag);
end;
function TGIFGraphicControlExtension.GetDisposal: TDisposalMethod;
begin
Result := TDisposalMethod((FGCExtension.PackedFields AND efDisposal) SHR 2);
end;
procedure TGIFGraphicControlExtension.SetDisposal(Value: TDisposalMethod);
begin
FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efDisposal)
OR ((ord(Value) SHL 2) AND efDisposal);
end;
procedure TGIFGraphicControlExtension.SaveToStream(Stream: TStream);
begin
inherited SaveToStream(Stream);
Stream.Write(FGCExtension, sizeof(FGCExtension));
end;
procedure TGIFGraphicControlExtension.LoadFromStream(Stream: TStream);
begin
inherited LoadFromStream(Stream);
if (Stream.Read(FGCExtension, sizeof(FGCExtension)) <> sizeof(FGCExtension)) then
begin
Warning(gsWarning, sOutOfData);
exit;
end;
// Set transparent flag in sub image
if (Transparent) then
SubImage.FTransparent := True;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFTextExtension
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFTextExtension.Create(ASubImage: TGIFSubImage);
begin
inherited Create(ASubImage);
FText := TStringList.Create;
FPlainTextExtension.BlockSize := 12;
FPlainTextExtension.Left := 0;
FPlainTextExtension.Top := 0;
FPlainTextExtension.Width := 0;
FPlainTextExtension.Height := 0;
FPlainTextExtension.CellWidth := 0;
FPlainTextExtension.CellHeight := 0;
FPlainTextExtension.TextFGColorIndex := 0;
FPlainTextExtension.TextBGColorIndex := 0;
end;
destructor TGIFTextExtension.Destroy;
begin
FText.Free;
inherited Destroy;
end;
function TGIFTextExtension.GetExtensionType: TGIFExtensionType;
begin
Result := bsPlainTextExtension;
end;
function TGIFTextExtension.GetForegroundColor: TColor;
begin
Result := SubImage.ColorMap[ForegroundColorIndex];
end;
procedure TGIFTextExtension.SetForegroundColor(Color: TColor);
begin
ForegroundColorIndex := SubImage.ActiveColorMap.AddUnique(Color);
end;
function TGIFTextExtension.GetBackgroundColor: TColor;
begin
Result := SubImage.ActiveColorMap[BackgroundColorIndex];
end;
procedure TGIFTextExtension.SetBackgroundColor(Color: TColor);
begin
BackgroundColorIndex := SubImage.ColorMap.AddUnique(Color);
end;
function TGIFTextExtension.GetBounds(Index: integer): WORD;
begin
case (Index) of
1: Result := FPlainTextExtension.Left;
2: Result := FPlainTextExtension.Top;
3: Result := FPlainTextExtension.Width;
4: Result := FPlainTextExtension.Height;
else
Result := 0; // To avoid compiler warnings
end;
end;
procedure TGIFTextExtension.SetBounds(Index: integer; Value: WORD);
begin
case (Index) of
1: FPlainTextExtension.Left := Value;
2: FPlainTextExtension.Top := Value;
3: FPlainTextExtension.Width := Value;
4: FPlainTextExtension.Height := Value;
end;
end;
function TGIFTextExtension.GetCharWidthHeight(Index: integer): BYTE;
begin
case (Index) of
1: Result := FPlainTextExtension.CellWidth;
2: Result := FPlainTextExtension.CellHeight;
else
Result := 0; // To avoid compiler warnings
end;
end;
procedure TGIFTextExtension.SetCharWidthHeight(Index: integer; Value: BYTE);
begin
case (Index) of
1: FPlainTextExtension.CellWidth := Value;
2: FPlainTextExtension.CellHeight := Value;
end;
end;
function TGIFTextExtension.GetColorIndex(Index: integer): BYTE;
begin
case (Index) of
1: Result := FPlainTextExtension.TextFGColorIndex;
2: Result := FPlainTextExtension.TextBGColorIndex;
else
Result := 0; // To avoid compiler warnings
end;
end;
procedure TGIFTextExtension.SetColorIndex(Index: integer; Value: BYTE);
begin
case (Index) of
1: FPlainTextExtension.TextFGColorIndex := Value;
2: FPlainTextExtension.TextBGColorIndex := Value;
end;
end;
procedure TGIFTextExtension.SaveToStream(Stream: TStream);
begin
inherited SaveToStream(Stream);
Stream.Write(FPlainTextExtension, sizeof(FPlainTextExtension));
WriteStrings(Stream, FText);
end;
procedure TGIFTextExtension.LoadFromStream(Stream: TStream);
begin
inherited LoadFromStream(Stream);
ReadCheck(Stream, FPlainTextExtension, sizeof(FPlainTextExtension));
ReadStrings(Stream, FText);
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFCommentExtension
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFCommentExtension.Create(ASubImage: TGIFSubImage);
begin
inherited Create(ASubImage);
FText := TStringList.Create;
end;
destructor TGIFCommentExtension.Destroy;
begin
FText.Free;
inherited Destroy;
end;
function TGIFCommentExtension.GetExtensionType: TGIFExtensionType;
begin
Result := bsCommentExtension;
end;
procedure TGIFCommentExtension.SaveToStream(Stream: TStream);
begin
inherited SaveToStream(Stream);
WriteStrings(Stream, FText);
end;
procedure TGIFCommentExtension.LoadFromStream(Stream: TStream);
begin
inherited LoadFromStream(Stream);
ReadStrings(Stream, FText);
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFApplicationExtension registration database
//
////////////////////////////////////////////////////////////////////////////////
type
PAppExtRec = ^TAppExtRec;
TAppExtRec = record
AppClass: TGIFAppExtensionClass;
Ident: TGIFApplicationRec;
end;
TAppExtensionList = class(TList)
public
constructor Create;
destructor Destroy; override;
procedure Add(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass);
function FindExt(eIdent: TGIFApplicationRec): TGIFAppExtensionClass;
procedure Remove(eClass: TGIFAppExtensionClass);
end;
constructor TAppExtensionList.Create;
const
NSLoopIdent: array[0..1] of TGIFApplicationRec =
((Identifier: 'NETSCAPE'; Authentication: '2.0'),
(Identifier: 'ANIMEXTS'; Authentication: '1.0'));
begin
inherited Create;
Add(NSLoopIdent[0], TGIFAppExtNSLoop);
Add(NSLoopIdent[1], TGIFAppExtNSLoop);
end;
destructor TAppExtensionList.Destroy;
var
I: Integer;
begin
for I := 0 to Count-1 do
Dispose(PAppExtRec(Items[I]));
inherited Destroy;
end;
procedure TAppExtensionList.Add(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass);
var
NewRec: PAppExtRec;
begin
New(NewRec);
NewRec^.Ident := eIdent;
NewRec^.AppClass := eClass;
inherited Add(NewRec);
end;
function TAppExtensionList.FindExt(eIdent: TGIFApplicationRec): TGIFAppExtensionClass;
var
I: Integer;
begin
for I := Count-1 downto 0 do
with PAppExtRec(Items[I])^ do
if CompareMem(@Ident, @eIdent, sizeof(TGIFApplicationRec)) then
begin
Result := AppClass;
Exit;
end;
Result := nil;
end;
procedure TAppExtensionList.Remove(eClass: TGIFAppExtensionClass);
var
I: Integer;
P: PAppExtRec;
begin
for I := Count-1 downto 0 do
begin
P := PAppExtRec(Items[I]);
if P^.AppClass.InheritsFrom(eClass) then
begin
Dispose(P);
Delete(I);
end;
end;
end;
var
AppExtensionList: TAppExtensionList = nil;
function GetAppExtensionList: TAppExtensionList;
begin
if (AppExtensionList = nil) then
AppExtensionList := TAppExtensionList.Create;
Result := AppExtensionList;
end;
class procedure TGIFApplicationExtension.RegisterExtension(eIdent: TGIFApplicationRec;
eClass: TGIFAppExtensionClass);
begin
GetAppExtensionList.Add(eIdent, eClass);
end;
class function TGIFApplicationExtension.FindSubExtension(Stream: TStream): TGIFExtensionClass;
var
eIdent : TGIFApplicationRec;
OldPos : longInt;
Size : BYTE;
begin
OldPos := Stream.Position;
Result := nil;
if (Stream.Read(Size, 1) <> 1) then
exit;
// Some old Adobe export filters mistakenly uses a value of 10
if (Size = 10) then
begin
{ TODO -oanme -cImprovement : replace with seek or read and check contents = 'Adobe' }
if (Stream.Read(eIdent, 10) <> 10) then
exit;
Result := TGIFUnknownAppExtension;
exit;
end else
if (Size <> sizeof(TGIFApplicationRec)) or
(Stream.Read(eIdent, sizeof(eIdent)) <> sizeof(eIdent)) then
begin
Stream.Position := OldPos;
Result := inherited FindSubExtension(Stream);
end else
begin
Result := GetAppExtensionList.FindExt(eIdent);
if (Result = nil) then
Result := TGIFUnknownAppExtension;
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFApplicationExtension
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFApplicationExtension.Create(ASubImage: TGIFSubImage);
begin
inherited Create(ASubImage);
FillChar(FIdent, sizeof(FIdent), 0);
end;
destructor TGIFApplicationExtension.Destroy;
begin
inherited Destroy;
end;
function TGIFApplicationExtension.GetExtensionType: TGIFExtensionType;
begin
Result := bsApplicationExtension;
end;
function TGIFApplicationExtension.GetAuthentication: string;
begin
Result := FIdent.Authentication;
end;
procedure TGIFApplicationExtension.SetAuthentication(const Value: string);
begin
if (Length(Value) < sizeof(TGIFAuthenticationCode)) then
FillChar(FIdent.Authentication, sizeof(TGIFAuthenticationCode), 32);
StrLCopy(@(FIdent.Authentication[0]), PChar(Value), sizeof(TGIFAuthenticationCode));
end;
function TGIFApplicationExtension.GetIdentifier: string;
begin
Result := FIdent.Identifier;
end;
procedure TGIFApplicationExtension.SetIdentifier(const Value: string);
begin
if (Length(Value) < sizeof(TGIFIdentifierCode)) then
FillChar(FIdent.Identifier, sizeof(TGIFIdentifierCode), 32);
StrLCopy(@(FIdent.Identifier[0]), PChar(Value), sizeof(TGIFIdentifierCode));
end;
procedure TGIFApplicationExtension.SaveToStream(Stream: TStream);
begin
inherited SaveToStream(Stream);
WriteByte(Stream, sizeof(FIdent)); // Block size
Stream.Write(FIdent, sizeof(FIdent));
SaveData(Stream);
end;
procedure TGIFApplicationExtension.LoadFromStream(Stream: TStream);
var
i : integer;
begin
inherited LoadFromStream(Stream);
i := ReadByte(Stream);
// Some old Adobe export filters mistakenly uses a value of 10
if (i = 10) then
FillChar(FIdent, sizeOf(FIdent), 0)
else
if (i < 11) then
Error(sBadBlockSize);
ReadCheck(Stream, FIdent, sizeof(FIdent));
Dec(i, sizeof(FIdent));
// Ignore extra data
Stream.Seek(i, soFromCurrent);
// ***FIXME***
// If self class is TGIFApplicationExtension, this will cause an "abstract
// error".
// TGIFApplicationExtension.LoadData should read and ignore rest of block.
LoadData(Stream);
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFUnknownAppExtension
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFBlock.Create(ASize: integer);
begin
inherited Create;
FSize := ASize;
GetMem(FData, FSize);
FillChar(FData^, FSize, 0);
end;
destructor TGIFBlock.Destroy;
begin
FreeMem(FData);
inherited Destroy;
end;
procedure TGIFBlock.SaveToStream(Stream: TStream);
begin
Stream.Write(FSize, 1);
Stream.Write(FData^, FSize);
end;
procedure TGIFBlock.LoadFromStream(Stream: TStream);
begin
ReadCheck(Stream, FData^, FSize);
end;
constructor TGIFUnknownAppExtension.Create(ASubImage: TGIFSubImage);
begin
inherited Create(ASubImage);
FBlocks := TList.Create;
end;
destructor TGIFUnknownAppExtension.Destroy;
var
i : integer;
begin
for i := 0 to FBlocks.Count-1 do
TGIFBlock(FBlocks[i]).Free;
FBlocks.Free;
inherited Destroy;
end;
procedure TGIFUnknownAppExtension.SaveData(Stream: TStream);
var
i : integer;
begin
for i := 0 to FBlocks.Count-1 do
TGIFBlock(FBlocks[i]).SaveToStream(Stream);
// Terminating zero
WriteByte(Stream, 0);
end;
procedure TGIFUnknownAppExtension.LoadData(Stream: TStream);
var
b : BYTE;
Block : TGIFBlock;
i : integer;
begin
// Zap old blocks
for i := 0 to FBlocks.Count-1 do
TGIFBlock(FBlocks[i]).Free;
FBlocks.Clear;
// Read blocks
if (Stream.Read(b, 1) <> 1) then
exit;
while (b <> 0) do
begin
Block := TGIFBlock.Create(b);
try
Block.LoadFromStream(Stream);
except
Block.Free;
raise;
end;
FBlocks.Add(Block);
if (Stream.Read(b, 1) <> 1) then
exit;
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFAppExtNSLoop
//
////////////////////////////////////////////////////////////////////////////////
const
// Netscape sub block types
nbLoopExtension = 1;
nbBufferExtension = 2;
constructor TGIFAppExtNSLoop.Create(ASubImage: TGIFSubImage);
const
NSLoopIdent: TGIFApplicationRec = (Identifier: 'NETSCAPE'; Authentication: '2.0');
begin
inherited Create(ASubImage);
FIdent := NSLoopIdent;
end;
procedure TGIFAppExtNSLoop.SaveData(Stream: TStream);
begin
// Write loop count
WriteByte(Stream, 1 + sizeof(FLoops)); // Size of block
WriteByte(Stream, nbLoopExtension); // Identify sub block as looping extension data
Stream.Write(FLoops, sizeof(FLoops)); // Loop count
// Write buffer size if specified
if (FBufferSize > 0) then
begin
WriteByte(Stream, 1 + sizeof(FBufferSize)); // Size of block
WriteByte(Stream, nbBufferExtension); // Identify sub block as buffer size data
Stream.Write(FBufferSize, sizeof(FBufferSize)); // Buffer size
end;
WriteByte(Stream, 0); // Terminating zero
end;
procedure TGIFAppExtNSLoop.LoadData(Stream: TStream);
var
BlockSize : integer;
BlockType : integer;
begin
// Read size of first block or terminating zero
BlockSize := ReadByte(Stream);
while (BlockSize <> 0) do
begin
BlockType := ReadByte(Stream);
dec(BlockSize);
case (BlockType AND $07) of
nbLoopExtension:
begin
if (BlockSize < sizeof(FLoops)) then
Error(sInvalidData);
// Read loop count
ReadCheck(Stream, FLoops, sizeof(FLoops));
dec(BlockSize, sizeof(FLoops));
end;
nbBufferExtension:
begin
if (BlockSize < sizeof(FBufferSize)) then
Error(sInvalidData);
// Read buffer size
ReadCheck(Stream, FBufferSize, sizeof(FBufferSize));
dec(BlockSize, sizeof(FBufferSize));
end;
end;
// Skip/ignore unread data
if (BlockSize > 0) then
Stream.Seek(BlockSize, soFromCurrent);
// Read size of next block or terminating zero
BlockSize := ReadByte(Stream);
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFImageList
//
////////////////////////////////////////////////////////////////////////////////
function TGIFImageList.GetImage(Index: Integer): TGIFSubImage;
begin
Result := TGIFSubImage(Items[Index]);
end;
procedure TGIFImageList.SetImage(Index: Integer; SubImage: TGIFSubImage);
begin
Items[Index] := SubImage;
end;
procedure TGIFImageList.LoadFromStream(Stream: TStream; Parent: TObject);
var
b : BYTE;
SubImage : TGIFSubImage;
begin
// Peek ahead to determine block type
repeat
if (Stream.Read(b, 1) <> 1) then
exit;
until (b <> 0); // Ignore 0 padding (non-compliant)
while (b <> bsTrailer) do
begin
Stream.Seek(-1, soFromCurrent);
if (b in [bsExtensionIntroducer, bsImageDescriptor]) then
begin
SubImage := TGIFSubImage.Create(Parent as TGIFImage);
try
SubImage.LoadFromStream(Stream);
Add(SubImage);
Image.Progress(Self, psRunning, MulDiv(Stream.Position, 100, Stream.Size),
GIFImageRenderOnLoad, Rect(0,0,0,0), sProgressLoading);
except
SubImage.Free;
raise;
end;
end else
begin
Warning(gsWarning, sBadBlock);
break;
end;
repeat
if (Stream.Read(b, 1) <> 1) then
exit;
until (b <> 0); // Ignore 0 padding (non-compliant)
end;
Stream.Seek(-1, soFromCurrent);
end;
procedure TGIFImageList.SaveToStream(Stream: TStream);
var
i : integer;
begin
for i := 0 to Count-1 do
begin
TGIFItem(Items[i]).SaveToStream(Stream);
Image.Progress(Self, psRunning, MulDiv((i+1), 100, Count), False, Rect(0,0,0,0), sProgressSaving);
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFPainter
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFPainter.CreateRef(Painter: PGIFPainter; AImage: TGIFImage;
ACanvas: TCanvas; ARect: TRect; Options: TGIFDrawOptions);
begin
Create(AImage, ACanvas, ARect, Options);
PainterRef := Painter;
if (PainterRef <> nil) then
PainterRef^ := self;
end;
constructor TGIFPainter.Create(AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect;
Options: TGIFDrawOptions);
var
i : integer;
BackgroundColor : TColor;
Disposals : set of TDisposalMethod;
begin
inherited Create(True);
FreeOnTerminate := True;
Onterminate := DoOnTerminate;
FImage := AImage;
FCanvas := ACanvas;
FRect := ARect;
FActiveImage := -1;
FDrawOptions := Options;
FStarted := False;
BackupBuffer := nil;
FrameBuffer := nil;
Background := nil;
FEventHandle := 0;
// This should be a parameter, but I think I've got enough of them already...
FAnimationSpeed := FImage.AnimationSpeed;
// An event handle is used for animation delays
if (FDrawOptions >= [goAnimate, goAsync]) and (FImage.Images.Count > 1) and
(FAnimationSpeed >= 0) then
FEventHandle := CreateEvent(nil, False, False, nil);
// Preprocessing of extensions to determine if we need frame buffers
Disposals := [];
if (FImage.DrawBackgroundColor = clNone) then
begin
if (FImage.GlobalColorMap.Count > 0) then
BackgroundColor := FImage.BackgroundColor
else
BackgroundColor := ColorToRGB(clWindow);
end else
BackgroundColor := ColorToRGB(FImage.DrawBackgroundColor);
// Need background buffer to clear on loop
if (goClearOnLoop in FDrawOptions) then
Include(Disposals, dmBackground);
for i := 0 to FImage.Images.Count-1 do
if (FImage.Images[i].GraphicControlExtension <> nil) then
with (FImage.Images[i].GraphicControlExtension) do
Include(Disposals, Disposal);
// Need background buffer to draw transparent on background
if (dmBackground in Disposals) and (goTransparent in FDrawOptions) then
begin
Background := TBitmap.Create;
Background.Height := FRect.Bottom-FRect.Top;
Background.Width := FRect.Right-FRect.Left;
// Copy background immediately
Background.Canvas.CopyMode := cmSrcCopy;
Background.Canvas.CopyRect(Background.Canvas.ClipRect, FCanvas, FRect);
end;
// Need frame- and backup buffer to restore to previous and background
if ((Disposals * [dmPrevious, dmBackground]) <> []) then
begin
BackupBuffer := TBitmap.Create;
BackupBuffer.Height := FRect.Bottom-FRect.Top;
BackupBuffer.Width := FRect.Right-FRect.Left;
BackupBuffer.Canvas.CopyMode := cmSrcCopy;
BackupBuffer.Canvas.Brush.Color := BackgroundColor;
BackupBuffer.Canvas.Brush.Style := bsSolid;
{$IFDEF DEBUG}
BackupBuffer.Canvas.Brush.Color := clBlack;
BackupBuffer.Canvas.Brush.Style := bsDiagCross;
{$ENDIF}
// Step 1: Copy destination to backup buffer
// Always executed before first frame and only once.
BackupBuffer.Canvas.CopyRect(BackupBuffer.Canvas.ClipRect, FCanvas, FRect);
FrameBuffer := TBitmap.Create;
FrameBuffer.Height := FRect.Bottom-FRect.Top;
FrameBuffer.Width := FRect.Right-FRect.Left;
FrameBuffer.Canvas.CopyMode := cmSrcCopy;
FrameBuffer.Canvas.Brush.Color := BackgroundColor;
FrameBuffer.Canvas.Brush.Style := bsSolid;
{$IFDEF DEBUG}
FrameBuffer.Canvas.Brush.Color := clBlack;
FrameBuffer.Canvas.Brush.Style := bsDiagCross;
{$ENDIF}
end;
end;
destructor TGIFPainter.Destroy;
begin
// OnTerminate isn't called if we are running in main thread, so we must call
// it manually
if not(goAsync in DrawOptions) then
DoOnTerminate(self);
// Reraise any exptions that were eaten in the Execute method
if (ExceptObject <> nil) then
raise ExceptObject at ExceptAddress;
inherited Destroy;
end;
procedure TGIFPainter.SetAnimationSpeed(Value: integer);
begin
if (Value < 0) then
Value := 0
else if (Value > 1000) then
Value := 1000;
if (Value <> FAnimationSpeed) then
begin
FAnimationSpeed := Value;
// Signal WaitForSingleObject delay to abort
if (FEventHandle <> 0) then
SetEvent(FEventHandle)
else
DoRestart := True;
end;
end;
procedure TGIFPainter.SetActiveImage(const Value: integer);
begin
if (Value >= 0) and (Value < FImage.Images.Count) then
FActiveImage := Value;
end;
// Conditional Synchronize
procedure TGIFPainter.DoSynchronize(Method: TThreadMethod);
begin
if (Terminated) then
exit;
if (goAsync in FDrawOptions) then
// Execute Synchronized if requested...
Synchronize(Method)
else
// ...Otherwise just execute in current thread (probably main thread)
Method;
end;
// Delete frame buffers - Executed in main thread
procedure TGIFPainter.DoOnTerminate(Sender: TObject);
begin
// It shouldn't really be nescessary to protect PainterRef in this manner
// since we are running in the main thread at this point, but I'm a little
// paranoid about the way PainterRef is being used...
if Image <> nil then // 2001.02.23
begin // 2001.02.23
with Image.Painters.LockList do
try
// Zap pointer to self and remove from painter list
if (PainterRef <> nil) and (PainterRef^ = self) then
PainterRef^ := nil;
finally
Image.Painters.UnLockList;
end;
Image.Painters.Remove(self);
FImage := nil;
end; // 2001.02.23
// Free buffers
if (BackupBuffer <> nil) then
BackupBuffer.Free;
if (FrameBuffer <> nil) then
FrameBuffer.Free;
if (Background <> nil) then
Background.Free;
// Delete event handle
if (FEventHandle <> 0) then
CloseHandle(FEventHandle);
end;
// Event "dispatcher" - Executed in main thread
procedure TGIFPainter.DoEvent;
begin
if (Assigned(FEvent)) then
FEvent(self);
end;
// Non-buffered paint - Executed in main thread
procedure TGIFPainter.DoPaint;
begin
FImage.Images[ActiveImage].Draw(FCanvas, FRect, (goTransparent in FDrawOptions),
(goTile in FDrawOptions));
FStarted := True;
end;
// Buffered paint - Executed in main thread
procedure TGIFPainter.DoPaintFrame;
var
DrawDestination : TCanvas;
DrawRect : TRect;
DoStep2 ,
DoStep3 ,
DoStep5 ,
DoStep6 : boolean;
SavePal ,
SourcePal : HPALETTE;
procedure ClearBackup;
var
r ,
Tile : TRect;
FrameTop ,
FrameHeight : integer;
ImageWidth ,
ImageHeight : integer;
begin
if (goTransparent in FDrawOptions) then
begin
// If the frame is transparent, we must remove it by copying the
// background buffer over it
if (goTile in FDrawOptions) then
begin
FrameTop := FImage.Images[ActiveImage].Top;
FrameHeight := FImage.Images[ActiveImage].Height;
ImageWidth := FImage.Width;
ImageHeight := FImage.Height;
Tile.Left := FRect.Left + FImage.Images[ActiveImage].Left;
Tile.Right := Tile.Left + FImage.Images[ActiveImage].Width;
while (Tile.Left < FRect.Right) do
begin
Tile.Top := FRect.Top + FrameTop;
Tile.Bottom := Tile.Top + FrameHeight;
while (Tile.Top < FRect.Bottom) do
begin
BackupBuffer.Canvas.CopyRect(Tile, Background.Canvas, Tile);
Tile.Top := Tile.Top + ImageHeight;
Tile.Bottom := Tile.Bottom + ImageHeight;
end;
Tile.Left := Tile.Left + ImageWidth;
Tile.Right := Tile.Right + ImageWidth;
end;
end else
begin
r := FImage.Images[ActiveImage].ScaleRect(BackupBuffer.Canvas.ClipRect);
BackupBuffer.Canvas.CopyRect(r, Background.Canvas, r)
end;
end else
begin
// If the frame isn't transparent, we just clear the area covered by
// it to the background color.
// Tile the background unless the frame covers all of the image
if (goTile in FDrawOptions) and
((FImage.Width <> FImage.Images[ActiveImage].Width) and
(FImage.height <> FImage.Images[ActiveImage].Height)) then
begin
FrameTop := FImage.Images[ActiveImage].Top;
FrameHeight := FImage.Images[ActiveImage].Height;
ImageWidth := FImage.Width;
ImageHeight := FImage.Height;
// ***FIXME*** I don't think this does any difference
BackupBuffer.Canvas.Brush.Color := FImage.DrawBackgroundColor;
Tile.Left := FRect.Left + FImage.Images[ActiveImage].Left;
Tile.Right := Tile.Left + FImage.Images[ActiveImage].Width;
while (Tile.Left < FRect.Right) do
begin
Tile.Top := FRect.Top + FrameTop;
Tile.Bottom := Tile.Top + FrameHeight;
while (Tile.Top < FRect.Bottom) do
begin
BackupBuffer.Canvas.FillRect(Tile);
Tile.Top := Tile.Top + ImageHeight;
Tile.Bottom := Tile.Bottom + ImageHeight;
end;
Tile.Left := Tile.Left + ImageWidth;
Tile.Right := Tile.Right + ImageWidth;
end;
end else
BackupBuffer.Canvas.FillRect(FImage.Images[ActiveImage].ScaleRect(FRect));
end;
end;
begin
if (goValidateCanvas in FDrawOptions) then
if (GetObjectType(ValidateDC) <> OBJ_DC) then
begin
Terminate;
exit;
end;
DrawDestination := nil;
DoStep2 := (goClearOnLoop in FDrawOptions) and (FActiveImage = 0);
DoStep3 := False;
DoStep5 := False;
DoStep6 := False;
{
Disposal mode algorithm:
Step 1: Copy destination to backup buffer
Always executed before first frame and only once.
Done in constructor.
Step 2: Clear previous frame (implementation is same as step 6)
Done implicitly by implementation.
Only done explicitly on first frame if goClearOnLoop option is set.
Step 3: Copy backup buffer to frame buffer
Step 4: Draw frame
Step 5: Copy buffer to destination
Step 6: Clear frame from backup buffer
+------------+------------------+---------------------+------------------------+
|New \ Old | dmNone | dmBackground | dmPrevious |
+------------+------------------+---------------------+------------------------+
|dmNone | | | |
| |4. Paint on backup|4. Paint on backup |4. Paint on backup |
| |5. Restore |5. Restore |5. Restore |
+------------+------------------+---------------------+------------------------+
|dmBackground| | | |
| |4. Paint on backup|4. Paint on backup |4. Paint on backup |
| |5. Restore |5. Restore |5. Restore |
| |6. Clear backup |6. Clear backup |6. Clear backup |
+------------+------------------+---------------------+------------------------+
|dmPrevious | | | |
| | |3. Copy backup to buf|3. Copy backup to buf |
| |4. Paint on dest |4. Paint on buf |4. Paint on buf |
| | |5. Copy buf to dest |5. Copy buf to dest |
+------------+------------------+---------------------+------------------------+
}
case (Disposal) of
dmNone, dmNoDisposal:
begin
DrawDestination := BackupBuffer.Canvas;
DrawRect := BackupBuffer.Canvas.ClipRect;
DoStep5 := True;
end;
dmBackground:
begin
DrawDestination := BackupBuffer.Canvas;
DrawRect := BackupBuffer.Canvas.ClipRect;
DoStep5 := True;
DoStep6 := True;
end;
dmPrevious:
case (OldDisposal) of
dmNone, dmNoDisposal:
begin
DrawDestination := FCanvas;
DrawRect := FRect;
end;
dmBackground, dmPrevious:
begin
DrawDestination := FrameBuffer.Canvas;
DrawRect := FrameBuffer.Canvas.ClipRect;
DoStep3 := True;
DoStep5 := True;
end;
end;
end;
// Find source palette
SourcePal := FImage.Images[ActiveImage].Palette;
if (SourcePal = 0) then
SourcePal := SystemPalette16; // This should never happen
SavePal := SelectPalette(DrawDestination.Handle, SourcePal, False);
RealizePalette(DrawDestination.Handle);
// Step 2: Clear previous frame
if (DoStep2) then
ClearBackup;
// Step 3: Copy backup buffer to frame buffer
if (DoStep3) then
FrameBuffer.Canvas.CopyRect(FrameBuffer.Canvas.ClipRect,
BackupBuffer.Canvas, BackupBuffer.Canvas.ClipRect);
// Step 4: Draw frame
if (DrawDestination <> nil) then
FImage.Images[ActiveImage].Draw(DrawDestination, DrawRect,
(goTransparent in FDrawOptions), (goTile in FDrawOptions));
// Step 5: Copy buffer to destination
if (DoStep5) then
begin
FCanvas.CopyMode := cmSrcCopy;
FCanvas.CopyRect(FRect, DrawDestination, DrawRect);
end;
if (SavePal <> 0) then
SelectPalette(DrawDestination.Handle, SavePal, False);
// Step 6: Clear frame from backup buffer
if (DoStep6) then
ClearBackup;
FStarted := True;
end;
// Prefetch bitmap
// Used to force the GIF image to be rendered as a bitmap
{$ifdef SERIALIZE_RENDER}
procedure TGIFPainter.PrefetchBitmap;
begin
// Touch current bitmap to force bitmap to be rendered
if not((FImage.Images[ActiveImage].Empty) or (FImage.Images[ActiveImage].HasBitmap)) then
FImage.Images[ActiveImage].Bitmap;
end;
{$endif}
// Main thread execution loop - This is where it all happens...
procedure TGIFPainter.Execute;
var
i : integer;
LoopCount ,
LoopPoint : integer;
Looping : boolean;
Ext : TGIFExtension;
Msg : TMsg;
Delay ,
OldDelay ,
DelayUsed : longInt;
DelayStart ,
NewDelayStart : DWORD;
procedure FireEvent(Event: TNotifyEvent);
begin
if not(Assigned(Event)) then
exit;
FEvent := Event;
try
DoSynchronize(DoEvent);
finally
FEvent := nil;
end;
end;
begin
{
Disposal:
dmNone: Same as dmNodisposal
dmNoDisposal: Do not dispose
dmBackground: Clear with background color *)
dmPrevious: Previous image
*) Note: Background color should either be a BROWSER SPECIFIED Background
color (DrawBackgroundColor) or the background image if any frames are
transparent.
}
try
try
if (goValidateCanvas in FDrawOptions) then
ValidateDC := FCanvas.Handle;
DoRestart := True;
// Loop to restart paint
while (DoRestart) and not(Terminated) do
begin
FActiveImage := 0;
// Fire OnStartPaint event
// Note: ActiveImage may be altered by the event handler
FireEvent(FOnStartPaint);
FStarted := False;
DoRestart := False;
LoopCount := 1;
LoopPoint := FActiveImage;
Looping := False;
if (goAsync in DrawOptions) then
Delay := 0
else
Delay := 1; // Dummy to process messages
OldDisposal := dmNoDisposal;
// Fetch delay start time
DelayStart := timeGetTime;
OldDelay := 0;
// Loop to loop - duh!
while ((LoopCount <> 0) or (goLoopContinously in DrawOptions)) and
not(Terminated or DoRestart) do
begin
FActiveImage := LoopPoint;
// Fire OnLoopPaint event
// Note: ActiveImage may be altered by the event handler
if (FStarted) then
FireEvent(FOnLoop);
// Loop to animate
while (ActiveImage < FImage.Images.Count) and not(Terminated or DoRestart) do
begin
// Ignore empty images
if (FImage.Images[ActiveImage].Empty) then
break;
// Delay from previous image
if (Delay > 0) then
begin
// Prefetch frame bitmap
{$ifdef SERIALIZE_RENDER}
DoSynchronize(PrefetchBitmap);
{$else}
FImage.Images[ActiveImage].Bitmap;
{$endif}
// Calculate inter frame delay
NewDelayStart := timeGetTime;
if (FAnimationSpeed > 0) then
begin
// Calculate number of mS used in prefetch and display
try
DelayUsed := integer(NewDelayStart-DelayStart)-OldDelay;
// Prevent feedback oscillations caused by over/undercompensation.
DelayUsed := DelayUsed DIV 2;
// Convert delay value to mS and...
// ...Adjust for time already spent converting GIF to bitmap and...
// ...Adjust for Animation Speed factor.
Delay := MulDiv(Delay * GIFDelayExp - DelayUsed, 100, FAnimationSpeed);
OldDelay := Delay;
except
Delay := GIFMaximumDelay * GIFDelayExp;
OldDelay := 0;
end;
end else
begin
if (goAsync in DrawOptions) then
Delay := longInt(INFINITE)
else
Delay := GIFMaximumDelay * GIFDelayExp;
end;
// Fetch delay start time
DelayStart := NewDelayStart;
// Sleep in one chunk if we are running in a thread
if (goAsync in DrawOptions) then
begin
// Use of WaitForSingleObject allows TGIFPainter.Stop to wake us up
if (Delay > 0) or (FAnimationSpeed = 0) then
begin
if (WaitForSingleObject(FEventHandle, DWORD(Delay)) <> WAIT_TIMEOUT) then
begin
// Don't use interframe delay feedback adjustment if delay
// were prematurely aborted (e.g. because the animation
// speed were changed)
OldDelay := 0;
DelayStart := longInt(timeGetTime);
end;
end;
end else
begin
if (Delay <= 0) then
Delay := 1;
// Fetch start time
NewDelayStart := timeGetTime;
// If we are not running in a thread we Sleep in small chunks
// and give the user a chance to abort
while (Delay > 0) and not(Terminated or DoRestart) do
begin
if (Delay < 100) then
Sleep(Delay)
else
Sleep(100);
// Calculate number of mS delayed in this chunk
DelayUsed := integer(timeGetTime - NewDelayStart);
dec(Delay, DelayUsed);
// Reset start time for chunk
NewDelaySTart := timeGetTime;
// Application.ProcessMessages wannabe
while (not(Terminated or DoRestart)) and
(PeekMessage(Msg, 0, 0, 0, PM_REMOVE)) do
begin
if (Msg.Message <> WM_QUIT) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end else
begin
// Put WM_QUIT back in queue and get out of here fast
PostQuitMessage(Msg.WParam);
Terminate;
end;
end;
end;
end;
end else
Sleep(0); // Yield
if (Terminated) then
break;
// Fire OnPaint event
// Note: ActiveImage may be altered by the event handler
FireEvent(FOnPaint);
if (Terminated) then
break;
// Pre-draw processing of extensions
Disposal := dmNoDisposal;
for i := 0 to FImage.Images[ActiveImage].Extensions.Count-1 do
begin
Ext := FImage.Images[ActiveImage].Extensions[i];
if (Ext is TGIFAppExtNSLoop) then
begin
// Recursive loops not supported (or defined)
if (Looping) then
continue;
Looping := True;
LoopCount := TGIFAppExtNSLoop(Ext).Loops;
if ((LoopCount = 0) or (goLoopContinously in DrawOptions)) and
(goAsync in DrawOptions) then
LoopCount := -1; // Infinite if running in separate thread
{$IFNDEF STRICT_MOZILLA}
// Loop from this image and on
// Note: This is not standard behavior
LoopPoint := ActiveImage;
{$ENDIF}
end else
if (Ext is TGIFGraphicControlExtension) then
Disposal := TGIFGraphicControlExtension(Ext).Disposal;
end;
// Paint the image
if (BackupBuffer <> nil) then
DoSynchronize(DoPaintFrame)
else
DoSynchronize(DoPaint);
OldDisposal := Disposal;
if (Terminated) then
break;
Delay := GIFDefaultDelay; // Default delay
// Post-draw processing of extensions
if (FImage.Images[ActiveImage].GraphicControlExtension <> nil) then
if (FImage.Images[ActiveImage].GraphicControlExtension.Delay > 0) then
begin
Delay := FImage.Images[ActiveImage].GraphicControlExtension.Delay;
// Enforce minimum animation delay in compliance with Mozilla
if (Delay < GIFMinimumDelay) then
Delay := GIFMinimumDelay;
// Do not delay more than 10 seconds if running in main thread
if (Delay > GIFMaximumDelay) and not(goAsync in DrawOptions) then
Delay := GIFMaximumDelay; // Max 10 seconds
end;
// Fire OnAfterPaint event
// Note: ActiveImage may be altered by the event handler
i := FActiveImage;
FireEvent(FOnAfterPaint);
if (Terminated) then
break;
// Don't increment frame counter if event handler modified
// current frame
if (FActiveImage = i) then
Inc(FActiveImage);
// Nothing more to do unless we are animating
if not(goAnimate in DrawOptions) then
break;
end;
if (LoopCount > 0) then
Dec(LoopCount);
if ([goAnimate, goLoop] * DrawOptions <> [goAnimate, goLoop]) then
break;
end;
if (Terminated) then // 2001.07.23
break; // 2001.07.23
end;
FActiveImage := -1;
// Fire OnEndPaint event
FireEvent(FOnEndPaint);
finally
// If we are running in the main thread we will have to zap our self
if not(goAsync in DrawOptions) then
Free;
end;
except
on E: Exception do
begin
// Eat exception and terminate thread...
// If we allow the exception to abort the thread at this point, the
// application will hang since the thread destructor will never be called
// and the application will wait forever for the thread to die!
Terminate;
// Clone exception
ExceptObject := E.Create(E.Message);
ExceptAddress := ExceptAddr;
end;
end;
end;
procedure TGIFPainter.Start;
begin
if (goAsync in FDrawOptions) then
Resume;
end;
procedure TGIFPainter.Stop;
begin
Terminate;
if (goAsync in FDrawOptions) then
begin
// Signal WaitForSingleObject delay to abort
if (FEventHandle <> 0) then
SetEvent(FEventHandle);
Priority := tpNormal;
if (Suspended) then
Resume; // Must be running before we can terminate
end;
end;
procedure TGIFPainter.Restart;
begin
DoRestart := True;
if (Suspended) and (goAsync in FDrawOptions) then
Resume; // Must be running before we can terminate
end;
////////////////////////////////////////////////////////////////////////////////
//
// TColorMapOptimizer
//
////////////////////////////////////////////////////////////////////////////////
// Used by TGIFImage to optimize local color maps to a single global color map.
// The following algorithm is used:
// 1) Build a histogram for each image
// 2) Merge histograms
// 3) Sum equal colors and adjust max # of colors
// 4) Map entries > max to entries <= 256
// 5) Build new color map
// 6) Map images to new color map
////////////////////////////////////////////////////////////////////////////////
type
POptimizeEntry = ^TOptimizeEntry;
TColorRec = record
case byte of
0: (Value: integer);
1: (Color: TGIFColor);
2: (SameAs: POptimizeEntry); // Used if TOptimizeEntry.Count = 0
end;
TOptimizeEntry = record
Count : integer; // Usage count
OldIndex : integer; // Color OldIndex
NewIndex : integer; // NewIndex color OldIndex
Color : TColorRec; // Color value
end;
TOptimizeEntries = array[0..255] of TOptimizeEntry;
POptimizeEntries = ^TOptimizeEntries;
THistogram = class(TObject)
private
PHistogram : POptimizeEntries;
FCount : integer;
FColorMap : TGIFColorMap;
FList : TList;
FImages : TList;
public
constructor Create(AColorMap: TGIFColorMap);
destructor Destroy; override;
function ProcessSubImage(Image: TGIFSubImage): boolean;
function Prune: integer;
procedure MapImages(UseTransparency: boolean; NewTransparentColorIndex: byte);
property Count: integer read FCount;
property ColorMap: TGIFColorMap read FColorMap;
property List: TList read FList;
end;
TColorMapOptimizer = class(TObject)
private
FImage : TGIFImage;
FHistogramList : TList;
FHistogram : TList;
FColorMap : TColorMap;
FFinalCount : integer;
FUseTransparency : boolean;
FNewTransparentColorIndex: byte;
protected
procedure ProcessImage;
procedure MergeColors;
procedure MapColors;
procedure ReplaceColorMaps;
public
constructor Create(AImage: TGIFImage);
destructor Destroy; override;
procedure Optimize;
end;
function CompareColor(Item1, Item2: Pointer): integer;
begin
Result := POptimizeEntry(Item2)^.Color.Value - POptimizeEntry(Item1)^.Color.Value;
end;
function CompareCount(Item1, Item2: Pointer): integer;
begin
Result := POptimizeEntry(Item2)^.Count - POptimizeEntry(Item1)^.Count;
end;
constructor THistogram.Create(AColorMap: TGIFColorMap);
var
i : integer;
begin
inherited Create;
FCount := AColorMap.Count;
FColorMap := AColorMap;
FImages := TList.Create;
// Allocate memory for histogram
GetMem(PHistogram, FCount * sizeof(TOptimizeEntry));
FList := TList.Create;
FList.Capacity := FCount;
// Move data to histogram and initialize
for i := 0 to FCount-1 do
with PHistogram^[i] do
begin
FList.Add(@PHistogram^[i]);
OldIndex := i;
Count := 0;
Color.Value := 0;
Color.Color := AColorMap.Data^[i];
NewIndex := 256; // Used to signal unmapped
end;
end;
destructor THistogram.Destroy;
begin
FImages.Free;
FList.Free;
FreeMem(PHistogram);
inherited Destroy;
end;
//: Build a color histogram
function THistogram.ProcessSubImage(Image: TGIFSubImage): boolean;
var
Size : integer;
Pixel : PChar;
IsTransparent ,
WasTransparent : boolean;
OldTransparentColorIndex: byte;
begin
Result := False;
if (Image.Empty) then
exit;
FImages.Add(Image);
Pixel := Image.data;
Size := Image.Width * Image.Height;
IsTransparent := Image.Transparent;
if (IsTransparent) then
OldTransparentColorIndex := Image.GraphicControlExtension.TransparentColorIndex
else
OldTransparentColorIndex := 0; // To avoid compiler warning
WasTransparent := False;
(*
** Sum up usage count for each color
*)
while (Size > 0) do
begin
// Ignore transparent pixels
if (not IsTransparent) or (ord(Pixel^) <> OldTransparentColorIndex) then
begin
// Check for invalid color index
if (ord(Pixel^) >= FCount) then
begin
Pixel^ := #0; // ***FIXME*** Isn't this an error condition?
Image.Warning(gsWarning, sInvalidColor);
end;
with PHistogram^[ord(Pixel^)] do
begin
// Stop if any color reaches the max count
if (Count = high(integer)) then
break;
inc(Count);
end;
end else
WasTransparent := WasTransparent or IsTransparent;
inc(Pixel);
dec(Size);
end;
(*
** Clear frames transparency flag if the frame claimed to
** be transparent, but wasn't
*)
if (IsTransparent and not WasTransparent) then
begin
Image.GraphicControlExtension.TransparentColorIndex := 0;
Image.GraphicControlExtension.Transparent := False;
end;
Result := WasTransparent;
end;
//: Removed unused color entries from the histogram
function THistogram.Prune: integer;
var
i, j : integer;
begin
(*
** Sort by usage count
*)
FList.Sort(CompareCount);
(*
** Determine number of used colors
*)
for i := 0 to FCount-1 do
// Find first unused color entry
if (POptimizeEntry(FList[i])^.Count = 0) then
begin
// Zap unused colors
for j := i to FCount-1 do
POptimizeEntry(FList[j])^.Count := -1; // Use -1 to signal unused entry
// Remove unused entries
FCount := i;
FList.Count := FCount;
break;
end;
Result := FCount;
end;
//: Convert images from old color map to new color map
procedure THistogram.MapImages(UseTransparency: boolean; NewTransparentColorIndex: byte);
var
i : integer;
Size : integer;
Pixel : PChar;
ReverseMap : array[byte] of byte;
IsTransparent : boolean;
OldTransparentColorIndex: byte;
begin
(*
** Build NewIndex map
*)
for i := 0 to List.Count-1 do
ReverseMap[POptimizeEntry(List[i])^.OldIndex] := POptimizeEntry(List[i])^.NewIndex;
(*
** Reorder all images using this color map
*)
for i := 0 to FImages.Count-1 do
with TGIFSubImage(FImages[i]) do
begin
Pixel := Data;
Size := Width * Height;
// Determine frame transparency
IsTransparent := (Transparent) and (UseTransparency);
if (IsTransparent) then
begin
OldTransparentColorIndex := GraphicControlExtension.TransparentColorIndex;
// Map transparent color
GraphicControlExtension.TransparentColorIndex := NewTransparentColorIndex;
end else
OldTransparentColorIndex := 0; // To avoid compiler warning
// Map all pixels to new color map
while (Size > 0) do
begin
// Map transparent pixels to the new transparent color index and...
if (IsTransparent) and (ord(Pixel^) = OldTransparentColorIndex) then
Pixel^ := char(NewTransparentColorIndex)
else
// ... all other pixels to their new color index
Pixel^ := char(ReverseMap[ord(Pixel^)]);
dec(size);
inc(Pixel);
end;
end;
end;
constructor TColorMapOptimizer.Create(AImage: TGIFImage);
begin
inherited Create;
FImage := AImage;
FHistogramList := TList.Create;
FHistogram := TList.Create;
end;
destructor TColorMapOptimizer.Destroy;
var
i : integer;
begin
FHistogram.Free;
for i := FHistogramList.Count-1 downto 0 do
THistogram(FHistogramList[i]).Free;
FHistogramList.Free;
inherited Destroy;
end;
procedure TColorMapOptimizer.ProcessImage;
var
Hist : THistogram;
i : integer;
ProcessedImage : boolean;
begin
FUseTransparency := False;
(*
** First process images using global color map
*)
if (FImage.GlobalColorMap.Count > 0) then
begin
Hist := THistogram.Create(FImage.GlobalColorMap);
ProcessedImage := False;
// Process all images that are using the global color map
for i := 0 to FImage.Images.Count-1 do
if (FImage.Images[i].ColorMap.Count = 0) and (not FImage.Images[i].Empty) then
begin
ProcessedImage := True;
// Note: Do not change order of statements. Shortcircuit evaluation not desired!
FUseTransparency := Hist.ProcessSubImage(FImage.Images[i]) or FUseTransparency;
end;
// Keep the histogram if any images used the global color map...
if (ProcessedImage) then
FHistogramList.Add(Hist)
else // ... otherwise delete it
Hist.Free;
end;
(*
** Next process images that have a local color map
*)
for i := 0 to FImage.Images.Count-1 do
if (FImage.Images[i].ColorMap.Count > 0) and (not FImage.Images[i].Empty) then
begin
Hist := THistogram.Create(FImage.Images[i].ColorMap);
FHistogramList.Add(Hist);
// Note: Do not change order of statements. Shortcircuit evaluation not desired!
FUseTransparency := Hist.ProcessSubImage(FImage.Images[i]) or FUseTransparency;
end;
end;
procedure TColorMapOptimizer.MergeColors;
var
Entry, SameEntry : POptimizeEntry;
i : integer;
begin
(*
** Sort by color value
*)
FHistogram.Sort(CompareColor);
(*
** Merge same colors
*)
SameEntry := POptimizeEntry(FHistogram[0]);
for i := 1 to FHistogram.Count-1 do
begin
Entry := POptimizeEntry(FHistogram[i]);
ASSERT(Entry^.Count > 0, 'Unused entry exported from THistogram');
if (Entry^.Color.Value = SameEntry^.Color.Value) then
begin
// Transfer usage count to first entry
inc(SameEntry^.Count, Entry^.Count);
Entry^.Count := 0; // Use 0 to signal merged entry
Entry^.Color.SameAs := SameEntry; // Point to master
end else
SameEntry := Entry;
end;
end;
procedure TColorMapOptimizer.MapColors;
var
i, j : integer;
Delta, BestDelta : integer;
BestIndex : integer;
MaxColors : integer;
begin
(*
** Sort by usage count
*)
FHistogram.Sort(CompareCount);
(*
** Handle transparency
*)
if (FUseTransparency) then
MaxColors := 255
else
MaxColors := 256;
(*
** Determine number of colors used (max 256)
*)
FFinalCount := FHistogram.Count;
for i := 0 to FFinalCount-1 do
if (i >= MaxColors) or (POptimizeEntry(FHistogram[i])^.Count = 0) then
begin
FFinalCount := i;
break;
end;
(*
** Build color map and reverse map for final entries
*)
for i := 0 to FFinalCount-1 do
begin
POptimizeEntry(FHistogram[i])^.NewIndex := i;
FColorMap[i] := POptimizeEntry(FHistogram[i])^.Color.Color;
end;
(*
** Map colors > 256 to colors <= 256 and build NewIndex color map
*)
for i := FFinalCount to FHistogram.Count-1 do
with POptimizeEntry(FHistogram[i])^ do
begin
// Entries with a usage count of -1 is unused
ASSERT(Count <> -1, 'Internal error: Unused entry exported');
// Entries with a usage count of 0 has been merged with another entry
if (Count = 0) then
begin
// Use mapping of master entry
ASSERT(Color.SameAs.NewIndex < 256, 'Internal error: Mapping to unmapped color');
NewIndex := Color.SameAs.NewIndex;
end else
begin
// Search for entry with nearest color value
BestIndex := 0;
BestDelta := 255*3;
for j := 0 to FFinalCount-1 do
begin
Delta := ABS((POptimizeEntry(FHistogram[j])^.Color.Color.Red - Color.Color.Red) +
(POptimizeEntry(FHistogram[j])^.Color.Color.Green - Color.Color.Green) +
(POptimizeEntry(FHistogram[j])^.Color.Color.Blue - Color.Color.Blue));
if (Delta < BestDelta) then
begin
BestDelta := Delta;
BestIndex := j;
end;
end;
NewIndex := POptimizeEntry(FHistogram[BestIndex])^.NewIndex;;
end;
end;
(*
** Add transparency color to new color map
*)
if (FUseTransparency) then
begin
FNewTransparentColorIndex := FFinalCount;
FColorMap[FFinalCount].Red := 0;
FColorMap[FFinalCount].Green := 0;
FColorMap[FFinalCount].Blue := 0;
inc(FFinalCount);
end;
end;
procedure TColorMapOptimizer.ReplaceColorMaps;
var
i : integer;
begin
// Zap all local color maps
for i := 0 to FImage.Images.Count-1 do
if (FImage.Images[i].ColorMap <> nil) then
FImage.Images[i].ColorMap.Clear;
// Store optimized global color map
FImage.GlobalColorMap.ImportColorMap(FColorMap, FFinalCount);
FImage.GlobalColorMap.Optimized := True;
end;
procedure TColorMapOptimizer.Optimize;
var
Total : integer;
i, j : integer;
begin
// Stop all painters during optimize...
FImage.PaintStop;
// ...and prevent any new from starting while we are doing our thing
FImage.Painters.LockList;
try
(*
** Process all sub images
*)
ProcessImage;
// Prune histograms and calculate total number of colors
Total := 0;
for i := 0 to FHistogramList.Count-1 do
inc(Total, THistogram(FHistogramList[i]).Prune);
// Allocate global histogram
FHistogram.Clear;
FHistogram.Capacity := Total;
// Move data pointers from local histograms to global histogram
for i := 0 to FHistogramList.Count-1 do
with THistogram(FHistogramList[i]) do
for j := 0 to Count-1 do
begin
ASSERT(POptimizeEntry(List[j])^.Count > 0, 'Unused entry exported from THistogram');
FHistogram.Add(List[j]);
end;
(*
** Merge same colors
*)
MergeColors;
(*
** Build color map and NewIndex map for final entries
*)
MapColors;
(*
** Replace local colormaps with global color map
*)
ReplaceColorMaps;
(*
** Process images for each color map
*)
for i := 0 to FHistogramList.Count-1 do
THistogram(FHistogramList[i]).MapImages(FUseTransparency, FNewTransparentColorIndex);
(*
** Delete the frame's old bitmaps and palettes
*)
for i := 0 to FImage.Images.Count-1 do
begin
FImage.Images[i].HasBitmap := False;
FImage.Images[i].Palette := 0;
end;
finally
FImage.Painters.UnlockList;
end;
end;
////////////////////////////////////////////////////////////////////////////////
//
// TGIFImage
//
////////////////////////////////////////////////////////////////////////////////
constructor TGIFImage.Create;
begin
inherited Create;
FImages := TGIFImageList.Create(self);
FHeader := TGIFHeader.Create(self);
FPainters := TThreadList.Create;
FGlobalPalette := 0;
// Load defaults
FDrawOptions := GIFImageDefaultDrawOptions;
ColorReduction := GIFImageDefaultColorReduction;
FReductionBits := GIFImageDefaultColorReductionBits;
FDitherMode := GIFImageDefaultDitherMode;
FCompression := GIFImageDefaultCompression;
FThreadPriority := GIFImageDefaultThreadPriority;
FAnimationSpeed := GIFImageDefaultAnimationSpeed;
FDrawBackgroundColor := clNone;
IsDrawing := False;
IsInsideGetPalette := False;
NewImage;
end;
destructor TGIFImage.Destroy;
var
i : integer;
begin
PaintStop;
with FPainters.LockList do
try
for i := Count-1 downto 0 do
TGIFPainter(Items[i]).FImage := nil;
finally
FPainters.UnLockList;
end;
Clear;
FPainters.Free;
FImages.Free;
FHeader.Free;
inherited Destroy;
end;
procedure TGIFImage.Clear;
begin
PaintStop;
FreeBitmap;
FImages.Clear;
FHeader.ColorMap.Clear;
FHeader.Height := 0;
FHeader.Width := 0;
FHeader.Prepare;
Palette := 0;
end;
procedure TGIFImage.NewImage;
begin
Clear;
end;
function TGIFImage.GetVersion: TGIFVersion;
var
v : TGIFVersion;
i : integer;
begin
Result := gvUnknown;
for i := 0 to FImages.Count-1 do
begin
v := FImages[i].Version;
if (v > Result) then
Result := v;
if (v >= high(TGIFVersion)) then
break;
end;
end;
function TGIFImage.GetColorResolution: integer;
var
i : integer;
begin
Result := FHeader.ColorResolution;
for i := 0 to FImages.Count-1 do
if (FImages[i].ColorResolution > Result) then
Result := FImages[i].ColorResolution;
end;
function TGIFImage.GetBitsPerPixel: integer;
var
i : integer;
begin
Result := FHeader.BitsPerPixel;
for i := 0 to FImages.Count-1 do
if (FImages[i].BitsPerPixel > Result) then
Result := FImages[i].BitsPerPixel;
end;
function TGIFImage.GetBackgroundColorIndex: BYTE;
begin
Result := FHeader.BackgroundColorIndex;
end;
procedure TGIFImage.SetBackgroundColorIndex(const Value: BYTE);
begin
FHeader.BackgroundColorIndex := Value;
end;
function TGIFImage.GetBackgroundColor: TColor;
begin
Result := FHeader.BackgroundColor;
end;
procedure TGIFImage.SetBackgroundColor(const Value: TColor);
begin
FHeader.BackgroundColor := Value;
end;
function TGIFImage.GetAspectRatio: BYTE;
begin
Result := FHeader.AspectRatio;
end;
procedure TGIFImage.SetAspectRatio(const Value: BYTE);
begin
FHeader.AspectRatio := Value;
end;
procedure TGIFImage.SetDrawOptions(Value: TGIFDrawOptions);
begin
if (FDrawOptions = Value) then
exit;
if (DrawPainter <> nil) then
DrawPainter.Stop;
FDrawOptions := Value;
// Zap all bitmaps
Pack;
Changed(self);
end;
function TGIFImage.GetAnimate: Boolean;
begin // 2002.07.07
Result:= goAnimate in DrawOptions;
end;
procedure TGIFImage.SetAnimate(const Value: Boolean);
begin // 2002.07.07
if Value then
DrawOptions:= DrawOptions + [goAnimate]
else
DrawOptions:= DrawOptions - [goAnimate];
end;
procedure TGIFImage.SetAnimationSpeed(Value: integer);
begin
if (Value < 0) then
Value := 0
else if (Value > 1000) then
Value := 1000;
if (Value <> FAnimationSpeed) then
begin
FAnimationSpeed := Value;
// Use the FPainters threadlist to protect FDrawPainter from being modified
// by the thread while we mess with it
with FPainters.LockList do
try
if (FDrawPainter <> nil) then
FDrawPainter.AnimationSpeed := FAnimationSpeed;
finally
// Release the lock on FPainters to let paint thread kill itself
FPainters.UnLockList;
end;
end;
end;
procedure TGIFImage.SetReductionBits(Value: integer);
begin
if (Value < 3) or (Value > 8) then
Error(sInvalidBitSize);
FReductionBits := Value;
end;
procedure TGIFImage.OptimizeColorMap;
var
ColorMapOptimizer : TColorMapOptimizer;
begin
ColorMapOptimizer := TColorMapOptimizer.Create(self);
try
ColorMapOptimizer.Optimize;
finally
ColorMapOptimizer.Free;
end;
end;
procedure TGIFImage.Optimize(Options: TGIFOptimizeOptions;
ColorReduction: TColorReduction; DitherMode: TDitherMode;
ReductionBits: integer);
var
i ,
j : integer;
Delay : integer;
GCE : TGIFGraphicControlExtension;
ThisRect ,
NextRect ,
MergeRect : TRect;
Prog ,
MaxProg : integer;
function Scan(Buf: PChar; Value: Byte; Count: integer): boolean; assembler;
asm
PUSH EDI
MOV EDI, Buf
MOV ECX, Count
MOV AL, Value
REPNE SCASB
MOV EAX, False
JNE @@1
MOV EAX, True
@@1:POP EDI
end;
begin
if (Empty) then
exit;
// Stop all painters during optimize...
PaintStop;
// ...and prevent any new from starting while we are doing our thing
FPainters.LockList;
try
Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressOptimizing);
try
Prog := 0;
MaxProg := Images.Count*6;
// Sort color map by usage and remove unused entries
if (ooColorMap in Options) then
begin
// Optimize global color map
if (GlobalColorMap.Count > 0) then
GlobalColorMap.Optimize;
// Optimize local color maps
for i := 0 to Images.Count-1 do
begin
inc(Prog);
if (Images[i].ColorMap.Count > 0) then
begin
Images[i].ColorMap.Optimize;
Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
Rect(0,0,0,0), sProgressOptimizing);
end;
end;
end;
// Remove passive elements, pass 1
if (ooCleanup in Options) then
begin
// Check for transparency flag without any transparent pixels
for i := 0 to Images.Count-1 do
begin
inc(Prog);
if (Images[i].Transparent) then
begin
if not(Scan(Images[i].Data,
Images[i].GraphicControlExtension.TransparentColorIndex,
Images[i].DataSize)) then
begin
Images[i].GraphicControlExtension.Transparent := False;
Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
Rect(0,0,0,0), sProgressOptimizing);
end;
end;
end;
// Change redundant disposal modes
for i := 0 to Images.Count-2 do
begin
inc(Prog);
if (Images[i].GraphicControlExtension <> nil) and
(Images[i].GraphicControlExtension.Disposal in [dmPrevious, dmBackground]) and
(not Images[i+1].Transparent) then
begin
ThisRect := Images[i].BoundsRect;
NextRect := Images[i+1].BoundsRect;
if (not IntersectRect(MergeRect, ThisRect, NextRect)) then
continue;
// If the next frame completely covers the current frame,
// change the disposal mode to dmNone
if (EqualRect(MergeRect, NextRect)) then
Images[i].GraphicControlExtension.Disposal := dmNone;
Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
Rect(0,0,0,0), sProgressOptimizing);
end;
end;
end else
inc(Prog, 2*Images.Count);
// Merge layers of equal pixels (remove redundant pixels)
if (ooMerge in Options) then
begin
// Merge from last to first to avoid intefering with merge
for i := Images.Count-1 downto 1 do
begin
inc(Prog);
j := i-1;
// If the "previous" frames uses dmPrevious disposal mode, we must
// instead merge with the frame before the previous
while (j > 0) and
((Images[j].GraphicControlExtension <> nil) and
(Images[j].GraphicControlExtension.Disposal = dmPrevious)) do
dec(j);
// Merge
Images[i].Merge(Images[j]);
Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
Rect(0,0,0,0), sProgressOptimizing);
end;
end else
inc(Prog, Images.Count);
// Crop transparent areas
if (ooCrop in Options) then
begin
for i := Images.Count-1 downto 0 do
begin
inc(Prog);
if (not Images[i].Empty) and (Images[i].Transparent) then
begin
// Remember frames delay in case frame is deleted
Delay := Images[i].GraphicControlExtension.Delay;
// Crop
Images[i].Crop;
// If the frame was completely transparent we remove it
if (Images[i].Empty) then
begin
// Transfer delay to previous frame in case frame was deleted
if (i > 0) and (Images[i-1].Transparent) then
Images[i-1].GraphicControlExtension.Delay :=
Images[i-1].GraphicControlExtension.Delay + Delay;
Images.Delete(i);
end;
Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
Rect(0,0,0,0), sProgressOptimizing);
end;
end;
end else
inc(Prog, Images.Count);
// Remove passive elements, pass 2
inc(Prog, Images.Count);
if (ooCleanup in Options) then
begin
for i := Images.Count-1 downto 0 do
begin
// Remove comments and application extensions
for j := Images[i].Extensions.Count-1 downto 0 do
if (Images[i].Extensions[j] is TGIFCommentExtension) or
(Images[i].Extensions[j] is TGIFTextExtension) or
(Images[i].Extensions[j] is TGIFUnknownAppExtension) or
((Images[i].Extensions[j] is TGIFAppExtNSLoop) and
((i > 0) or (Images.Count = 1))) then
Images[i].Extensions.Delete(j);
if (Images[i].GraphicControlExtension <> nil) then
begin
GCE := Images[i].GraphicControlExtension;
// Zap GCE if all of the following are true:
// * No delay or only one image
// * Not transparent
// * No prompt
// * No disposal or only one image
if ((GCE.Delay = 0) or (Images.Count = 1)) and
(not GCE.Transparent) and
(not GCE.UserInput) and
((GCE.Disposal in [dmNone, dmNoDisposal]) or (Images.Count = 1)) then
begin
GCE.Free;
end;
end;
// Zap frame if it has become empty
if (Images[i].Empty) and (Images[i].Extensions.Count = 0) then
Images[i].Free;
end;
Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
Rect(0,0,0,0), sProgressOptimizing);
end else
// Reduce color depth
if (ooReduceColors in Options) then
begin
if (ColorReduction = rmPalette) then
Error(sInvalidReduction);
{ TODO -oanme -cFeature : Implement ooReduceColors option. }
// Not implemented!
end;
finally
if ExceptObject = nil then
i := 100
else
i := 0;
Progress(Self, psEnding, i, False, Rect(0,0,0,0), sProgressOptimizing);
end;
finally
FPainters.UnlockList;
end;
end;
procedure TGIFImage.Pack;
var
i : integer;
begin
// Zap bitmaps and palettes
FreeBitmap;
Palette := 0;
for i := 0 to FImages.Count-1 do
begin
FImages[i].Bitmap := nil;
FImages[i].Palette := 0;
end;
// Only pack if no global colormap and a single image
if (FHeader.ColorMap.Count > 0) or (FImages.Count <> 1) then
exit;
// Copy local colormap to global
FHeader.ColorMap.Assign(FImages[0].ColorMap);
// Zap local colormap
FImages[0].ColorMap.Clear;
end;
procedure TGIFImage.SaveToStream(Stream: TStream);
var
n : Integer;
begin
Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressSaving);
try
// Write header
FHeader.SaveToStream(Stream);
// Write images
FImages.SaveToStream(Stream);
// Write trailer
with TGIFTrailer.Create(self) do
try
SaveToStream(Stream);
finally
Free;
end;
finally
if ExceptObject = nil then
n := 100
else
n := 0;
Progress(Self, psEnding, n, True, Rect(0,0,0,0), sProgressSaving);
end;
end;
procedure TGIFImage.LoadFromStream(Stream: TStream);
var
n : Integer;
Position : integer;
begin
Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressLoading);
try
// Zap old image
Clear;
Position := Stream.Position;
try
// Read header
FHeader.LoadFromStream(Stream);
// Read images
FImages.LoadFromStream(Stream, self);
// Read trailer
with TGIFTrailer.Create(self) do
try
LoadFromStream(Stream);
finally
Free;
end;
except
// Restore stream position in case of error.
// Not required, but "a nice thing to do"
Stream.Position := Position;
raise;
end;
finally
if ExceptObject = nil then
n := 100
else
n := 0;
Progress(Self, psEnding, n, True, Rect(0,0,0,0), sProgressLoading);
end;
end;
procedure TGIFImage.LoadFromResourceName(Instance: THandle; const ResName: String);
// 2002.07.07
var
Stream: TCustomMemoryStream;
begin
Stream := TResourceStream.Create(Instance, ResName, RT_RCDATA);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
function TGIFImage.GetBitmap: TBitmap;
begin
if not(Empty) then
begin
Result := FBitmap;
if (Result <> nil) then
exit;
FBitmap := TBitmap.Create;
Result := FBitmap;
FBitmap.OnChange := Changed;
// Use first image as default
if (Images.Count > 0) then
begin
if (Images[0].Width = Width) and (Images[0].Height = Height) then
begin
// Use first image as it has same dimensions
FBitmap.Assign(Images[0].Bitmap);
end else
begin
// Draw first image on bitmap
FBitmap.Palette := CopyPalette(Palette);
FBitmap.Height := Height;
FBitmap.Width := Width;
Images[0].Draw(FBitmap.Canvas, FBitmap.Canvas.ClipRect, False, False);
end;
end;
end else
Result := nil
end;
// Create a new (empty) bitmap
function TGIFImage.NewBitmap: TBitmap;
begin
Result := FBitmap;
if (Result <> nil) then
exit;
FBitmap := TBitmap.Create;
Result := FBitmap;
FBitmap.OnChange := Changed;
// Draw first image on bitmap
FBitmap.Palette := CopyPalette(Palette);
FBitmap.Height := Height;
FBitmap.Width := Width;
end;
procedure TGIFImage.FreeBitmap;
begin
if (DrawPainter <> nil) then
DrawPainter.Stop;
if (FBitmap <> nil) then
begin
FBitmap.Free;
FBitmap := nil;
end;
end;
function TGIFImage.Add(Source: TPersistent): integer;
var
Image : TGIFSubImage;
begin
Image := nil; // To avoid compiler warning - not needed.
if (Source is TGraphic) then
begin
Image := TGIFSubImage.Create(self);
try
Image.Assign(Source);
// ***FIXME*** Documentation should explain the inconsistency here:
// TGIFimage does not take ownership of Source after TGIFImage.Add() and
// therefore does not delete Source.
except
Image.Free;
raise;
end;
end else
if (Source is TGIFSubImage) then
Image := TGIFSubImage(Source)
else
Error(sUnsupportedClass);
Result := FImages.Add(Image);
FreeBitmap;
Changed(self);
end;
function TGIFImage.GetEmpty: Boolean;
begin
Result := (FImages.Count = 0);
end;
function TGIFImage.GetHeight: Integer;
begin
Result := FHeader.Height;
end;
function TGIFImage.GetWidth: Integer;
begin
Result := FHeader.Width;
end;
function TGIFImage.GetIsTransparent: Boolean;
var
i : integer;
begin
Result := False;
for i := 0 to Images.Count-1 do
if (Images[i].GraphicControlExtension <> nil) and
(Images[i].GraphicControlExtension.Transparent) then
begin
Result := True;
exit;
end;
end;
function TGIFImage.Equals(Graphic: TGraphic): Boolean;
begin
Result := (Graphic = self);
end;
function TGIFImage.GetPalette: HPALETTE;
begin
// Check for recursion
// (TGIFImage.GetPalette->TGIFSubImage.GetPalette->TGIFImage.GetPalette etc...)
if (IsInsideGetPalette) then
Error(sNoColorTable);
IsInsideGetPalette := True;
try
Result := 0;
if (FBitmap <> nil) and (FBitmap.Palette <> 0) then
// Use bitmaps own palette if possible
Result := FBitmap.Palette
else if (FGlobalPalette <> 0) then
// Or a previously exported global palette
Result := FGlobalPalette
else if (DoDither) then
begin
// or create a new dither palette
FGlobalPalette := WebPalette;
Result := FGlobalPalette;
end else
if (FHeader.ColorMap.Count > 0) then
begin
// or create a new if first time
FGlobalPalette := FHeader.ColorMap.ExportPalette;
Result := FGlobalPalette;
end else
if (FImages.Count > 0) then
// This can cause a recursion if no global palette exist and image[0]
// hasn't got one either. Checked by the IsInsideGetPalette semaphor.
Result := FImages[0].Palette;
finally
IsInsideGetPalette := False;
end;
end;
procedure TGIFImage.SetPalette(Value: HPalette);
var
NeedNewBitmap : boolean;
begin
if (Value <> FGlobalPalette) then
begin
// Zap old palette
if (FGlobalPalette <> 0) then
DeleteObject(FGlobalPalette);
// Zap bitmap unless new palette is same as bitmaps own
NeedNewBitmap := (FBitmap <> nil) and (Value <> FBitmap.Palette);
// Use new palette
FGlobalPalette := Value;
if (NeedNewBitmap) then
begin
// Need to create new bitmap and repaint
FreeBitmap;
PaletteModified := True;
Changed(Self);
end;
end;
end;
// Obsolete
// procedure TGIFImage.Changed(Sender: TObject);
// begin
// inherited Changed(Sender);
// end;
procedure TGIFImage.SetHeight(Value: Integer);
var
i : integer;
begin
for i := 0 to Images.Count-1 do
if (Images[i].Top + Images[i].Height > Value) then
Error(sBadHeight);
if (Value <> Header.Height) then
begin
Header.Height := Value;
FreeBitmap;
Changed(self);
end;
end;
procedure TGIFImage.SetWidth(Value: Integer);
var
i : integer;
begin
for i := 0 to Images.Count-1 do
if (Images[i].Left + Images[i].Width > Value) then
Error(sBadWidth);
if (Value <> Header.Width) then
begin
Header.Width := Value;
FreeBitmap;
Changed(self);
end;
end;
procedure TGIFImage.WriteData(Stream: TStream);
begin
if (GIFImageOptimizeOnStream) then
Optimize([ooCrop, ooMerge, ooCleanup, ooColorMap, ooReduceColors], rmNone, dmNearest, 8);
inherited WriteData(Stream);
end;
procedure TGIFImage.AssignTo(Dest: TPersistent);
begin
if (Dest is TBitmap) then
Dest.Assign(Bitmap)
else
inherited AssignTo(Dest);
end;
{ TODO 1 -oanme -cImprovement : Better handling of TGIFImage.Assign(Empty TBitmap). }
procedure TGIFImage.Assign(Source: TPersistent);
var
i : integer;
Image : TGIFSubImage;
begin
if (Source = self) then
exit;
if (Source = nil) then
begin
Clear;
end else
//
// TGIFImage import
//
if (Source is TGIFImage) then
begin
Clear;
// Temporarily copy event handlers to be able to generate progress events
// during the copy and handle copy errors
OnProgress := TGIFImage(Source).OnProgress;
try
FOnWarning := TGIFImage(Source).OnWarning;
Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressCopying);
try
FHeader.Assign(TGIFImage(Source).Header);
FThreadPriority := TGIFImage(Source).ThreadPriority;
FDrawBackgroundColor := TGIFImage(Source).DrawBackgroundColor;
FDrawOptions := TGIFImage(Source).DrawOptions;
FColorReduction := TGIFImage(Source).ColorReduction;
FDitherMode := TGIFImage(Source).DitherMode;
// 2002.07.07 ->
FOnWarning:= TGIFImage(Source).FOnWarning;
FOnStartPaint:= TGIFImage(Source).FOnStartPaint;
FOnPaint:= TGIFImage(Source).FOnPaint;
FOnEndPaint:= TGIFImage(Source).FOnEndPaint;
FOnAfterPaint:= TGIFImage(Source).FOnAfterPaint;
FOnLoop:= TGIFImage(Source).FOnLoop;
// 2002.07.07 <-
for i := 0 to TGIFImage(Source).Images.Count-1 do
begin
Image := TGIFSubImage.Create(self);
Image.Assign(TGIFImage(Source).Images[i]);
Add(Image);
Progress(Self, psRunning, MulDiv((i+1), 100, TGIFImage(Source).Images.Count),
False, Rect(0,0,0,0), sProgressCopying);
end;
finally
if ExceptObject = nil then
i := 100
else
i := 0;
Progress(Self, psEnding, i, False, Rect(0,0,0,0), sProgressCopying);
end;
finally
// Reset event handlers
FOnWarning := nil;
OnProgress := nil;
end;
end else
//
// Import via TGIFSubImage.Assign
//
begin
Clear;
Image := TGIFSubImage.Create(self);
try
Image.Assign(Source);
Add(Image);
except
on E: EConvertError do
begin
Image.Free;
// Unsupported format - fall back to Source.AssignTo
inherited Assign(Source);
end;
else
// Unknown conversion error
Image.Free;
raise;
end;
end;
end;
procedure TGIFImage.LoadFromClipboardFormat(AFormat: Word; AData: THandle;
APalette: HPALETTE);
{$IFDEF REGISTER_TGIFIMAGE}
var
Size : Longint;
Buffer : Pointer;
Stream : TMemoryStream;
Bmp : TBitmap;
{$ENDIF} // 2002.07.07
begin // 2002.07.07
{$IFDEF REGISTER_TGIFIMAGE} // 2002.07.07
if (AData = 0) then
AData := GetClipboardData(AFormat);
if (AData <> 0) and (AFormat = CF_GIF) then
begin
// Get size and pointer to data
Size := GlobalSize(AData);
Buffer := GlobalLock(AData);
try
Stream := TMemoryStream.Create;
try
// Copy data to a stream
Stream.SetSize(Size);
Move(Buffer^, Stream.Memory^, Size);
// Load GIF from stream
LoadFromStream(Stream);
finally
Stream.Free;
end;
finally
GlobalUnlock(AData);
end;
end else
if (AData <> 0) and (AFormat = CF_BITMAP) then
begin
// No GIF on clipboard - try loading a bitmap instead
Bmp := TBitmap.Create;
try
Bmp.LoadFromClipboardFormat(AFormat, AData, APalette);
Assign(Bmp);
finally
Bmp.Free;
end;
end else
Error(sUnknownClipboardFormat);
{$ELSE} // 2002.07.07
Error(sGIFToClipboard); // 2002.07.07
{$ENDIF} // 2002.07.07
end;
procedure TGIFImage.SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
var APalette: HPALETTE);
{$IFDEF REGISTER_TGIFIMAGE}
var
Stream : TMemoryStream;
Data : THandle;
Buffer : Pointer;
{$ENDIF} // 2002.07.07
begin // 2002.07.07
{$IFDEF REGISTER_TGIFIMAGE} // 2002.07.07
if (Empty) then
exit;
// First store a bitmap version on the clipboard...
Bitmap.SaveToClipboardFormat(AFormat, AData, APalette);
// ...then store a GIF
Stream := TMemoryStream.Create;
try
// Save the GIF to a memory stream
SaveToStream(Stream);
Stream.Position := 0;
// Allocate some memory for the GIF data
Data := GlobalAlloc(HeapAllocFlags, Stream.Size);
try
if (Data <> 0) then
begin
Buffer := GlobalLock(Data);
try
// Copy GIF data from stream memory to clipboard memory
Move(Stream.Memory^, Buffer^, Stream.Size);
finally
GlobalUnlock(Data);
end;
// Transfer data to clipboard
if (SetClipboardData(CF_GIF, Data) = 0) then
Error(sFailedPaste);
end;
except
GlobalFree(Data);
raise;
end;
finally
Stream.Free;
end;
{$ELSE} // 2002.07.07
Error(sGIFToClipboard); // 2002.07.07
{$ENDIF} // 2002.07.07
end;
function TGIFImage.GetColorMap: TGIFColorMap;
begin
Result := FHeader.ColorMap;
end;
function TGIFImage.GetDoDither: boolean;
begin
Result := (goDither in DrawOptions) and
(((goAutoDither in DrawOptions) and DoAutoDither) or
not(goAutoDither in DrawOptions));
end;
{$IFDEF VER9x}
procedure TGIFImage.Progress(Sender: TObject; Stage: TProgressStage;
PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string);
begin
if Assigned(FOnProgress) then
FOnProgress(Sender, Stage, PercentDone, RedrawNow, R, Msg);
end;
{$ENDIF}
procedure TGIFImage.StopDraw;
{$IFNDEF VER14_PLUS} // 2001.07.23
var
Msg : TMsg;
ThreadWindow : HWND;
{$ENDIF} // 2001.07.23
begin
repeat
// Use the FPainters threadlist to protect FDrawPainter from being modified
// by the thread while we mess with it
with FPainters.LockList do
try
if (FDrawPainter = nil) then
break;
// Tell thread to terminate
FDrawPainter.Stop;
// No need to wait for "thread" to terminate if running in main thread
if not(goAsync in FDrawPainter.DrawOptions) then
break;
finally
// Release the lock on FPainters to let paint thread kill itself
FPainters.UnLockList;
end;
{$IFDEF VER14_PLUS}
// 2002.07.07
if (GetCurrentThreadID = MainThreadID) then
while CheckSynchronize do {loop};
{$ELSE}
// Process Messages to make Synchronize work
// (Instead of Application.ProcessMessages)
//{$IFDEF VER14_PLUS} // 2001.07.23
// Break; // 2001.07.23
// Sleep(0); // Yield // 2001.07.23
//{$ELSE} // 2001.07.23
ThreadWindow := FindWindow('TThreadWindow', nil);
while PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) do
begin
if (Msg.Message <> WM_QUIT) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end else
begin
PostQuitMessage(Msg.WParam);
exit;
end;
end;
{$ENDIF} // 2001.07.23
Sleep(0); // Yield
until (False);
FreeBitmap;
end;
procedure TGIFImage.Draw(ACanvas: TCanvas; const Rect: TRect);
var
Canvas : TCanvas;
DestRect : TRect;
{$IFNDEF VER14_PLUS} // 2001.07.23
Msg : TMsg;
ThreadWindow : HWND;
{$ENDIF} // 2001.07.23
procedure DrawTile(Rect: TRect; Bitmap: TBitmap);
var
Tile : TRect;
begin
if (goTile in FDrawOptions) then
begin
// Note: This design does not handle transparency correctly!
Tile.Left := Rect.Left;
Tile.Right := Tile.Left + Width;
while (Tile.Left < Rect.Right) do
begin
Tile.Top := Rect.Top;
Tile.Bottom := Tile.Top + Height;
while (Tile.Top < Rect.Bottom) do
begin
ACanvas.StretchDraw(Tile, Bitmap);
Tile.Top := Tile.Top + Height;
Tile.Bottom := Tile.Top + Height;
end;
Tile.Left := Tile.Left + Width;
Tile.Right := Tile.Left + Width;
end;
end else
ACanvas.StretchDraw(Rect, Bitmap);
end;
begin
// Prevent recursion(s(s(s)))
if (IsDrawing) or (FImages.Count = 0) then
exit;
IsDrawing := True;
try
// Copy bitmap to canvas if we are already drawing
// (or have drawn but are finished)
if (FImages.Count = 1) or // Only one image
(not (goAnimate in FDrawOptions)) then // Don't animate
begin
FImages[0].Draw(ACanvas, Rect, (goTransparent in FDrawOptions),
(goTile in FDrawOptions));
exit;
end else
if (FBitmap <> nil) and not(goDirectDraw in FDrawOptions) then
begin
DrawTile(Rect, Bitmap);
exit;
end;
// Use the FPainters threadlist to protect FDrawPainter from being modified
// by the thread while we mess with it
with FPainters.LockList do
try
// If we are already painting on the canvas in goDirectDraw mode
// and at the same location, just exit and let the painter do
// its thing when it's ready
if (FDrawPainter <> nil) and (FDrawPainter.Canvas = ACanvas) and
EqualRect(FDrawPainter.Rect, Rect) then
exit;
// Kill the current paint thread
StopDraw;
if not(goDirectDraw in FDrawOptions) then
begin
// Create a bitmap to draw on
NewBitmap;
Canvas := FBitmap.Canvas;
DestRect := Canvas.ClipRect;
// Initialize bitmap canvas with background image
Canvas.CopyRect(DestRect, ACanvas, Rect);
end else
begin
Canvas := ACanvas;
DestRect := Rect;
end;
// Create new paint thread
InternalPaint(@FDrawPainter, Canvas, DestRect, FDrawOptions);
if (FDrawPainter <> nil) then
begin
// Launch thread
FDrawPainter.Start;
if not(goDirectDraw in FDrawOptions) then
begin
{$IFDEF VER14_PLUS}
// 2002.07.07
while (FDrawPainter <> nil) and (not FDrawPainter.Terminated) and
(not FDrawPainter.Started) do
begin
if not CheckSynchronize then
Sleep(0); // Yield
end;
{$ELSE}
//{$IFNDEF VER14_PLUS} // 2001.07.23
ThreadWindow := FindWindow('TThreadWindow', nil);
// Wait for thread to render first frame
while (FDrawPainter <> nil) and (not FDrawPainter.Terminated) and
(not FDrawPainter.Started) do
// Process Messages to make Synchronize work
// (Instead of Application.ProcessMessages)
if PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) then
begin
if (Msg.Message <> WM_QUIT) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end else
begin
PostQuitMessage(Msg.WParam);
exit;
end;
end else
Sleep(0); // Yield
{$ENDIF} // 2001.07.23
// Draw frame to destination
DrawTile(Rect, Bitmap);
end;
end;
finally
FPainters.UnLockList;
end;
finally
IsDrawing := False;
end;
end;
// Internal pain(t) routine used by Draw()
function TGIFImage.InternalPaint(Painter: PGifPainter; ACanvas: TCanvas;
const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter;
begin
if (Empty) or (Rect.Left >= Rect.Right) or (Rect.Top >= Rect.Bottom) then
begin
Result := nil;
if (Painter <> nil) then
Painter^ := Result;
exit;
end;
// Draw in main thread if only one image
if (Images.Count = 1) then
Options := Options - [goAsync, goAnimate];
Result := TGIFPainter.CreateRef(Painter, self, ACanvas, Rect, Options);
FPainters.Add(Result);
Result.OnStartPaint := FOnStartPaint;
Result.OnPaint := FOnPaint;
Result.OnAfterPaint := FOnAfterPaint;
Result.OnLoop := FOnLoop;
Result.OnEndPaint := FOnEndPaint;
if not(goAsync in Options) then
begin
// Run in main thread
Result.Execute;
// Note: Painter threads executing in the main thread are freed upon exit
// from the Execute method, so no need to do it here.
Result := nil;
if (Painter <> nil) then
Painter^ := Result;
end else
Result.Priority := FThreadPriority;
end;
function TGIFImage.Paint(ACanvas: TCanvas; const Rect: TRect;
Options: TGIFDrawOptions): TGIFPainter;
begin
Result := InternalPaint(nil, ACanvas, Rect, Options);
if (Result <> nil) then
// Run in separate thread
Result.Start;
end;
procedure TGIFImage.PaintStart;
var
i : integer;
begin
with FPainters.LockList do
try
for i := 0 to Count-1 do
TGIFPainter(Items[i]).Start;
finally
FPainters.UnLockList;
end;
end;
procedure TGIFImage.PaintStop;
var
Ghosts : integer;
i : integer;
{$IFNDEF VER14_PLUS} // 2001.07.23
Msg : TMsg;
ThreadWindow : HWND;
{$ENDIF} // 2001.07.23
{$IFNDEF VER14_PLUS} // 2001.07.23
procedure KillThreads;
var
i : integer;
begin
with FPainters.LockList do
try
for i := Count-1 downto 0 do
if (goAsync in TGIFPainter(Items[i]).DrawOptions) then
begin
TerminateThread(TGIFPainter(Items[i]).Handle, 0);
Delete(i);
end;
finally
FPainters.UnLockList;
end;
end;
{$ENDIF} // 2001.07.23
begin
try
// Loop until all have died
repeat
with FPainters.LockList do
try
if (Count = 0) then
exit;
// Signal painters to terminate
// Painters will attempt to remove them self from the
// painter list when they die
Ghosts := Count;
for i := Ghosts-1 downto 0 do
begin
if not(goAsync in TGIFPainter(Items[i]).DrawOptions) then
dec(Ghosts);
TGIFPainter(Items[i]).Stop;
end;
finally
FPainters.UnLockList;
end;
// If all painters were synchronous, there's no purpose waiting for them
// to terminate, because they are running in the main thread.
if (Ghosts = 0) then
exit;
{$IFDEF VER14_PLUS}
// 2002.07.07
if (GetCurrentThreadID = MainThreadID) then
while CheckSynchronize do {loop};
{$ELSE}
// Process Messages to make TThread.Synchronize work
// (Instead of Application.ProcessMessages)
//{$IFDEF VER14_PLUS} // 2001.07.23
// Exit; // 2001.07.23
//{$ELSE} // 2001.07.23
ThreadWindow := FindWindow('TThreadWindow', nil);
if (ThreadWindow = 0) then
begin
KillThreads;
Exit;
end;
while PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) do
begin
if (Msg.Message <> WM_QUIT) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end else
begin
KillThreads;
Exit;
end;
end;
{$ENDIF} // 2001.07.23
Sleep(0);
until (False);
finally
FreeBitmap;
end;
end;
procedure TGIFImage.PaintPause;
var
i : integer;
begin
with FPainters.LockList do
try
for i := 0 to Count-1 do
TGIFPainter(Items[i]).Suspend;
finally
FPainters.UnLockList;
end;
end;
procedure TGIFImage.PaintResume;
var
i : integer;
begin
// Implementation is currently same as PaintStart, but don't call PaintStart
// in case its implementation changes
with FPainters.LockList do
try
for i := 0 to Count-1 do
TGIFPainter(Items[i]).Start;
finally
FPainters.UnLockList;
end;
end;
procedure TGIFImage.PaintRestart;
var
i : integer;
begin
with FPainters.LockList do
try
for i := 0 to Count-1 do
TGIFPainter(Items[i]).Restart;
finally
FPainters.UnLockList;
end;
end;
procedure TGIFImage.Warning(Sender: TObject; Severity: TGIFSeverity; Message: string);
begin
if (Assigned(FOnWarning)) then
FOnWarning(Sender, Severity, Message);
end;
{$IFDEF VER12_PLUS}
{$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23
type
TDummyThread = class(TThread)
protected
procedure Execute; override;
end;
procedure TDummyThread.Execute;
begin
end;
{$ENDIF} // 2001.07.23
{$ENDIF}
var
DesktopDC: HDC;
{$IFDEF VER12_PLUS}
{$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23
DummyThread: TThread;
{$ENDIF} // 2001.07.23
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
//
// Initialization
//
////////////////////////////////////////////////////////////////////////////////
initialization
{$IFDEF REGISTER_TGIFIMAGE}
TPicture.RegisterFileFormat('GIF', sGIFImageFile, TGIFImage);
CF_GIF := RegisterClipboardFormat(PChar(sGIFImageFile));
TPicture.RegisterClipboardFormat(CF_GIF, TGIFImage);
{$ENDIF}
DesktopDC := GetDC(0);
try
PaletteDevice := (GetDeviceCaps(DesktopDC, BITSPIXEL) * GetDeviceCaps(DesktopDC, PLANES) <= 8);
DoAutoDither := PaletteDevice;
finally
ReleaseDC(0, DesktopDC);
end;
{$IFDEF VER9x}
// Note: This doesn't return the same palette as the Delphi 3 system palette
// since the true system palette contains 20 entries and the Delphi 3 system
// palette only contains 16.
// For our purpose this doesn't matter since we do not care about the actual
// colors (or their number) in the palette.
// Stock objects doesn't have to be deleted.
SystemPalette16 := GetStockObject(DEFAULT_PALETTE);
{$ENDIF}
{$IFDEF VER12_PLUS}
// Make sure that at least one thread always exist.
// This is done to circumvent a race condition bug in Delphi 4.x and later:
// When threads are deleted and created in rapid succesion, a situation might
// arise where the thread window is deleted *after* the threads it controls
// has been created. See the Delphi Bug Lists for more information.
{$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23
DummyThread := TDummyThread.Create(True);
{$ENDIF} // 2001.07.23
{$ENDIF}
////////////////////////////////////////////////////////////////////////////////
//
// Finalization
//
////////////////////////////////////////////////////////////////////////////////
finalization
ExtensionList.Free;
AppExtensionList.Free;
{$IFNDEF VER9x}
{$IFDEF REGISTER_TGIFIMAGE}
TPicture.UnregisterGraphicClass(TGIFImage);
{$ENDIF}
{$IFDEF VER100}
if (pf8BitBitmap <> nil) then
pf8BitBitmap.Free;
{$ENDIF}
{$ENDIF}
{$IFDEF VER12_PLUS}
{$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23
if (DummyThread <> nil) then
DummyThread.Free;
{$ENDIF} // 2001.07.23
{$ENDIF}
end.