图像的饱和度调节的标准函数:
procedure GetGrayAlpha(const intSaturationValue: Integer; var alpha: TAlpha; var grays: TGrays);
var
X : Integer;
I : Integer;
Gray: Integer;
begin
for I := 0 to 255 do
begin
alpha[I] := (I * intSaturationValue) shr 8;
end;
X := 0;
for I := 0 to 255 do
begin
Gray := I - alpha[I];
grays[X] := Gray;
Inc(X);
grays[X] := Gray;
Inc(X);
grays[X] := Gray;
Inc(X);
end;
end;
procedure Saturation_Scanline(bmp: TBitmap; const alpha: TAlpha; const grays: TGrays);
var
X, Y : Integer;
pColor: PRGBQuad;
Gray : Integer;
begin
for Y := 0 to bmp.height - 1 do
begin
pColor := bmp.ScanLine[Y];
for X := 0 to bmp.width - 1 do
begin
Gray := grays[pColor^.rgbRed + pColor^.rgbGreen + pColor^.rgbBlue];
pColor^.rgbRed := Max(0, Min(255, Gray + alpha[pColor^.rgbRed]));
pColor^.rgbGreen := Max(0, Min(255, Gray + alpha[pColor^.rgbGreen]));
pColor^.rgbBlue := Max(0, Min(255, Gray + alpha[pColor^.rgbBlue]));
Inc(pColor);
end;
end;
end;
多核并行 + SSE 优化版本:
procedure Saturation_SSEParallel_Proc(pColor: PRGBQuad; const intSaturationValue, bmpWidth: Integer);
asm
MOVSS XMM0, [c_GraySSEDiv3] // XMM0 = 00000000000000000000000000000055
MOVD XMM1, EDX // XMM1 = 00000000000000intSaturationValue
MOVSS XMM2, [c_PixBGRAMask] // XMM2 = |00000000|00000000|00000000|000000FF
SHUFPS XMM0, XMM0, 0 // XMM2 = |00000055|00000055|00000055|00000055
SHUFPS XMM1, XMM1, 0 // XMM1 = |intSaturationValue|intSaturationValue|intSaturationValue|intSaturationValue
SHUFPS XMM2, XMM2, 0 // XMM2 = |000000FF|000000FF|000000FF|000000FF
@LOOP:
MOVUPS XMM5, [EAX] // XMM5 = |A3R3G3B3|A2R2G2B2|A1R1G1B1|A0R0G0B0|
MOVAPS XMM6, XMM5 // XMM6 = |A3R3G3B3|A2R2G2B2|A1R1G1B1|A0R0G0B0|
MOVAPS XMM7, XMM5 // XMM7 = |A3R3G3B3|A2R2G2B2|A1R1G1B1|A0R0G0B0|
// 获取 4 个像素的 B3, B2, B1, B0
ANDPS XMM5, XMM2 // XMM5 = |000000B3|000000B2|000000B1|000000B0|
// 获取 4 个像素的 G3, G2, G1, G0
PSRLD XMM6, 8 // XMM6 = |00A3R3G3|00A2R2G2|00A1R1G1|00A0R0G0|
ANDPS XMM6, XMM2 // XMM6 = |000000G3|000000G2|000000G1|000000G0|
// 获取 4 个像素的 R3, R2, R1, R0
PSRLD XMM7, 16 // XMM7 = |0000A3R3|0000A2R2|0000A1R1|0000A0R0|
ANDPS XMM7, XMM2 // XMM7 = |000000R3|000000R2|000000R1|000000R0|
// 计算饱和值 // Gray = I - alpha[I] = I - (I * intSaturationValue) shr 8
MOVAPS XMM3, XMM5 // XMM3 = XMM5
PADDD XMM5, XMM6 // XMM5 = G+B
PADDD XMM5, XMM7 // XMM5 = G+B+R
PMULLW XMM5, XMM0 // XMM5 = (G+B+R)*85
PSRLD XMM5, 8 // XMM5 = I = (G+B+R)*85/256 = (G+B+R) / 3
MOVAPS XMM4, XMM5 // XMM4 = I = (G+B+R)*85/256 = (G+B+R) / 3
PMULLW XMM5, XMM1 // XMM5 = I * intSaturationValue
PSRLD XMM5, 8 // XMM5 = I * intSaturationValue >> 8
PSUBW XMM4, XMM5 // XMM4 = Gray = I - alpha[I]
PMULLW XMM3, XMM1 // XMM3 = pColor^.rgbBlue * intSaturationValue
PSRLD XMM3, 8 // XMM3 = (pColor^.rgbBlue * intSaturationValue) >> 8 = alpha[pColor^.rgbBlue]
PADDW XMM3, XMM4 // XMM3 = Gray + alpha[pColor^.rgbBlue]
MOVAPS XMM5, XMM3 // XMM5 = Gray + alpha[pColor^.rgbBlue]
PXOR XMM3, XMM3 // XMM3 = |00000000|00000000|00000000|00000000|
PADDUSB XMM5, XMM3 // XMM5 控制在 0 --- 255 之间
PMULLW XMM6, XMM1 // XMM6 = pColor^.rgbGreen * intSaturationValue
PSRLD XMM6, 8 // XMM6 = (pColor^.rgbGreen * intSaturationValue) >> 8 = alpha[pColor^.rgbGreen]
PADDW XMM6, XMM4 // XMM6 = Gray + alpha[pColor^.rgbGreen]
PADDUSB XMM6, XMM3 // XMM6 控制在 0 --- 255 之间
PMULLW XMM7, XMM1 // XMM7 = pColor^.rgbRed * intSaturationValue
PSRLD XMM7, 8 // XMM7 = (pColor^.rgbRed * intSaturationValue) >> 8 = alpha[pColor^.rgbRed]
PADDW XMM7, XMM4 // XMM7 = Gray + alpha[pColor^.rgbRed]
PADDUSB XMM7, XMM3 // XMM7 控制在 0 --- 255 之间
// 返回结果
PSLLD XMM6, 8 // XMM6 = |0000Y300|0000Y200|0000Y100|0000Y000|
PSLLD XMM7, 16 // XMM7 = |00Y30000|00Y20000|00Y10000|00Y00000|
ORPS XMM5, XMM6 // XMM5 = |0000Y3Y3|0000Y2Y2|0000Y1Y1|0000Y0Y0|
ORPS XMM5, XMM7 // XMM5 = |00Y3Y3Y3|00Y2Y2Y2|00Y1Y1Y1|00Y0Y0Y0|
MOVUPS [EAX], XMM5 // [EAX] = XMM5
ADD EAX, 16 // pColor 地址加 16,EAX 指向下4个像素的地址
SUB ECX, 4 // Width 减 4, 每 4 个像素一循环
JNZ @LOOP // 循环
end;
procedure Saturation_SSEParallel(bmp: TBitmap; const intSaturationValue: Integer);
var
StartScanLine: Integer;
bmpWidthBytes: Integer;
begin
StartScanLine := Integer(bmp.ScanLine[0]);
bmpWidthBytes := Integer(bmp.ScanLine[1]) - Integer(bmp.ScanLine[0]);
TParallel.For(0, bmp.height - 1,
procedure(Y: Integer)
var
pColor: PRGBQuad;
begin
pColor := PRGBQuad(StartScanLine + Y * bmpWidthBytes);
Saturation_SSEParallel_Proc(pColor, intSaturationValue, bmp.width);
end);
end;
在我的机器上,4096X4096X32 的图片,调节饱和度,耗时在 4 毫秒 --- 10 毫秒之间。
因为有近似值(SSE 除法)运算,图像有一点差异(和标准的 DELPHI 的函数相比)。有点瑕疵。
详细代码:https://github.com/dbyoung720/ImageGray.git
qq交流群:101611228