閱讀提示:
《Delphi影像處理》系列以效率為側重點,一般代碼為PASCAL,核心代碼採用BASM。
《C++影像處理》系列以代碼清晰,可讀性為主,全部使用C++代碼。
儘可能保持二者內容一致,可相互對照。
本文代碼必須包括文章《Delphi影像處理 -- 資料類型及公用過程》中的ImageData.pas單元。
最近一段時間,在整理以前的文章,前幾天剛剛將《Delphi影像處理 -- 色相/飽和度調整》一文修改整理完畢,可昨天不知怎麼突然想到其實《Delphi影像處理 -- 色相/飽和度調整》中的代碼還有最佳化的餘地,因為在我的幾篇關於映像色相/飽和度調整的文章裡,一直是按照SHL為基礎寫的代碼,事實上Photoshop的色相/飽和度調整功能也確實是以SHL為基礎的,但是畢竟Photoshop的色相/飽和度調整並不是SHL調整,無論是明度、色相還是飽和度都有著很大獨立性,它們之間的關係是鬆散性的,並非象SHL那樣,三者聯絡得很緊,在《Delphi影像處理 -- 色相/飽和度調整》一文中,明度部分已經是獨立調整的,但色相和飽和度部分還有著一些聯絡,主要是共用像素的S、L部分,但事實上,用SHL調整色相是很麻煩的,如果採用SHV來調整色相則會簡單得多(當然,這隻是指我採用的SSE代碼,如果不是SSE代碼,就很難說了,因為在《Delphi影像處理 -- 色相/飽和度調整》。一文的色相調整代碼中,只計算了中間值,最大值和最小值保留原來的,這樣本身就節省了不少代碼)。改進代碼寫完後,本想覆蓋更新《Delphi影像處理 -- 色相/飽和度調整》中的代碼,但又覺得以前的代碼還是保留為好,因此,把這改進後的代碼作為續篇,以便二者之間對照比較:
procedure GetBrightTable(Bright: Integer; var Table: TGrayTable);asm push ebx cmp eax, -255 jge @@1 mov eax, -255 jmp @@2@@1: cmp eax, 255 jle @@2 mov eax, 255@@2: push eax mov ebx, 255 fild dword ptr[esp] fwait mov [esp], ebx fidiv dword ptr[esp]// Bright / 255 fwait xor ecx, ecx test eax, eax jg @@Loop xor ebx, ebx // mask = Bright > 0? 255 : 0@@Loop: mov [esp], ecx xor [esp], ebx fild dword ptr[esp] fmul st(0), st(1) fistp dword ptr[esp] fwait mov eax, [esp] add eax, ecx mov [edx], al // Table[i] = (i ^ mask) * Bright / 255 inc edx inc ecx cmp ecx, 256 jb @@Loop ffree st pop eax pop ebxend;procedure HSBSetBright(var Data: TImageData; const Table: TGrayTable);asm push ebp push esi push edi push ebx mov esi, edx call _SetDataRegs mov ebp, edx@@yLoop: push ecx@@xLoop: movzx eax, [edi].TARGBQuad.Blue movzx edx, [edi].TARGBQuad.Green mov al, [esi+eax] mov dl, [esi+edx] mov [edi].TARGBQuad.Blue, al mov [edi].TARGBQuad.Green, dl movzx eax, [edi].TARGBQuad.Red mov al, [esi+eax] mov [edi].TARGBQuad.Red, al add edi, 4 loop @@xLoop pop ecx add edi, ebx dec ebp jnz @@yLoop pop ebx pop edi pop esi pop ebpend;procedure HSBSetHueAndSaturation(var Data: TImageData; Hv, Sv: Integer);const _fc2: Single = 2.0; _fc4: Single = 4.0; _fc6: Single = 6.0; _fc128: Single = 128.0;var Hv0: Integer; fHv: Single; width, height, datOffset: Integer;asm push esi push edi push ebx push ecx mov Hv0, edx call _SetDataRegs mov width, ecx mov height, edx mov datOffset, ebx pop ebx // Sv pxor xmm7, xmm7 pxor xmm3, xmm3 // xmm3清零,便於色相處理時的或運算 mov eax, 1 cvtsi2ss xmm6, eax mov eax, 60 cvtsi2ss xmm5, Hv0 cvtsi2ss xmm4, eax divss xmm5, xmm4 movss fHv, xmm5 // fHv = Hv / 60 mov eax, 255 cvtsi2ss xmm5, ebx cvtsi2ss xmm4, eax divss xmm5, xmm4 movss xmm4, xmm5 // xmm4 = xmm5 = Sv / 255 test ebx, ebx jle @@1 movaps xmm5, xmm6 // if (Sv > 0) subss xmm5, xmm4 // xmm5 = 1 / (1 - xmm4) - 1 rcpss xmm5, xmm5 subss xmm5, xmm6@@1: pshufd xmm5, xmm5, 0@@yLoop: push width@@xLoop: movzx ecx, [edi].TARGBQuad.Blue movzx edx, [edi].TARGBQuad.Green movzx eax, [edi].TARGBQuad.Red cmp ecx, edx // ecx = rgbMax jge @@3 // edx = rgbMin xchg ecx, edx@@3: cmp ecx, eax jge @@4 xchg ecx, eax@@4: cmp edx, eax cmova edx, eax mov eax, ecx sub eax, edx jz @@next // if (delta == 0) continue cvtsi2ss xmm3, eax // xmm3 = delta = rgbMax - rgbmin cmp Hv0, 0 jne @@6 movd xmm0, [edi] punpcklbw xmm0, xmm7 punpcklwd xmm0, xmm7 cvtdq2ps xmm0, xmm0 jmp @@20@@6: // 按HSV調整色相,比HSL快40% movss xmm2, fHv // add = fHv cmp cl, [edi].TARGBQuad.Red jne @@8 // if (R == rgbMax) eax = G - B movzx eax, [edi].TARGBQuad.Green movzx esi, [edi].TARGBQuad.Blue jmp @@10@@8: cmp cl, [edi].TARGBQuad.Green jne @@9 movzx eax, [edi].TARGBQuad.Blue movzx esi, [edi].TARGBQuad.Red addss xmm2, _fc2 // if (G == rgbMax) eax = B - R; add += 2 jmp @@10@@9: movzx eax, [edi].TARGBQuad.Red movzx esi, [edi].TARGBQuad.Green addss xmm2, _fc4 // if (B == rgbMax) eax = R - G; add += 4@@10: sub eax, esi cvtsi2ss xmm1, eax divss xmm1, xmm3 addss xmm1, xmm2 // H = eax / delta + add comiss xmm1, xmm7 jae @@11 addss xmm1, _fc6 // if (H < 0) H += 6 jmp @@12@@11: comiss xmm1, _fc6 jb @@12 subss xmm1, _fc6 // else if (H >= 6) H -= 6@@12: cvtss2si esi, xmm1 // index = Round(H) cvtsi2ss xmm2, esi subss xmm1, xmm2 // extra = H - index comiss xmm1, xmm7 // if (extra < 0) // 如果index發生五入 jae @@13 // { dec esi // index -- addss xmm1, xmm6 // extra ++@@13: // } test esi, 1 jnz @@14 movaps xmm2, xmm1 movss xmm1, xmm6 subss xmm1, xmm2 // if ((index & 1) == 0) extra = 1 - extra@@14: mulss xmm1, xmm3 // xmm1 = delta * extra pslldq xmm1, 4 orps xmm1, xmm3 movlhps xmm1, xmm7 // xmm1 = 0 0 delta*extra delta cvtsi2ss xmm0, ecx // xmm0 = V = rgbMax pshufd xmm0, xmm0, 0 // xmm0 = V V V V subps xmm0, xmm1 // xmm0 - xmm1 = NAN V T P jmp @@jmpTable[esi*4].Pointer@@jmpTable: dd offset @@H60 dd offset @@H120 dd offset @@H180 dd offset @@H240 dd offset @@H300 dd offset @@H360 dd offset @@H60// 當H=6.0時,SSE判斷誤差導致index=6,實際應為0@@H360: // 300 - 359 (V, P, T) pshufd xmm0, xmm0, 11100001b jmp @@H60@@H300: // 240 - 299 (T, P, V) pshufd xmm0, xmm0, 11010010b jmp @@H60@@H240: // 180 - 239 (P, T, V) pshufd xmm0, xmm0, 11000110b jmp @@H60@@H180: // 120 - 179 (P, V, T) pshufd xmm0, xmm0, 11001001b jmp @@H60@@H120: // 60 - 119 (T, V, P) pshufd xmm0, xmm0, 11011000b@@H60: // 0 - 59 (V, T, P) test ebx, ebx je @@25@@20: // 調整飽和度。先按HSL計算亮度 add ecx, edx // ecx = rgbMar + rgbMin cvtsi2ss xmm2, ecx divss xmm2, _fc2 // xmm3 = L = ecx / 2 pshufd xmm2, xmm2, 0 movaps xmm1, xmm0 subps xmm0, xmm2 // rgb0 = rgb - L test ebx, ebx jle @@23 // 如果飽和度增量為正,按SHL計算飽和度以控制新的飽和度上限 comiss xmm2, _fc128 // if (Sv > 0) jb @@21 // { neg ecx add ecx, 510 // if (L >= 128) ecx = 510 - ecx@@21: cvtsi2ss xmm2, ecx divss xmm3, xmm2 // S = delta / ecx addss xmm3, xmm4 comiss xmm3, xmm6 jb @@23 subss xmm3, xmm4 // if ((xmm4 + S) >= 1) rcpss xmm2, xmm3 // rgb0 = rgb0 * (1 / S - 1) subss xmm2, xmm6 // else pshufd xmm2, xmm2, 0 // // 事先已經計算好存放在xmm5中 mulps xmm0, xmm2 // rgb0 = rgb0 * (1 / (1 - xmm4) - 1) jmp @@24 // }@@23: // else mulps xmm0, xmm5 // rgb0 = rgb0 * fSv@@24: addps xmm0, xmm1 // rgb += rgb0@@25: cvtps2dq xmm0, xmm0 packssdw xmm0, xmm7 packuswb xmm0, xmm7 mov al, [edi].TARGBQuad.Alpha movd [edi], xmm0 mov [edi].TARGBQuad.Alpha, al@@next: add edi, 4 dec width jnz @@xLoop add edi, datOffset pop width dec height jnz @@yLoop pop ebx pop edi pop esiend;procedure ImageHSBAdjustment(var Data: TImageData; hValue, sValue, bValue: Integer);var BrightTab: TGrayTable;begin if hValue > 180 then hValue := 180 else if hValue < -180 then hValue := -180; if sValue > 255 then sValue := 255 else if sValue < -255 then sValue := -255; if bValue <> 0 then GetBrightTable(bValue, BrightTab); if (sValue > 0) and (bValue <> 0) then HSBSetBright(Data, BrightTab); if (hValue <> 0) or (sValue <> 0) then begin HSBSetHueAndSaturation(Data, hValue, sValue); end; if (sValue <= 0) and (bValue <> 0) then HSBSetBright(Data, BrightTab);end;
從上面的代碼結構看,色相、飽和度和明度三者基本“獨立”了,明度不用說,早獨立了,色相和飽和度之間共用的部分僅僅是像素RGB值的最大值和差值(HSBSetHueAndSaturation過程中的ecx和xmm3)。經過簡單測試,改進的代碼速度提升不少,色相、飽和度和明度三者全部調整的時間只相當於《Delphi影像處理 -- 色相/飽和度調整》中的色相部分的調整時間。
最後鄙視一下CSDN,近來一直整理修改以前的文章,每一篇文章都重新加了4 - 5個標籤,可是沒過幾天,文章的標籤沒了,我以為是我沒設定好,又重新加了一遍,幾天后,又沒了!不帶這麼玩人的,再次鄙視CSDN。
《Delphi影像處理》系列使用GDI+單元和說明見文章《GDI+ for VCL基礎 -- GDI+ 與 VCL》。
因水平有限,錯誤在所難免,歡迎指正和指導。郵箱地址:maozefa@hotmail.com
這裡可訪問《Delphi影像處理 -- 文章索引》。