天天看點

Delphi圖像處理 -- 真彩色圖像轉換為低色彩圖像

    特點:

    1、真彩色圖像轉換為單色、16色、256色及16位(555和565)彩色圖像;

    2、可選的抖動仿色功能,使圖像轉換品質得到了很大提高;

    3、轉換256色索引圖像時,使用16位映射表比對調色闆(可選),使轉換速度得到了大幅度提高;

    4、采用八叉樹節點擷取索引圖像調色闆時,使用了圖像像素樣本進行計算(可選),不僅提高了處理速度,而且也可使圖像中的主色調(俗稱流行色)更有機會進入調色闆;

    5、對索引圖像,可輸入任意的外部調色闆進行比對處理;

    6、32位圖像轉索引圖像時,可設定背景色,對原Alpha通道進行背景色填充。注意:在使用映射表比對調色闆時,顔色有可能發生微小變換,此時可取消映射表比對調色闆功能,好在此類圖檔一般不是很大,對處理速度影響不是很大。

    下面是真彩色圖像轉低色彩圖像類的全部代碼:

unit IndexImage;

(*****************************************************************************

* *

* TIndexImage: 真彩色圖像轉換為低色彩圖像。 *

* *

* TIndexBitmap: TBitmap真彩色圖像轉換為低色彩圖像。 *

* *

* TGpIndexBitmap: TGpBitmap真彩色圖像轉換為低色彩圖像。 *

* *

* 編 制 人: 湖北省公安縣統計局 毛澤發 2008.10 *

* *

*****************************************************************************)

interface

uses

Windows, SysUtils, Graphics, ImageData;

const

MinSamplings = 1024;

MaxSamplings = $7fffffff;

type

TIndexFormat = (if1Bit, if4Bit, if8Bit, if15Bit, if16Bit);

PIndexColorArray = ^TIndexColorArray;

TIndexColorArray = array[Byte] of TRGBQuad;

PErrQuad = ^TErrQuad;

TErrQuad = packed record

Blue: SmallInt;

Green: SmallInt;

Red: SmallInt;

Alpha: SmallInt;

end;

TSetDitherPixel = procedure(var Err: TErrQuad; PixelAddr: Pointer; x: Integer) of Object;

PErrQuadArray = ^TErrQuadArray;

TErrQuadArray = array[0..(MaxLongInt div 12) - 1] of TErrQuad;

TIndexImage = class;

TColorNode = class

private

FIsLeaf: Boolean;

FPixelCount: LongWord;

FRedSum: LongWord;

FGreenSum: LongWord;

FBlueSum: LongWord;

FChild: array[0..7] of TColorNode;

FNext: TColorNode;

FData: TIndexImage;

public

constructor Create(Level: Integer; Tree: TIndexImage);

destructor Destroy; override;

procedure AddColor(PColor: PRGBQuad; Level: Integer);

procedure GetPaletteColors(var Index: Integer);

end;

TIndexImage = class

private

FColorBits: Integer;

FMaxColors: Integer;

FLeafCount: Integer;

FMinSamplingCount: Integer;

FMonochromeThreshold: LongWord;

FIndexFormat: TIndexFormat;

FDithering: Boolean;

FUseColorMap: Boolean;

FSource: TImageData;

FColorMap: PByteArray;

FColors: PIndexColorArray;

FNodes: array[0..8] of TColorNode;

procedure CreateColorMap;

procedure CreateIndexData1(var IndexData: TImageData);

procedure CreateIndexData4(var IndexData: TImageData);

procedure CreateIndexData8(var IndexData: TImageData);

procedure CreateIndexData8_2(var IndexData: TImageData);

procedure CreateIndexData15(var IndexData: TImageData);

procedure CreateIndexData16(var IndexData: TImageData);

procedure CreateSamplingData(Dest: TImageData);

function GetColorCount: Integer;

function GetColors: PIndexColorArray;

function GetBitmap: HBitmap;

function GetIndexColor(PColor: PRGBQuad): Integer;

function GetPalette: HPalette;

procedure SetBitmap(const Value: HBitmap);

procedure SetDitherPixel1(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

procedure SetDitherPixel4(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

procedure SetDitherPixel8(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

procedure SetDitherPixel8_2(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

procedure SetDitherPixel15(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

procedure SetDitherPixel16(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

procedure SetFormat(const Value: TIndexFormat);

procedure SetMinSamplingCount(const Value: Integer);

procedure SetMonochromeThreshold(const Value: LongWord);

procedure SetPalette(const Value: HPalette);

procedure SetSourceData(const Value: TImageData);

protected

function CanIndex: Boolean;

procedure CreateColors;

procedure CreateDitherData(var IndexData: TImageData; SetPixelProc: TSetDitherPixel);

procedure CreateIndexImageData(var IndexData: TImageData);

procedure FillBackground(ColorBackground: LongWord);

procedure ColorsChange; virtual;

procedure ReduceTree;

procedure SetColorCount(Count: Integer);

property ColorBits: Integer read FColorBits;

property MaxColorCount: Integer read FMaxColors;

// 擷取或設定資料源結構,注意:設定資料源是淺拷貝

property SourceData: TImageData read FSource write SetSourceData;

public

constructor Create;

destructor Destroy; override;

// 設定顔色表(可按給定顔色表比對索引位圖)。AColors=[]清除顔色表。

procedure SetColors(const AColors: array of TRGBQuad);

// 擷取按IndexFormat比對的位圖句柄,或者設定位圖源句柄。不清除調色闆顔色表

property Bitmap: HBitmap read GetBitmap write SetBitmap;

// 擷取調色闆顔色的個數

property ColorCount: Integer read GetColorCount;

// 擷取調色闆顔色表

property Colors: PIndexColorArray read GetColors;

// 轉換圖像時是否抖動仿色,預設為False

property Dithering: Boolean read FDithering write FDithering;

// 擷取或設定索引圖格式。設定格式将清除可能存在的調色闆顔色表

property IndexFormat: TIndexFormat read FIndexFormat write SetFormat;

// 轉換單色圖像時的閥值。預設為128

property MonochromeThreshold: LongWord read FMonochromeThreshold write SetMonochromeThreshold;

// 建立調色闆的像素最小樣本數(不得小于MinSamplings),預設為4096。

// 如果像素個數/100>=最小樣本數,取圖像尺寸1/10為樣本,否則取>=4096的圖像尺寸

property MinSamplingCount: Integer read FMinSamplingCount write SetMinSamplingCount;

// 擷取或設定調色闆(可按給定調色闆比對索引位圖),Palette=0清除顔色表

property Palette: HPalette read GetPalette write SetPalette;

// True: 使用16位顔色映射表比對調色闆轉換8bit圖像(4bit圖像轉換無映射表)

// Flae: 直接比對調色闆,但比對速度較慢。預設為True。

property UseColorMap: Boolean read FUseColorMap write FUseColorMap;

end;

TIndexBitmap = class(TIndexImage)

public

// 擷取按IndexFormat比對的TBitmap對象

function GetIndexBitmap: TBitmap;

// 設定源對象。ColorBackground為32位圖像Alpha背景色,0不填充。不清除調色闆顔色表

procedure SetSource(Bitmap: TBitmap; ColorBackground: TColor = 0); overload;

procedure SetSource(Graphic: TGraphic; ColorBackground: TColor = 0); overload;

end;

TGpIndexBitmap = class(TIndexImage)

private

FPalette: PColorPalette;

function GetPalette: PColorPalette;

procedure SetPalette(const Value: PColorPalette);

protected

procedure ColorsChange; override;

public

// 擷取按IndexFormat比對的TGpBitmap對象

function GetIndexBitmap: TGpBitmap;

// 設定源對象。ColorBackground為32位圖像Alpha背景色,0不填充。不清除調色闆顔色表

procedure SetSource(Bitmap: TGpBitmap; ColorBackground: TARGB = 0);

// 擷取或設定GDI+調色闆(可按給定調色闆比對索引位圖),ColorPalette=nil清除顔色表

property ColorPalette: PColorPalette read GetPalette write SetPalette;

end;

implementation

{ TColorNode }

procedure TColorNode.AddColor(PColor: PRGBQuad; Level: Integer);

const

mask: array[0..7] of Byte = ($80, $40, $20, $10, $08, $04, $02, $01);

var

Index, shift: Integer;

begin

if FIsLeaf then

begin

Inc(FPixelCount);

Inc(FRedSum, PColor^.rgbRed);

Inc(FGreenSum, PColor^.rgbGreen);

Inc(FBlueSum, PColor^.rgbBlue);

end

else

begin

shift := 7 - Level;

Index := (((PColor.rgbRed and mask[Level]) shr shift) shl 2) or

(((PColor.rgbGreen and mask[Level]) shr shift) shl 1) or

((PColor.rgbBlue and mask[Level]) shr shift);

Inc(Level);

if not Assigned(FChild[Index]) then

FChild[Index] := TColorNode.Create(Level, FData);

FChild[Index].AddColor(PColor, Level);

end;

end;

constructor TColorNode.Create(Level: Integer; Tree: TIndexImage);

begin

FData := Tree;

FIsLeaf := Level = FData.FColorBits;

if FIsLeaf then

Inc(FData.FLeafCount)

else

begin

FNext := FData.FNodes[Level];

FData.FNodes[Level] := Self;

end;

end;

destructor TColorNode.Destroy;

var

I: Integer;

begin

for I := 0 to 7 do

if Assigned(FChild[I]) then

FChild[I].Free;

end;

procedure TColorNode.GetPaletteColors(var Index: Integer);

var

I: Integer;

begin

if FIsLeaf then

begin

FData.FColors[Index].rgbRed := FRedSum div FPixelCount;

FData.FColors[Index].rgbGreen := FGreenSum div FPixelCount;

FData.FColors[Index].rgbBlue := FBlueSum div FPixelCount;

FData.FColors[Index].rgbReserved := 0;

Inc(Index);

end

else

begin

for I := 0 to 7 do

if Assigned(FChild[I]) then

FChild[I].GetPaletteColors(Index);

end;

end;

{ TIndexImage }

function TIndexImage.CanIndex: Boolean;

begin

Result := FSource.Scan0 <> nil;

end;

procedure TIndexImage.ColorsChange;

begin

end;

constructor TIndexImage.Create;

begin

IndexFormat := if8bit;

FMinSamplingCount := 4096;

FMonochromeThreshold := 128;

FUseColorMap := True;

end;

// 建立調色闆顔色表

procedure TIndexImage.CreateColors;

var

Node: TColorNode;

x, y, Index: Integer;

Offset: Integer;

P: PRGBQuad;

Scale: Single;

tmp: TImageData;

begin

if not CanIndex or (FLeafCount > 0) or (IndexFormat > if8Bit) then Exit;

if IndexFormat = if1Bit then

begin

SetColorCount(2);

LongWord(FColors[0]) := 0;

LongWord(FColors[1]) := $FFFFFF;

Exit;

end;

Node := TColorNode.Create(0, Self);

try

if (FSource.Width * FSource.Height div 100) >= FMinSamplingCount then

Scale := 0.1

else

begin

Scale := 0.9;

while (Trunc(FSource.Width * Scale * FSource.Height * Scale)) > FMinSamplingCount do

Scale := Scale - 0.1;

Scale := Scale + 0.1;

end;

if Scale < 1.0 then // 如果Source像素數量>MinSamplingCount,取樣本資料

begin

tmp := GetImageData(Round(Scale * FSource.Width), Round(Scale * FSource.Height));

CreateSamplingData(tmp);

end

else // 否則取原圖像資料

tmp := GetImageData(FSource.Width, FSource.Height, FSource.Stride, FSource.Scan0);

try

P := tmp.Scan0; // 建立顔色八叉樹

Offset := tmp.Stride - (tmp.Width shl 2);

for y := 1 to tmp.Height do

begin

for x := 1 to tmp.Width do

begin

Node.AddColor(P, 0);

while FLeafCount > FMaxColors do

ReduceTree;

Inc(P);

end;

Inc(Integer(P), Offset);

end;

finally

FreeImageData(tmp);

end;

SetColorCount(FLeafCount);

Index := 0;

Node.GetPaletteColors(Index); // 擷取調色闆顔色表

finally

Node.Free;

end;

end;

// 根據調色闆顔色表建立8位索引圖像的16位顔色映射表

procedure TIndexImage.CreateColorMap;

var

r, g, b: Integer;

Color: TRGBQuad;

begin

r := 32 * 32 * 32;

GetMem(FColorMap, r);

while r > 0 do

begin

Dec(r, 32 * 32);

Color.rgbRed := r shr 7;

g := 32 * 32;

while g > 0 do

begin

Dec(g, 32);

Color.rgbGreen := g shr 2;

b := 32;

while b > 0 do

begin

Dec(b);

Color.rgbBlue := b shl 3;

FColorMap[r or g or b] := GetIndexColor(@Color);

end;

end;

end;

end;

procedure TIndexImage.CreateDitherData(var IndexData: TImageData;

SetPixelProc: TSetDitherPixel);

var

PErrs, Errs0, Errs1: PErrQuadArray;

Width: Integer;

srcOffset, dstStride: Integer;

this, dstScan0: Pointer;

procedure FillErrs;

asm

push edi

push ecx

mov edi, Errs1

mov ecx, Width

add ecx, ecx

xor eax, eax

rep stosd

pop ecx

pop edi

end;

asm

push esi

push edi

push ebx

mov this, eax

push edx

mov eax, [edx].TImageData.Stride

mov dstStride, eax

mov edx, [edx].TImageData.Width

add edx, 2

shl edx, 3 + 1 // edx = (Dest.Width + 2) * Sizeof(TErrQuad) * 2

push edx

mov eax, GMEM_MOVEABLE

call GlobalAllocPtr

mov PErrs, eax // PErrs = GlobalAllocPtr(GMEM_MOVEABLE, edx)

add eax, 8

mov Errs0, eax // Errs0 = @PErrs[1]

pop edx

shr edx, 1

add eax, edx

mov Errs1, eax // Errs1 = @PErrs[Dest.Width + 2 + 1]

pop edx

mov eax, this

lea eax, [eax].TIndexImage.FSource

call SetCopyRegs

mov srcOffset, ebx

mov dstScan0, esi

mov esi, edi // esi = Source.Scan0

mov Width, ecx

pxor mm7, mm7

mov ebx, 70007h

movd mm6, ebx

punpcklwd mm6, mm6

mov ebx, 50005h

movd mm5, ebx

punpcklwd mm5, mm5

mov ebx, 30003h

movd mm4, ebx

punpcklwd mm4, mm4

call FillErrs // FillErrs(Errs1, Dest.Width, 0)

@yLoop: // for (y = 0; y < Height; y ++)

mov ebx, Errs0 // {

mov edi, Errs1

mov ecx, Width

@readLoop: // for (x = 0; x < Width; x ++)

movd mm0, [esi] // {

punpcklbw mm0, mm7 // Errs0[x] = Errs1[x] + Source.Pixels[x, y]

paddw mm0, [edi] // if (Errs0[x] < 0)

packuswb mm0, mm7 // Errs[x] = 0

punpcklbw mm0, mm7 // else if (Errs0[x] > 255)

movq [ebx], mm0 // Errs0[x] = 255

add esi, 4 //

add edi, 8

add ebx, 8

loop @readLoop // }

call FillErrs // FillErrs(Errs1, Dest.Width, 0)

xor ecx, ecx

push edx

mov ebx, Errs0

mov edi, Errs1

@xLoop: // for (x = 0; x < Width; x ++)

movq mm1, [ebx] // {

push ecx // mm1 = Errs0[x]

push ecx

mov edx, ebx

mov ecx, dstScan0

mov eax, this // SetPixelProc(Errs0[x], Dest.Scan0, x)

call dword ptr SetPixelProc

pop ecx // mm1 = currErr = SetPixelProc return value

add ebx, 8

movq mm0, mm1 // Right:

pmullw mm0, mm6 // Errs0[x+1] += (currErr * 7/16)

psraw mm0, 4 // if (Errs0[x] < 0) Errs0[x] = 0

paddsw mm0, [ebx] // if (Errs0[x] > 255) Errs0[x] = 255

packuswb mm0, mm7

punpcklbw mm0, mm7

movq [ebx], mm0

movq mm0, mm1 // Bottom:

pmullw mm0, mm5 // Errs1[x] += (currErr * 5/16)

psraw mm0, 4 // if (Errs1[x] < 0) Errs1[x] = 0

paddsw mm0, [edi] // if (Errs1[x] > 255) Errs1[x] = 255

packuswb mm0, mm7

punpcklbw mm0, mm7

movq [edi], mm0

sub edi, 8

movq mm0, mm1 // Left Bottom:

pmullw mm0, mm4 // Errs1[x-1] += (currErr * 3/16)

psraw mm0, 4 // if (Errs1[x-1] < 0) Errs1[x-1] = 0

paddsw mm0, [edi] // if (Errs1[x-1] > 255) Errs1[x-1] = 255

packuswb mm0, mm7

punpcklbw mm0, mm7

movq [edi], mm0

add edi, 16 // Right Bottom:

psraw mm1, 4 // Errs1[x+1] += (currErr * 1/16)

paddsw mm1, [edi] // if (Errs1[x+1] < 0) Errs1[x+1] = 0

packuswb mm1, mm7 // if (Errs1[x+1] > 255) Errs1[x+1] = 255

punpcklbw mm1, mm7

movq [edi], mm1

inc ecx

cmp ecx, Width

jl @xLoop // }

pop edx

add esi, srcOffset // esi += srcOffset

mov eax, dstStride

add dstScan0, eax // dstScan0 += Dest.Stride

dec edx

jnz @yLoop // }

emms

mov eax, PErrs

call GlobalFreePtr // GlobalFreePtr(PErrs)

pop ebx

pop edi

pop esi

end;

procedure TIndexImage.CreateIndexData1(var IndexData: TImageData);

var

dstOffset, srcOffset, Height: Integer;

procedure CopyPixel;

asm

mov eax, 8000h

@PixelLoop:

cmp [esi], bl

jb @@1

or al, ah

@@1:

shr ah, 1

add esi, 4

loop @PixelLoop

stosb

end;

asm

push esi

push edi

push ebx

push [eax].TIndexImage.FMonochromeThreshold

lea eax, [eax].TIndexImage.FSource

xchg eax, edx

call SetCopyRegs

mov dstOffset, ebx

mov srcOffset, eax

mov Height, edx

mov edx, 7

and edx, ecx

shr ecx, 3

pop ebx

@yLoop:

push ecx

jecxz @@1

@xLoop:

push ecx

mov ecx, 8

call CopyPixel

pop ecx

loop @xLoop

@@1:

mov ecx, edx

jecxz @@2

call CopyPixel

@@2:

pop ecx

add esi, srcOffset

add edi, dstOffset

dec Height

jnz @yLoop

pop ebx

pop edi

pop esi

end;

procedure TIndexImage.CreateIndexData4(var IndexData: TImageData);

var

srcOffset, dstOffset, Width, Height: Integer;

asm

push esi

push edi

push ebx

push eax

lea eax, [eax].TIndexImage.FSource

xchg eax, edx

call SetCopyRegs

mov srcOffset, eax

mov dstOffset, ebx

mov Height, edx

mov Width, ecx

shr ecx, 1

pop ebx

@yLoop:

push ecx

jecxz @@1

@xLoop:

push ecx

mov edx, esi

mov eax, ebx

call GetIndexColor

shl eax, 4

add esi, 4

push eax

mov edx, esi

mov eax, ebx

call GetIndexColor

pop ecx

or eax, ecx

stosb

add esi, 4

pop ecx

loop @xLoop

@@1:

test Width, 1

jz @@2

mov edx, esi

mov eax, ebx

call GetIndexColor

shl eax, 4

stosb

add esi, 4

@@2:

pop ecx

add esi, srcOffset

add edi, dstOffset

dec Height

jnz @yLoop

pop ebx

pop edi

pop esi

end;

procedure TIndexImage.CreateIndexData8(var IndexData: TImageData);

var

srcOffset, dstOffset, Height: Integer;

asm

push esi

push edi

push ebx

push eax

lea eax, [eax].TIndexImage.FSource

xchg eax, edx

call SetCopyRegs

mov srcOffset, eax

mov dstOffset, ebx

mov Height, edx

pop eax

mov ebx, [eax].TIndexImage.FColorMap

@yLoop:

push ecx

@xLoop:

lodsd

mov edx, eax

shr dh, 3

shr dx, 3

shr eax, 9

and eax, 7c00h

or ax, dx

mov al, [ebx + eax]

stosb

loop @xLoop

pop ecx

add esi, srcOffset

add edi, dstOffset

dec Height

jnz @yLoop

pop ebx

pop edi

pop esi

end;

procedure TIndexImage.CreateIndexData8_2(var IndexData: TImageData);

var

srcOffset, dstOffset, Height: Integer;

asm

push esi

push edi

push ebx

push eax

lea eax, [eax].TIndexImage.FSource

xchg eax, edx

call SetCopyRegs

mov srcOffset, eax

mov dstOffset, ebx

mov Height, edx

pop ebx

@yLoop:

push ecx

@xLoop:

push ecx

mov edx, esi

mov eax, ebx

call GetIndexColor

stosb

pop ecx

add esi, 4

loop @xLoop

pop ecx

add esi, srcOffset

add edi, dstOffset

dec Height

jnz @yLoop

pop ebx

pop edi

pop esi

end;

procedure TIndexImage.CreateIndexData15(var IndexData: TImageData);

var

dstOffset, srcoffset: Integer;

asm

push esi

push edi

push ebx

lea eax, [eax].TIndexImage.FSource

xchg eax, edx

call SetCopyRegs

mov dstOffset, ebx

mov srcOffset, eax

@yLoop:

push ecx

@xLoop:

lodsd

mov ebx, eax

shr bh, 3

shr bx, 3

shr eax, 9

and ax, 7c00h

or ax, bx

stosw

loop @xLoop

pop ecx

add esi, srcOffset

add edi, dstOffset

dec edx

jnz @yLoop

pop ebx

pop edi

pop esi

end;

procedure TIndexImage.CreateIndexData16(var IndexData: TImageData);

var

dstOffset, srcoffset: Integer;

asm

push esi

push edi

push ebx

lea eax, [eax].TIndexImage.FSource

xchg eax, edx

call SetCopyRegs

mov dstOffset, ebx

mov srcOffset, eax

@yLoop:

push ecx

@xLoop:

lodsd

mov ebx, eax

shr bh, 2

shr bx, 3

shr eax, 8

and ax, 0f800h

or ax, bx

stosw

loop @xLoop

pop ecx

add esi, srcOffset

add edi, dstOffset

dec edx

jnz @yLoop

pop ebx

pop edi

pop esi

end;

// 擷取索引位圖

procedure TIndexImage.CreateIndexImageData(var IndexData: TImageData);

procedure GrayCopy(Dest, Source: TImageData);

var

srcOffset: Integer;

asm

push esi

push edi

push ebx

call SetCopyRegs

mov srcOffset, eax

@yLoop:

push ecx

@xLoop:

movzx eax, [esi]

movzx ebx, [esi + 1]

imul eax, 117 // blue 0.114 * 1024

imul ebx, 601 // green 0.587 * 1024

add eax, ebx

movzx ebx, [esi + 2]

imul ebx, 306 // red 0.299 * 1024

add eax, ebx

add eax, 512 // Rounding

shr eax, 10 // eax = Round((R * 306 + G * 601 + B * 117) / 1024)

mov [edi], al // blue = al

add edi, 4

add esi, 4

loop @xLoop

pop ecx

add esi, srcOffset

dec edx

jnz @yLoop

pop ebx

pop edi

pop esi

end;

var

saveData: TImageData;

begin

if not CanIndex then // 資料源不存在,錯誤

raise EImageDataError.Create(EIDErrorEmptyData);

CreateColors; // 建立顔色表

case IndexFormat of

if1Bit: // 單色

begin

saveData := FSource; // 儲存源圖像資料

FSource := GetImageData(saveData.Width, saveData.Height);

try

GrayCopy(FSource, saveData);// 拷貝灰階圖像

if FDithering then

CreateDitherData(IndexData, SetDitherPixel1)

else

CreateIndexData1(IndexData);

finally

FreeImageData(FSource);

FSource := saveData; // 恢複源圖像資料

end;

end;

if4Bit: // 16色

if FDithering then

CreateDitherData(IndexData, SetDitherPixel4)

else

CreateIndexData4(IndexData);

if8Bit: // 256色

begin

if UseColorMap then // 使用映射表比對調色闆

begin

CreateColorMap;

if FDithering then

CreateDitherData(IndexData, SetDitherPixel8)

else

CreateIndexData8(IndexData);

FreeMem(FColorMap);

end

else // 直接比對調色闆

begin

if FDithering then

CreateDitherData(IndexData, SetDitherPixel8_2)

else

CreateIndexData8_2(IndexData);

end;

end;

if15Bit: // 15位(555)

if FDithering then

CreateDitherData(IndexData, SetDitherPixel15)

else

CreateIndexData15(IndexData);

if16Bit:

if FDithering then // 16位(565)

CreateDitherData(IndexData, SetDitherPixel16)

else

CreateIndexData16(IndexData);

end;

end;

// 擷取樣本圖像資料

procedure TIndexImage.CreateSamplingData(Dest: TImageData);

var

xDelta, yDelta: Integer;

asm

push esi

push edi

push ebx

mov edi, edx

lea ebx, [eax].TIndexImage.FSource

mov eax, [ebx].TimageData.Height

shl eax, 8

cdq

div [edi].TimageData.Height

mov yDelta, eax // yDelta = Source.Height * 256 / Dest.Height

mov ecx, [edi].TimageData.Height

dec ecx

imul ecx, eax // y = (Dest.Height - 1) * yDelta

mov eax, [ebx].TimageData.Width

shl eax, 8

cdq

div [edi].TimageData.Width

mov xDelta, eax // xDelta = Source.Width * 256 / Dest.Width

mov edx, [edi].TimageData.Width

dec edx

imul edx, eax // x = (Dest.Width - 1) * xDelta

mov edi, [edi].TImageData.Scan0 // edi = Dest.Scan0

@yLoop: // for (; y >= 0; y -= yDelta)

mov esi, ecx // {

shr esi, 8 // esi = Source.Scan0 + y / 256 * Source.Stride

imul esi, [ebx].TImageData.Stride

add esi, [ebx].TImageData.Scan0

push edx

@xLoop:

push esi // for (; x >= 0; x -= xDelta)

mov eax, edx // {

shr eax, 8

shl eax, 2

add esi, eax // esi += (x / 256 * 4)

movsd // *edi ++ = *esi

pop esi

sub edx, xDelta

jns @xLoop // }

pop edx

sub ecx, yDelta

jns @yLoop // }

pop ebx

pop edi

pop esi

end;

destructor TIndexImage.Destroy;

begin

SetColorCount(0);

FreeImageData(FSource);

end;

// 填充Alpha通道背景顔色

procedure TIndexImage.FillBackground(ColorBackground: LongWord);

asm

push edi

push ebx

or edx, 0ff000000h

pxor mm7, mm7 // mm7 = 00 00 00 00 00 00 00 00

movd mm3, edx // mm3 = 00 00 00 00 Ad Rd Gd Bd

punpcklbw mm3, mm7 // mm3 = 00 Ad 00 Rd 00 Gd 00 Bd

movq mm1, mm3

psllw mm1, 8 // mm1 = Ad*256 Rd*256 Gd*256 Bd*256

lea edi, [eax].TIndexImage.FSource

mov ecx, [edi].TImageData.Width

mov edx, [edi].TImageData.Height

mov ebx, [edi].TImageData.Stride

mov edi, [edi].TImageData.Scan0

mov eax, ecx

shl eax, 2

sub ebx, eax

cld

@yLoop:

push ecx

@xLoop:

movd mm0, [edi] // mm0 = 00 00 00 00 As Rs Gs Bs

punpcklbw mm0, mm7 // mm0 = 00 As 00 Rs 00 Gs 00 Bs

movq mm2, mm0

punpckhwd mm2, mm2

punpckhdq mm2, mm2 // mm2 = Alpha Alpha Alpha Alpha

psubw mm0, mm3 // mm0 = As-Ad Rs-Rd Gs-Gd Bs-Bd

pmullw mm0, mm2 // mm0 = As*Alpha Rs*Alpha Gs*Alpha Bs*Alpha

paddw mm0, mm1 // mm0 = 00 An 00 Rn 00 Gn 00 Bn

psrlw mm0, 8 // mm0 = An/256 Rn/256 Gn/256 Bn/256

packuswb mm0, mm7 // mm0 = 00 00 00 00 An Rn Gn Bn

movd [edi], mm0

add edi, 4

loop @xLoop

pop ecx

add edi, ebx

dec edx

jnz @yLoop

emms

pop ebx

pop edi

end;

// 擷取Windows位圖句柄

function TIndexImage.GetBitmap: HBitmap;

const

BitFields: array[0..2] of LongWord = ($F800, $07E0, $001F);

var

pbi: PBitmapInfo;

Data: TImageData;

DC: HDC;

begin

Result := 0;

if not CanIndex then Exit;

Data := GetImageData(FSource.Width, FSource.Height, TPixelFormat(Integer(IndexFormat) + 1));

Data.InvertLine := True;

DC := GetDC(0);

try

CreateIndexImageData(Data);

GetMem(pbi, Sizeof(TBitmapInfoHeader) + 12 + Sizeof(TRGBQuad) * FLeafCount);

try

pbi^.bmiHeader := GetBitmapInfoHeader(Data);

if Data.PixelFormat and $ffff = $1000 then

begin

pbi^.bmiHeader.biCompression := BI_BITFIELDS;

Move(BitFields, pbi^.bmiColors, Sizeof(TRGBQuad) * 3);

end

else

Move(FColors^, pbi^.bmiColors, Sizeof(TRGBQuad) * FLeafCount);

Result := CreateDIBitmap(DC, pbi^.bmiHeader, CBM_INIT,

Data.Scan0, pbi^, DIB_RGB_COLORS);

finally

FreeMem(pbi);

end;

finally

ReleaseDC(0, DC);

FreeImageData(Data);

end;

end;

function TIndexImage.GetColorCount: Integer;

begin

if FLeafCount = 0 then

CreateColors;

Result := FLeafCount;

end;

function TIndexImage.GetColors: PIndexColorArray;

begin

CreateColors;

Result := FColors;

end;

// 按顔色表平方差比對像素顔色

function TIndexImage.GetIndexColor(PColor: PRGBQuad): Integer;

var

Count, Index, Diff: LongWord;

asm

push esi

push edi

push ebx

mov ecx, [eax].TIndexImage.FLeafCount

mov Count, ecx

mov esi, [eax].TIndexImage.FColors

movzx ebx, [edx]

movzx ecx, [edx + 1]

movzx edi, [edx + 2]

mov Diff, 7FFFFFFFh

mov Index, esi

push esi

@Loop:

movzx eax, [esi]

movzx edx, [esi + 1]

sub eax, ebx

sub edx, ecx

imul eax, eax

imul edx, edx

add eax, edx

movzx edx, [esi + 2]

sub edx, edi

imul edx, edx

add eax, edx

jnz @@4

mov Index, esi

jmp @@6

@@4:

cmp eax, Diff

jae @@5

mov Diff, eax

mov Index, esi

@@5:

add esi, 4

dec Count

jnz @Loop

@@6:

pop esi

mov eax, Index

sub eax, esi

shr eax, 2

pop ebx

pop edi

pop esi

end;

// 擷取Windows位圖調色闆句柄

function TIndexImage.GetPalette: HPalette;

var

Pal: TMaxLogPalette;

begin

Result := 0;

CreateColors;

if FLeafCount = 0 then Exit;

Move(FColors^, Pal.palPalEntry, Sizeof(TRGBQuad) * FLeafCount);

SwapColors(@Pal.palPalEntry, FLeafCount);

Pal.palVersion := $300;

Pal.palNumEntries := FLeafCount;

Result := CreatePalette(PLogPalette(@Pal)^);

end;

// 歸并八叉樹

procedure TIndexImage.ReduceTree;

var

I: Integer;

Node: TColorNode;

begin

I := FColorBits - 1;

while FNodes[I] = nil do Dec(I);

Node := FNodes[I];

FNodes[I] := Node.FNext;

for I := 0 to 7 do

begin

if Node.FChild[I] <> nil then

begin

Inc(Node.FRedSum, Node.FChild[I].FRedSum);

Inc(Node.FGreenSum, Node.FChild[I].FGreenSum);

Inc(Node.FBlueSum, Node.FChild[I].FBlueSum);

Inc(Node.FPixelCount, Node.FChild[I].FPixelCount);

FreeAndNil(Node.FChild[I]);

Dec(FLeafCount);

end;

end;

Inc(FLeafCount);

Node.FIsLeaf := True;

end;

// 通過Windows位圖調色闆句柄擷取源圖像資料結構

procedure TIndexImage.SetBitmap(const Value: HBitmap);

var

DC: HDC;

bmi: TBitmapInfo;

begin

FreeImageData(FSource);

if Value = 0 then Exit;

DC := GetDC(0);

try

FSource.PixelFormat := 0;

bmi.bmiHeader := GetBitmapInfoHeader(FSource);

if GetDIBits(DC, Value, 0, 1, nil, bmi, DIB_RGB_COLORS) = 0 then

raise EImageDataError.Create(EIDErrorParam);

FSource := GetImageData(bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight, pf32bit);

FSource.InvertLine := True;

bmi.bmiHeader := GetBitmapInfoHeader(FSource);

GetDIBits(DC, Value, 0, FSource.Height, FSource.Scan0, bmi, DIB_RGB_COLORS);

finally

ReleaseDC(0, DC);

end;

end;

procedure TIndexImage.SetColorCount(Count: Integer);

begin

if FColors <> nil then

begin

FreeMem(FColors);

FColors := nil;

end;

FLeafCount := Count;

if FLeafCount > 0 then

GetMem(FColors, Sizeof(TRGBQuad) * FLeafCount);

ColorsChange;

end;

// 設定外部顔色表

procedure TIndexImage.SetColors(const AColors: array of TRGBQuad);

begin

if IndexFormat > if8Bit then Exit;

SetColorCount(Length(AColors));

if FLeafCount = 0 then Exit;

Move(AColors, FColors^, Sizeof(TRGBQuad) * FLeafCount);

end;

// in mm1 = Errs[x]; out mm1 = currErr

procedure TIndexImage.SetDitherPixel1(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

const

mask1: int64 = $00ff00ff00ff;

asm

push edi

mov edi, x

shr edi, 3 // edi = PixelAddr + x / 8

add edi, ecx

mov eax, [eax].TIndexImage.FMonochromeThreshold

test x, 7

jnz @@1

mov [edi], ah // if (x & 7 == 0) *edi = 0 (init)

@@1:

cmp ax, [edx].TErrQuad.Blue

jae @@2

mov eax, 80h // if (Err > Threshold)

mov ecx, 7 // {

and ecx, x // *edi |= (0x80 >> (x & 7))

shr eax, cl // currErr = Errs[x] - 255

or [edi], al // }

psubsw mm1, mask1

@@2:

pop edi

end;

// in mm1 = Errs[x]; out mm1 = currErr

procedure TIndexImage.SetDitherPixel4(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

asm

push [eax].TIndexImage.FColors

push ecx

movq mm0, mm1

packuswb mm0, mm7

movq [edx], mm0

call GetIndexColor // index = GetIndexColor(@Errs[x])

pop ecx

mov edx, x

shr edx, 1

add edx, ecx // edx = PixelAddr + x / 2

test x, 1

jnz @@1

shl eax, 4

mov [edx], al // if (x & 1 == 0) *edx = index << 4

shr eax, 4

jmp @@2

@@1:

or [edx], al // else *edx |= index

@@2:

pop ecx

shl eax, 2

add ecx, eax

movd mm0, [ecx]

punpcklbw mm0, mm7

psubsw mm1, mm0 // currErr = Errs[x] - FColors[index]

end;

// in mm1 = Errs[x]; out mm1 = currErr

procedure TIndexImage.SetDitherPixel8(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

asm

add ecx, x

push ecx

mov ecx, [edx]

mov dx, [edx].TErrQuad.Red

and edx, 0f8h

and ecx, 0f800f8h

shl edx, 7

shr cx, 3

or dx, cx

shr ecx, 14

or edx, ecx

add edx, [eax].TIndexImage.FColorMap

movzx edx, [edx] // index = FColorMap[Errs[x] to 16Bit] (555)

pop ecx // ecx = PixelAddr + x

mov [ecx], dl // *ecx = index

shl edx, 2

add edx, [eax].TIndexImage.FColors

movd mm0, [edx]

punpcklbw mm0, mm7

psubsw mm1, mm0 // currErr = Errs[x] - FColors[index]

end;

// in mm1 = Errs[x]; out mm1 = currErr

procedure TIndexImage.SetDitherPixel8_2(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

asm

push [eax].TIndexImage.FColors

push ecx

movq mm0, mm1

packuswb mm0, mm7

movq [edx], mm0

call GetIndexColor // index = GetIndexColor(@Errs[x])

pop ecx

add ecx, x

mov [ecx], al // *ecx = index

pop edx

shl eax, 2

movd mm0, [edx + eax]

punpcklbw mm0, mm7

psubsw mm1, mm0 // currErr = Errs[x] - FColors[index]

end;

// in mm1 = Errs[x]; out mm1 = currErr

procedure TIndexImage.SetDitherPixel15(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

const

mask16: int64 = $000700070007;

asm

pand mm1, mask16 // currErr = Errs[x] & 0x0700030007

mov eax, [edx]

mov dx, [edx].TErrQuad.Red

and edx, 0f8h

and eax, 0f800f8h

shl edx, 7

shr ax, 3

or dx, ax

shr eax, 14

or eax, edx

add ecx, x

add ecx, x // ecx = PixelAddr + x * 2

mov [ecx], ax // *ecx = Errs[x] to 16Bit (555)

end;

// in mm1 = Errs[x]; out mm1 = currErr

procedure TIndexImage.SetDitherPixel16(var Err: TErrQuad; PixelAddr: Pointer; x: Integer);

const

mask16: int64 = $000700030007;

asm

pand mm1, mask16 // currErr = Errs[x] & 0x0700030007

mov eax, [edx]

mov dx, [edx].TErrQuad.Red

and edx, 0f8h

and eax, 0fc00f8h

shl edx, 8

shr ax, 3

or dx, ax

shr eax, 13

or eax, edx

add ecx, x

add ecx, x // ecx = PixelAddr + x * 2

mov [ecx], ax // *ecx = Errs[x] to 16Bit (565)

end;

procedure TIndexImage.SetFormat(const Value: TIndexFormat);

const

Bits: array[TIndexFormat] of Integer = (1, 4, 8, 16, 16);

begin

if IndexFormat <> Value then

begin

FIndexFormat := Value;

FColorBits := Bits[Value];

FMaxColors := 1 shl FColorBits;

SetColorCount(0);

end;

end;

// 設定圖像樣本圖的像素個數

procedure TIndexImage.SetMinSamplingCount(const Value: Integer);

begin

if FMinSamplingCount <> Value then

begin

FMinSamplingCount := Value;

if FMinSamplingCount < MinSamplings then

FMinSamplingCount := MinSamplings;

SetColorCount(0);

end;

end;

procedure TIndexImage.SetMonochromeThreshold(const Value: LongWord);

begin

if Value < 256 then

FMonochromeThreshold := Value;

end;

// 通過外部Windows調色闆句柄擷取顔色表

procedure TIndexImage.SetPalette(const Value: HPalette);

var

Pal: TMaxLogPalette;

begin

if Value = 0 then

SetColorCount(0)

else if IndexFormat <= if8Bit then

begin

SetColorCount(GetPaletteEntries(Value, 0, FMaxColors, Pal.palPalEntry));

if FLeafCount > 0 then

begin

SwapColors(@Pal.palPalEntry, FLeafCount);

Move(Pal.palPalEntry, FColors^, Sizeof(TRGBQuad) * FLeafCount);

end;

end;

end;

// 設定源圖像資料結構(淺拷貝)

procedure TIndexImage.SetSourceData(const Value: TImageData);

begin

if (Value.PixelFormat shr 8) and $ff <> 32 then

raise EImageDataError.CreateFmt(EIDErrorSourceMust, [32]);

FreeImageData(FSource);

FSource := Value;

end;

{ TIndexBitmap }

function TIndexBitmap.GetIndexBitmap: TBitmap;

begin

if CanIndex then

begin

Result := TBitmap.Create;

Result.Handle := GetBitmap;

end

else Result := nil;

end;

procedure TIndexBitmap.SetSource(Bitmap: TBitmap;

ColorBackground: TColor);

begin

SetBitmap(Bitmap.Handle);

if (Bitmap.PixelFormat = pf32Bit) and (ColorBackground <> 0) then

begin

SwapColors(@ColorBackground, 1);

FillBackground(ColorBackground);

end;

end;

procedure TIndexBitmap.SetSource(Graphic: TGraphic;

ColorBackground: TColor);

var

tmp: TBitmap;

begin

if Graphic is TBitmap then

SetSource(TBitmap(Graphic))

else

begin

tmp := TBitmap.Create;

try

tmp.Assign(Graphic);

SetSource(tmp);

finally

tmp.Free;

end;

end;

end;

{ TGpIndexBitmap }

procedure TGpIndexBitmap.ColorsChange;

begin

if FPalette <> nil then

begin

FreeMem(FPalette);

FPalette := nil;

end;

end;

function TGpIndexBitmap.GetIndexBitmap: TGpBitmap;

const

Formats: array[TIndexFormat] of Gdiplus.TPixelFormat

= (pf1bppIndexed, pf4bppIndexed, pf8bppIndexed, pf16bppRGB555, pf16bppRGB565);

var

Data: TBitmapData;

begin

if CanIndex then

begin

Result := TGpBitmap.Create(SourceData.Width, SourceData.Height,

Formats[IndexFormat]);

if (IndexFormat <= if8bit) then

Result.Palette := GetPalette;

Data := Result.LockBits(GpRect(0, 0, Result.Width, Result.Height),

[imRead, imWrite], Result.PixelFormat);

try

CreateIndexImageData(TImageData(Data));

finally

Result.UnlockBits(Data);

end;

end

else Result := nil;

end;

function TGpIndexBitmap.GetPalette: PColorPalette;

var

I: Integer;

cols: PIndexColorArray;

begin

if ColorCount > 0 then

begin

GetMem(FPalette, Sizeof(TColorPalette) + Sizeof(TARGB) * (ColorCount - 1));

FPalette^.Flags := 0;

FPalette^.Count := ColorCount;

cols := Colors;

for I := 0 to FPalette^.Count - 1 do

FPalette^.Entries[I] := TARGB(cols[I]) or $ff000000;

end;

Result := FPalette;

end;

procedure TGpIndexBitmap.SetPalette(const Value: PColorPalette);

var

Count: Integer;

cols: PIndexColorArray;

begin

if IndexFormat > if8Bit then Exit;

Count := 0;

if Value <> nil then

begin

Count := Value.Count;

if Count > MaxColorCount then

Count := MaxColorCount;

end;

SetColorCount(Count);

if Count > 0 then

begin

cols := Colors;

Move(Value.Entries, cols^, Count * Sizeof(TARGB));

end;

end;

procedure TGpIndexBitmap.SetSource(Bitmap: TGpBitmap;

ColorBackground: TARGB);

var

Data: TBitmapData;

begin

TImageData(Data) := GetImageData(Bitmap.Width, Bitmap.Height);

Data := Bitmap.LockBits(GpRect(0, 0, Integer(Data.Width), Integer(Data.Height)),

[imRead, imUserInputBuf], pf32bppARGB);

Bitmap.UnlockBits(Data);

SourceData := TImageData(Data);

if (Bitmap.PixelFormat = pf32bppARGB) and (ColorBackground <> 0) then

FillBackground(ColorBackground);

end;

end.

    TBitmap應用舉例:

var

Bmp, Bmp8: TBitmap;

IdxData: TIndexBitmap;

begin

Bmp := TBitmap.Create;

Bmp.LoadFromFile('D:/VclLib/GdiplusDemo/Media/20041001.bmp');

IdxData := TIndexBitmap.Create;

IdxData.SetSource(Bmp);

IdxData.Dithering := True;

IdxData.IndexFormat := if8bit; // 轉換為256色

Bmp8 := IdxData.GetIndexBitmap;

Canvas.Draw(0, 0, Bmp);

Canvas.Draw(0, Bmp.Height, Bmp8);

Bmp8.Free;

IdxData.Free;

Bmp.Free;

end;

    GDI+應用舉例:

var

Bmp, Bmp8: TGpBitmap;

IdxData: TGpIndexBitmap;

g: TGpGraphics;

begin

Bmp := TGpBitmap.Create('D:/56-3.jpg');

IdxData := TGpIndexBitmap.Create;

IdxData.SetSource(Bmp);

IdxData.Dithering := True;

IdxData.IndexFormat := if1bit; // 轉換為單色

Bmp8 := IdxData.GetIndexBitmap;

g := TGpGraphics.Create(Canvas.Handle);

g.DrawImage(Bmp, 0, 0);

g.DrawImage(Bmp8, 0, Bmp.Height);

g.Free;

Bmp8.Free;

IdxData.Free;

Bmp.Free;

    下面是本類轉換的幾張圖檔效果,從上到下,依次為:源圖,單色圖,單色仿色圖,16色圖,16色仿色圖,256色圖,256色仿色圖:

    源圖:           

Delphi圖像處理 -- 真彩色圖像轉換為低色彩圖像

    單色圖:        

Delphi圖像處理 -- 真彩色圖像轉換為低色彩圖像

    單色仿色圖:  

Delphi圖像處理 -- 真彩色圖像轉換為低色彩圖像

    16色圖:       

Delphi圖像處理 -- 真彩色圖像轉換為低色彩圖像

    16色仿色圖: 

Delphi圖像處理 -- 真彩色圖像轉換為低色彩圖像

    256色圖:     

Delphi圖像處理 -- 真彩色圖像轉換為低色彩圖像

    256色仿色圖:

Delphi圖像處理 -- 真彩色圖像轉換為低色彩圖像

    例子使用GDI+版本下載下傳位址和說明見《GDI+ for VCL基礎 -- GDI+ 與 VCL》。

    文章中所用資料類型及某些過程見《Delphi圖像處理 -- 資料類型及内部過程》。

    盡管我十分努力,但水準有限,錯誤在所難免,歡迎指正和指導。郵箱位址:

    [email protected]

    注:本文章已于2009.10.27重新整理過。

繼續閱讀