mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-11 00:42:49 +02:00
1746 lines
49 KiB
ObjectPascal
1746 lines
49 KiB
ObjectPascal
{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 <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}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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.
|
|
|