{Version 7.5} {***************************************************************} {* LiteDith.PAS *} {* *} {* Thanks to Anders Melander, anders@melander.dk, for the *} {* color dithering code in this module. This code was *} {* extracted from his excellent TGifImage.pas unit. *} {* *} {* *} {* Bugs introduced by Dave Baldwin *} {***************************************************************} // Copyright (c) 1997,98 Anders Melander. // // All rights reserved. // // // //////////////////////////////////////////////////////////////////////////////// // // // This software is copyrighted as noted above. It may be freely copied, // // modified, and redistributed, provided that the copyright notice(s) is // // preserved on all copies. // // // // TGIFImage is freeware and I would like it to remain so. This means that it // // may not be bundled with commercial libraries or sold as shareware. You are // // welcome to use it in commercial and shareware applications providing you // // do not charge for the functionality provided by TGIFImage. // // If you are in doubt, please contact me and I will explain this. // // // // There is no warranty or other guarantee of fitness for this software, it // // is provided solely "as is". Bug reports or fixes may be sent to the // // author, who may or may not act on them as he desires. // // // // If you redistribute this code in binary form (i.e. as a library or linked // // into an application), the accompanying documentation should state that // // "this software is based, in part, on the work of Anders Melander" or words // // to that effect. // // // // If you modify this software, you should include a notice in the revision // // history in the history.txt file giving the date and the name of the person // // performing the modification and a brief description of the modification. // // // unit LiteDith; {$i LiteCons.inc} interface {$DEFINE PIXELFORMAT_TOO_SLOW} {$IFDEF HL_LAZARUS} {$DEFINE VER10x} {$DEFINE VER11_PLUS} {$DEFINE D4_BCB3} {$ELSE} //////////////////////////////////////////////////////////////////////////////// // // Determine Delphi and C++ Builder version // //////////////////////////////////////////////////////////////////////////////// // Delphi 2.x {$IFDEF VER90} Error: This module not used with Delphi 2 {$ENDIF} // Delphi 3.x {$IFDEF VER100} {$DEFINE VER10x} {$ENDIF} // C++ Builder 3.x {$IFDEF VER110} {$DEFINE VER10x} {$DEFINE VER11_PLUS} {$DEFINE D4_BCB3} {$ENDIF} // Delphi 4.x {$IFDEF VER120} {$DEFINE VER10x} {$DEFINE VER11_PLUS} {$DEFINE D4_BCB3} {$ENDIF} {$ifdef Ver130} {Delphi 5 and C++Builder 5} {$DEFINE VER10x} {$DEFINE VER11_PLUS} {$DEFINE D4_BCB3} {$ENDIF} {$ifdef ver125} {C++Builder 4} {$DEFINE VER11_PLUS} {$DEFINE D4_BCB3} {$endif} {$ENDIF not HL_LAZARUS} //////////////////////////////////////////////////////////////////////////////// // // External dependecies // //////////////////////////////////////////////////////////////////////////////// uses {$IFDEF HL_LAZARUS} Classes, SysUtils, VCLGlobals, LCLType, GraphType, Graphics; {$ELSE} sysutils, Windows, Graphics, Classes; {$ENDIF} //////////////////////////////////////////////////////////////////////////////// // // Misc constants and support types // //////////////////////////////////////////////////////////////////////////////// type // TGIFImage mostly throws exceptions of type GIFException GIFException = class(EInvalidGraphic); // 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) rmNetscape, // Reduce to the Netscape 216 color palette rmMyPalette, rmQuantizeWindows // Reduce to optimal 256 color windows palette ); TDitherMode = (dmNearest, // Nearest color matching w/o error correction dmFloydSteinberg // Floyd Steinberg Error Diffusion dithering // dmOrdered, // Ordered dither // dmCustom // Custom palette ); //////////////////////////////////////////////////////////////////////////////// // // 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. function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction; DitherMode: TDitherMode): TBitmap; //////////////////////////////////////////////////////////////////////////////// // // Error messages // //////////////////////////////////////////////////////////////////////////////// resourcestring // GIF Error messages sOutOfData = 'Premature end of data'; sOutOfMemDIB = 'Failed to allocate memory for GIF DIB'; sDIBCreate = 'Failed to create DIB from Bitmap'; sNoDIB = 'Image has no DIB'; sInvalidBitmap = 'Bitmap image is not valid'; SInvalidPixelFormat = 'Invalid pixel format'; SScanLine = 'Scan line index out of range'; function GetBitmap(Source: TPersistent): TBitmap; {LDB} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // Implementation // //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// implementation {$IFDEF HL_LAZARUS} function WebPalette: HPalette; begin Result := 0; end; function GetBitmap(Source: TPersistent): TBitmap; {LDB} begin Result:=TBitmap.Create; Result.Assign(Source); end; function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction; DitherMode: TDitherMode): TBitmap; begin Result:=TBitmap.Create; Result.Assign(Bitmap); end; {$ELSE} uses {$ifdef DEBUG} dialogs, {$endif} mmsystem, // timeGetTime() messages, LiteUn2; //////////////////////////////////////////////////////////////////////////////// // // Utilities // //////////////////////////////////////////////////////////////////////////////// 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; (* ** 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; // 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; type TPixelFormats = set of TPixelFormat; // -------------------------- // 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 D4_BCB3} // Disable optimization to circumvent D4/BCB3 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 FillChar(DIB, sizeof(DIB), 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.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; // -------------- // 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; begin Result := Bitmap.PixelFormat; end; // -------------- // 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); begin Bitmap.PixelFormat := PixelFormat; end; // ------------------ // 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 SetPixelFormat // and TBitmap.PixelFormat suffers from. // // Parameters: // Bitmap The bitmap to modify. // PixelFormat The pixel format to convert to. {$IFDEF VER11_PLUS} procedure SafeSetPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat); begin Bitmap.PixelFormat := PixelFormat; end; {$ELSE} var pf8BitBitmap: TBitmap = nil; procedure SafeSetPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat); var Width , Height : integer; begin if (PixelFormat = pf8bit) then begin // Solution to "TBitmap.PixelFormat := pf8bit" leak by Greg Chapman 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} //////////////////////////////////////////////////////////////////////////////// // // TDIB Class // // These classes gives read and write access to TBitmap's pixel data // independantly of the Delphi version used. // //////////////////////////////////////////////////////////////////////////////// type TDIB = class(TObject) private FBitmap : TBitmap; FPixelFormat : TPixelFormat; protected function GetScanline(Row: integer): pointer; virtual; abstract; public constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); virtual; property Scanline[Row: integer]: pointer read GetScanline; property Bitmap: TBitmap read FBitmap; end; TDIBReader = class(TDIB) protected function GetScanline(Row: integer): pointer; override; public constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); override; destructor Destroy; override; end; TDIBWriter = class(TDIB) private {$ifdef PIXELFORMAT_TOO_SLOW} FDIBInfo : PBitmapInfo; FDIBBits : pointer; FDIBInfoSize : integer; FDIBBitsSize : longInt; {$endif} protected procedure CreateDIB; procedure FreeDIB; procedure NeedDIB; function GetScanline(Row: integer): pointer; override; public constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); override; destructor Destroy; override; procedure UpdateBitmap; end; //////////////////////////////////////////////////////////////////////////////// constructor TDIB.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); begin inherited Create; FBitmap := ABitmap; FPixelFormat := APixelFormat; end; //////////////////////////////////////////////////////////////////////////////// constructor TDIBReader.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); begin inherited Create(ABitmap, APixelFormat); SetPixelFormat(FBitmap, FPixelFormat); end; destructor TDIBReader.Destroy; begin inherited Destroy; end; function TDIBReader.GetScanline(Row: integer): pointer; begin Result := FBitmap.ScanLine[Row]; end; //////////////////////////////////////////////////////////////////////////////// constructor TDIBWriter.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); {$ifndef PIXELFORMAT_TOO_SLOW} var SavePalette : HPalette; {$endif} begin inherited Create(ABitmap, APixelFormat); {$ifndef PIXELFORMAT_TOO_SLOW} SavePalette := FBitmap.ReleasePalette; try SafeSetPixelFormat(FBitmap, FPixelFormat); finally FBitmap.Palette := SavePalette; end; {$else} FDIBInfo := nil; FDIBBits := nil; {$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 >= FBitmap.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 , DstColors : WORD; // 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} if (FBitmap.Handle = 0) then Error(sInvalidBitmap); FreeDIB; // Get header- and pixel data size InternalGetDIBSizes(FBitmap.Handle, FDIBInfoSize, FDIBBitsSize, FPixelFormat); // Allocate TBitmapInfo structure GetMem(FDIBInfo, FDIBInfoSize); try // Allocate pixel buffer FDIBBits := GlobalAllocPtr(GMEM_MOVEABLE, FDIBBitsSize); if (FDIBBits = nil) then raise EOutOfMemory.Create(sOutOfMemDIB); // Get pixel data if not(InternalGetDIB(FBitmap.Handle, FBitmap.Palette, FDIBInfo^, FDIBBits^, FPixelFormat)) then Error(sDIBCreate); if (FPixelFormat <= pf8bit) then begin // Find number of colors defined by palette if (FBitmap.Palette = 0) or (GetObject(FBitmap.Palette, sizeof(SrcColors), @SrcColors) = 0) or (SrcColors = 0) then exit; // Determine how many colors there are room for in DIB header DstColors := FDIBInfo^.bmiHeader.biClrUsed; if (DstColors = 0) then DstColors := 1 SHL FDIBInfo^.bmiHeader.biBitCount; // Don't copy any more colors than there are room for if (DstColors <> 0) and (DstColors < SrcColors) then SrcColors := DstColors; // Copy all colors... GetPaletteEntries(FBitmap.Palette, 0, SrcColors, FDIBInfo^.bmiColors[0]); // ...and convert BGR to RGB ByteSwapColors(FDIBInfo^.bmiColors[0], SrcColors); // Finally zero any unused entried if (SrcColors < DstColors) then FillChar(pointer(LongInt(@FDIBInfo^.bmiColors)+SizeOf(TRGBQuad)*SrcColors)^, DstColors - SrcColors, 0); {.$ENDIF} end; except FreeDIB; raise; end; {$endif} end; procedure TDIBWriter.FreeDIB; begin {$ifdef PIXELFORMAT_TOO_SLOW} if (FDIBInfo <> nil) then FreeMem(FDIBInfo); if (FDIBBits <> nil) then GlobalFreePtr(FDIBBits); FDIBInfo := nil; FDIBBits := nil; {$endif} end; procedure TDIBWriter.NeedDIB; begin {$ifdef PIXELFORMAT_TOO_SLOW} if (FDIBBits = nil) then 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} if (FDIBInfo = nil) or (FDIBBits = nil) then exit; 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 DIB into bitmap Stream.Position := 0; FBitmap.LoadFromStream(Stream); finally Stream.Free; end; {$endif} end; //////////////////////////////////////////////////////////////////////////////// // // Color Mapping // //////////////////////////////////////////////////////////////////////////////// type TColorLookup = class(TObject) private FColors : integer; function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; virtual; abstract; public constructor Create(Palette: hPalette); virtual; 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; // 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; 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 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; //////////////////////////////////////////////////////////////////////////////// // // Dithering engine // //////////////////////////////////////////////////////////////////////////////// type TDitherEngine = class 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; 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; TFloydSteinbergEngine = 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; 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); inc(FColumn, FDirection); end; procedure TDitherEngine.NextLine; begin FDirection := -FDirection; if (FDirection = 1) then FColumn := 0 else FColumn := Width-1; end; constructor TFloydSteinbergEngine.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 TFloydSteinbergEngine.Destroy; begin FreeMem(ErrorsR); FreeMem(ErrorsG); FreeMem(ErrorsB); inherited Destroy; end; {$IFOPT R+} {$DEFINE R_PLUS} {$RANGECHECKS OFF} {$ENDIF} function TFloydSteinbergEngine.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; var BelowNextError : TErrorTerm; Delta : TErrorTerm; begin CurrentErrorR := Red + (CurrentErrorR + ErrorR[FDirection] + 8) DIV 16; if (CurrentErrorR < 0) then CurrentErrorR := 0 else if (CurrentErrorR > 255) then CurrentErrorR := 255; CurrentErrorG := Green + (CurrentErrorG + ErrorG[FDirection] + 8) DIV 16; if (CurrentErrorG < 0) then CurrentErrorG := 0 else if (CurrentErrorG > 255) then CurrentErrorG := 255; CurrentErrorB := Blue + (CurrentErrorB + ErrorB[FDirection] + 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; BelowNextError := CurrentErrorR; // Error * 1 Delta := CurrentErrorR * 2; CurrentErrorR := CurrentErrorR + Delta; ErrorR[0] := BelowPrevErrorR + CurrentErrorR; // Error * 3 CurrentErrorR := CurrentErrorR + Delta; BelowPrevErrorR := BelowErrorR + CurrentErrorR; // Error * 5 BelowErrorR := BelowNextError; // Error * 1 CurrentErrorR := CurrentErrorR + Delta; // Error * 7 // Green component CurrentErrorG := CurrentErrorG - G; BelowNextError := CurrentErrorG; // Error * 1 Delta := CurrentErrorG * 2; CurrentErrorG := CurrentErrorG + Delta; ErrorG[0] := BelowPrevErrorG + CurrentErrorG; // Error * 3 CurrentErrorG := CurrentErrorG + Delta; BelowPrevErrorG := BelowErrorG + CurrentErrorG; // Error * 5 BelowErrorG := BelowNextError; // Error * 1 CurrentErrorG := CurrentErrorG + Delta; // Error * 7 // Blue component CurrentErrorB := CurrentErrorB - B; BelowNextError := CurrentErrorB; // Error * 1 Delta := CurrentErrorB * 2; CurrentErrorB := CurrentErrorB + Delta; ErrorB[0] := BelowPrevErrorB + CurrentErrorB; // Error * 3 CurrentErrorB := CurrentErrorB + Delta; BelowPrevErrorB := BelowErrorB + CurrentErrorB; // Error * 5 BelowErrorB := BelowNextError; // Error * 1 CurrentErrorB := CurrentErrorB + Delta; // Error * 7 // Move on to next column if (FDirection = 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 TFloydSteinbergEngine.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 (FDirection = 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} //////////////////////////////////////////////////////////////////////////////// // // 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 doCreateOptimizedPaletteForSingleBitmap(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 CreateOptimizedPaletteForSingleBitmap(const Bitmap: TBitmap; Colors, ColorBits: integer; Windows: boolean): hPalette; var DIB : TDIBReader; begin DIB := TDIBReader.Create(Bitmap, pf24bit); try Result := doCreateOptimizedPaletteForSingleBitmap(DIB, Colors, ColorBits, Windows); finally DIB.Free; end; end; //////////////////////////////////////////////////////////////////////////////// // // Color reduction // //////////////////////////////////////////////////////////////////////////////// {$IFOPT R+} {$DEFINE R_PLUS} {$RANGECHECKS OFF} {$ENDIF} function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction; DitherMode: TDitherMode): 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} begin {$ifdef DEBUG_DITHERPERFORMANCE} timeBeginPeriod(5); TimeStart := timeGetTime; {$endif} Result := TBitmap.Create; try if (ColorReduction = rmNone) then begin Result.Assign(Bitmap); SetPixelFormat(Result, pf24bit); exit; end; // Set bitmap width and height Result.Width := Bitmap.Width; Result.Height := Bitmap.Height; // Set the bitmap pixel format SafeSetPixelFormat(Result, pf8bit); Result.Palette := 0; 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); try // Create a palette based on current options case (ColorReduction) of rmQuantizeWindows: Palette := CreateOptimizedPaletteForSingleBitmap(Bitmap, 256, 8, True); rmNetscape: Palette := WebPalette; rmMyPalette: Palette := CopyPalette(ThePalette); rmWindows20: Palette := GetStockObject(DEFAULT_PALETTE); else exit; end; Result.Palette := Palette; case (ColorReduction) of // For some strange reason my fast and dirty color lookup // is more precise that Windows GetNearestPaletteIndex... rmNetscape: ColorLookup := TNetscapeColorLookup.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 := TFloydSteinbergEngine.Create(Bitmap.Width, ColorLookup); else exit; end; // The processed bitmap is returned in pf8bit format DIBResult := TDIBWriter.Create(Result, pf8bit); // 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; except Result.ReleasePalette; if (Palette <> 0) then DeleteObject(Palette); raise; 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; 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} function GetBitmap(Source: TPersistent): TBitmap; var PixelFormat : TPixelFormat; FBitmap: TBitmap; ColorReduction: TColorReduction; DitherMode: TDitherMode; begin Result := Nil; if (Source is TBitmap) then {should always be} begin if (TBitmap(Source).Empty) then exit; PixelFormat := GetPixelFormat(TBitmap(Source)); if (PixelFormat > pfDevice) then begin if ColorBits >= 8 then ColorReduction := rmMyPalette else ColorReduction := rmWindows20; DitherMode := dmFloydSteinberg; // Convert image to 8 bits/pixel or less FBitmap := ReduceColors(TBitmap(Source), ColorReduction, DitherMode); end else begin // Create new bitmap and copy FBitmap := TBitmap.Create; FBitmap.Assign(TBitmap(Source)); end; Result := FBitmap; end; end; {$ENDIF not HL_LAZARUS} end.