特點:
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色仿色圖:
源圖:

單色圖:
單色仿色圖:
16色圖:
16色仿色圖:
256色圖:
256色仿色圖:
例子使用GDI+版本下載下傳位址和說明見《GDI+ for VCL基礎 -- GDI+ 與 VCL》。
文章中所用資料類型及某些過程見《Delphi圖像處理 -- 資料類型及内部過程》。
盡管我十分努力,但水準有限,錯誤在所難免,歡迎指正和指導。郵箱位址:
[email protected]
注:本文章已于2009.10.27重新整理過。