接著上一篇文章的熱度,繼續講一些稍微簡單的演算法。
本文來講講碎片演算法,先貼幾個效果圖:
* *
這是個破壞性的濾鏡,拿美女來說事是因為搞影像的人90%是男人,色色的男人。
關於碎片濾鏡的原理,網路上可找到的資料為:將影像建立四個相互偏移的副本,產生類似重影的效果。
就憑上述一句話,我們就可以動手了。
分析:透過上述幾個影像的比較,特別是眼睛部位,可以看出處理的圖應該看得出像是單眼變成了4個眼睛,因此,網絡上的說法可靠。
那麼偏移的中心在哪裡,偏移的數量又是多少呢,4個偏移,分別是往那些方向偏移呢,這些問題也很簡單,可以那PS做驗證:
具體步驟如下:打開一幅影像,在影像顏色較單調的地方(例如上述美女的手臂處)填滿一處2*2像素的紅色,接著複製圖層,將複製後的圖層進行碎片濾鏡處理,並調整圖層透明度為50%,局部放大可取得下列影像:
&*
如此效果,則可輕易得出結論:
偏移的中心就是以每個像素為中心,4個偏移分別以中心對稱,斜45度均勻圓週佈置,水平和垂直偏移各45度,偏移量4個像素。
那麼如何疊加的問題應該可以猜測,是取四次偏移後累積值的平均值。
針對如此思路,我寫出如下算法:
private void CmdFragment_Click(object sender, EventArgs e) { int X, Y, Z, XX, YY; int Width, Height, Stride; int Speed, Index; int SumR, SumG, SumB; Bitmap Bmp = (Bitmap)Pic.Image; if (Bmp.PixelFormat != PixelFormat.Format24bppRgb) throw new Exception("不支持的图像格式."); Width = Bmp.Width; Height = Bmp.Height; Stride = (int)((Bmp.Width * 3 + 3) & 0XFFFFFFFC); byte[] ImageData = new byte[Stride * Height]; // 用于保存图像数据,(处理前后的都为他) byte[] ImageDataC = new byte[Stride * Height]; // 用于保存克隆的图像数据 int[] OffsetX = new int[] { 4, -4, -4, 4 }; // 每个点的偏移量 int[] OffsetY = new int[] { -4, -4, 4, 4 }; fixed (byte* P = &ImageData[0], CP = &ImageDataC[0]) { byte* DataP = P, DataCP = CP; BitmapData BmpData = new BitmapData(); BmpData.Scan0 = (IntPtr)DataP; // 设置为字节数组的的第一个元素在内存中的地址 BmpData.Stride = Stride; Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite | ImageLockMode.UserInputBuffer, PixelFormat.Format24bppRgb, BmpData); Stopwatch Sw = new Stopwatch(); // 只获取计算用时 Sw.Start(); System.Buffer.BlockCopy(ImageData, 0, ImageDataC, 0, Stride * Height); // 填充克隆数据 for (Y = 0; Y < Height; Y++) { Speed = Y * Stride; for (X = 0; X < Width; X++) { SumB = 0; SumG = 0; SumR = 0; for (Z = 0; Z < 4; Z++) // 累积取样点的取样和 { XX = X + OffsetX[Z]; YY = Y + OffsetY[Z]; if (XX < 0) // 注意越界 XX = 0; else if (XX >= Width) XX = Width - 1; if (YY < 0) YY = 0; else if (YY >= Height) YY = Height - 1; Index = YY * Stride + XX * 3; SumB += DataCP[Index]; SumG += DataCP[Index + 1]; SumR += DataCP[Index + 2]; } DataP[Speed] = (byte)((SumB+2) >> 2); // 求平均值(Sum+2)/4,为什么要+2,就为了四舍五入。比如如果计算结果为108.6,则取像素109更为合理 DataP[Speed + 1] = (byte)((SumG + 2) >> 2); DataP[Speed + 2] = (byte)((SumR + 2) >> 2); Speed += 3; // 跳往下一个像素 } } Sw.Stop(); this.Text = "计算用时: " + Sw.ElapsedMilliseconds.ToString() + " ms"; Bmp.UnlockBits(BmpData); // 必须先解锁,否则Invalidate失败 } Pic.Invalidate();}
演算法中, OffsetX 和 OffsetY分別為取樣點像素的偏移量。同樣,由於該濾鏡涉及了領域操作,在處理前需要做像素備份,但這裡沒有對備份資料進行擴展。因此,在內部程式碼裡就需要對取樣點的座標進行驗證,看是否超過其範圍,如果超過範圍,通常在影像濾鏡演算法範圍內,有3種處理方式:
(1)超過了則認為是其最接近的邊界值,即重複邊緣像素,這部分代碼即上述貼出的if ..... else if 部分。
(2)折回,可用以下程式碼來描述:
while (XX >= Width) XX = XX - Width;while (XX < 0) XX = XX + Width;while (YY >= Height) YY = YY - Height;while (YY < 0) YY = YY + Height;
(3 ) 只計算在影像範圍內的像素:
if (XX >= 0 && XX < Width && YY >= 0 && YY < Height) { // 累加计算 }
當然這樣做,就必須用一個變數記錄下都做了多少次符合條件的計算。
有興趣的朋友可以自己改改程式碼試試看。
上述程式碼段中DataP[Speed] = (byte)((SumB+2) >> 2);要對SumB加2的原因是為了讓結果進行四捨五入的操作,這樣才較為合理。 經過測試,上述程式碼和PS處理的效果100%的吻合。說明我們的猜測是完全正確的。 還能進一步延伸演算法: 想的遠一點,為什麼非的是4個重影呢,非得是45度角度呢,非得是4個像素的水平和垂直偏移呢。我給下圖讓有興趣的讀者自己研發吧。 圖中,角度為32度,半徑為10,碎片數為7,可產生類似下方的效果(可用我的Imageshop進行驗證): 更多PhotoShop演算法原理解析系列- 像素化-碎片相關文章請關注PHP中文網!