mirror of
https://github.com/dashr9230/SA-MP.git
synced 2024-12-22 14:37:29 +08:00
12561 lines
353 KiB
ObjectPascal
12561 lines
353 KiB
ObjectPascal
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.
|
|
|