為什么圖片反復(fù)壓縮后會(huì)普遍會(huì)變綠而不是變其他顏色?
Android 系統(tǒng)自起誕生以來(lái)就引入了名為 Skia 的圖像庫(kù)(Google 自家產(chǎn)品),用于處理圖像,其中包括把圖片壓縮成 JPEG(平時(shí)說(shuō)的 JPG)。而 Skia 又是調(diào)用 libjpeg-turbo 來(lái)實(shí)現(xiàn)真正的壓縮過(guò)程的。為了達(dá)到更好的壓縮效果,JPEG 算法本身,將通常屏幕上表示顏色的 RGB(紅綠藍(lán))數(shù)值,轉(zhuǎn)換為 YUV 數(shù)值(亮度,藍(lán)色分量,紅色分量)。正常情況下這個(gè)算法是輕微有損的。
但是 Skia 不走尋常路,將這個(gè)變換算法的各個(gè)常數(shù)復(fù)制到自己的代碼里(當(dāng)然是合法地),然后降低了精度,以達(dá)到更高的速度(專(zhuān)業(yè)準(zhǔn)確地說(shuō),從 16 位定點(diǎn)數(shù),降低到了 8 位定點(diǎn)數(shù)),這導(dǎo)致了更大的損傷。
最可怕的是……在進(jìn)行這個(gè)變換運(yùn)算的最后一步,需要除以 256,而代碼中,采用了右移操作代替除法以提高執(zhí)行速度(看不懂可以跳過(guò)):
int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT;
int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT;
int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT;
// C?? 是已經(jīng)擴(kuò)大到 2^CSHIFT 倍的矩陣參數(shù)(-0.5 ~ 0.5),CSHIFT = 8
這個(gè)操作并沒(méi)有什么問(wèn)題,數(shù)學(xué)意義就是除以 256。但是問(wèn)題出在:
1、直接截?cái)嗔诵?shù)部分,等價(jià)于 trunc()。如果符號(hào)數(shù)是用補(bǔ)碼實(shí)現(xiàn)的。即全部往負(fù)數(shù)方向取整。如:1.2 → 1; 3.9 → 3;0.0 → 0;-5.1 → -6.
2、較冒險(xiǎn)的符號(hào)數(shù)移位:根據(jù)規(guī)范的定義,對(duì)符號(hào)數(shù)(可正可負(fù)的數(shù))使用移位的效果將由具體的編譯器明確定義決定(implementation-defined)。因?yàn)橐莆皇且粋€(gè)符號(hào)無(wú)關(guān)的操作,對(duì)符號(hào)數(shù)移位將依賴(lài)于符號(hào)數(shù)的具體表現(xiàn)形式。而這個(gè)形式 C++ 沒(méi)有給出一個(gè)限定,由具體的編譯器自行決定,對(duì)于非“補(bǔ)碼”(2's complement)的情況結(jié)果可能并不是所期待的那樣數(shù)值整除2的冪。這里假設(shè)了編譯器都能“正確”理解
# YUV 值向負(fù)方向取整導(dǎo)致什么?
復(fù)習(xí)一下 YUV 的定義:
Y,亮度,0.0 ~ 1.0;
U,或者叫做 Cb,藍(lán)色分量,-0.5 ~ 0.5;
V,或者叫做 Cr,紅色分量,-0.5 ~ 0.5。
在 Skia 的代碼里,YUV 三個(gè)值均對(duì)應(yīng)到 0~255 的范圍。
因?yàn)橄蛳氯≌哉`差在 1 一個(gè)單位以?xún)?nèi):0/256 到 1/256 也就是,YUV 三個(gè)值都變小 0.00% 到 0.39% 這個(gè)范圍。
看一下 U, V 這兩個(gè)決定顏色的值是如何變化的:
顯然,YUV 值向負(fù)方向取整,結(jié)果是呼之欲出的:變暗,變綠。(這里的變暗是 YUV 里的 Y 減小,并不完全準(zhǔn)確對(duì)應(yīng)人類(lèi)視覺(jué)的明暗概念)
這個(gè)錯(cuò)誤的舍入,使得:所有在 0 ~ 255 范圍內(nèi)非整數(shù)的 YUV 值都受到影響。那么某個(gè)像素被舍入到整數(shù)之后,下一次再壓縮 JPEG 應(yīng)該會(huì)好一些吧?很不幸的是,隨之而來(lái)的大量其他有損操作(比如 DCT 變換之后濾去高頻)又會(huì)使得 YUV 值發(fā)生變化:如果發(fā)生變化,假設(shè)隨機(jī)產(chǎn)生關(guān)于 0 對(duì)稱(chēng)的誤差,那么實(shí)際上也有 50% 的機(jī)率使得這個(gè)數(shù)值 -1,因?yàn)橹灰仍瓉?lái)的值小,都會(huì)被向下舍去。
這使得,圖片隨著 Skia 缺陷的色彩空間變換算法反復(fù)壓縮,越來(lái)越綠。