📄 ditherunit.pas
字号:
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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -