隱藏圖不是什麼新鮮的東西,具體表現在大部分社交軟體中,預覽圖看到的是一張圖,而點開後看到的又是另一張圖。雖然很早就看到過這類圖片,但是一直沒有仔細研究過它的原理,今天思考了一下,發現挺有趣的,所以自己也寫了個簡單的演算法把兩張圖片合成為一張隱藏圖。 比如下麵這張圖。 當背景顏色為白色時,通常也就是在預 ...
隱藏圖不是什麼新鮮的東西,具體表現在大部分社交軟體中,預覽圖看到的是一張圖,而點開後看到的又是另一張圖。雖然很早就看到過這類圖片,但是一直沒有仔細研究過它的原理,今天思考了一下,發現挺有趣的,所以自己也寫了個簡單的演算法把兩張圖片合成為一張隱藏圖。
比如下麵這張圖。
當背景顏色為白色時,通常也就是在預覽狀態下,它是這個樣子的
而當背景顏色變黑以後,通常也就是點開圖片以後,它是這樣子的。。
隱藏圖原理
我們知道一張圖片中具有透明度的像素會疊加一部分的背景色,因此當背景色為白色時,所有具有透明度的白色像素全部顯示為純白色,而當背景色為黑色時,所有具有透明度的黑色會顯示為純黑色。因此我們只需要把圖片一的所有像素根據其灰度值轉換成不同透明度的黑色,將圖片二的所有像素根據其灰度值轉換成不同透明度的白色,並將兩圖的所有像素按照任意規律交叉排列,即可生成隱藏圖。這樣當背景色為黑色時,圖一的所有像素將會顯示為純黑色,圖二的所有像素會因為其透明度不同顯現出不同的灰色,此時圖一隱藏,圖二顯現,反之同理。
演算法實現
基本的演算法思路上面已經提過了,可以說是一個相當簡單的演算法了。不過具體有幾點需要註意:
- 由其原理可知,隱藏圖只能是黑白的,不能是彩色的,因此當遇到彩色像素時,需要將其轉換成灰度。對於彩色轉灰度,心理學中有一個公式:Gray = R*0.299 + G*0.587 + B*0.114,我們需要做的是根據算出來的灰度設定像素透明度。在白色背景下,黑色像素的灰度會隨著像透明度增高而降低,在黑色背景下,白色像素的灰度會隨著透明度增高而增高。
- 考慮到需要合成的兩張圖片尺寸不一致,為了保證生成的隱藏圖能夠完成保留兩張圖片信息並且不發生變形,我們需要將最終圖片的長和寬設定為兩張圖片尺寸中最大的長和最大的寬。
好的,接下來把我的代碼實現貼出來吧
1 using System; 2 using System.IO; 3 using System.Drawing; 4 5 class MainClass 6 { 7 public static void Main(string[] args) 8 { 9 //圖片一的文件路徑 10 Stream blackImageReader = new FileStream("/Users/shiyidu/Desktop//1.jpg", FileMode.Open); 11 Bitmap blackImage = new Bitmap(blackImageReader); 12 13 //圖片二的文件路徑 14 Stream whiteImageReader = new FileStream("/Users/shiyidu/Desktop//2.jpg", FileMode.Open); 15 Bitmap whiteImage = new Bitmap(whiteImageReader); 16 17 //生成最終圖片 18 Bitmap finalImage = CalculateHiddenImage(blackImage, whiteImage); 19 20 //最終圖片保存路徑 21 Stream imageCreater = new FileStream("/Users/shiyidu/Desktop//final.png", FileMode.Create); 22 finalImage.Save(imageCreater, System.Drawing.Imaging.ImageFormat.Png); 23 } 24 25 private static Bitmap CalculateHiddenImage(Bitmap blackImage, Bitmap whiteImage) 26 { 27 int b_width = blackImage.Width; 28 int b_height = blackImage.Height; 29 int w_width = whiteImage.Width; 30 int w_height = whiteImage.Height; 31 32 //設定最終圖片的尺寸 33 int f_width = Math.Max(b_width, w_width); 34 int f_height = Math.Max(b_height, w_height); 35 36 Bitmap result = new Bitmap(f_width, f_height); 37 38 //黑色圖片距離邊緣的距離 39 int b_widthOffset = b_width == f_width ? 0 : (f_width - b_width) / 2; 40 int b_heightOffset = b_height == f_height ? 0 : (f_height - b_height) / 2; 41 42 //白色圖片離邊緣距離 43 int w_widthOffset = w_width == f_width ? 0 : (f_width - w_width) / 2; 44 int w_heightOffset = w_height == f_height ? 0 : (f_height - w_height) / 2; 45 46 for (int x = 0; x < f_width; x++) { 47 for (int y = 0; y < f_height; y++) { 48 //上下左右交叉排列黑白像素 49 bool blackPixel = (x + y) % 2 == 0 ? true : false; 50 51 int coor_x; 52 int coor_y; 53 //決定當前像素位置是否對應圖一或圖二某像素,如果沒有,跳過迴圈 54 bool validPixel = true; 55 if (blackPixel) { 56 coor_x = x - b_widthOffset; 57 if (coor_x > b_width - 1) validPixel = false; 58 coor_y = y - b_heightOffset; 59 if (coor_y > b_height - 1) validPixel = false; 60 } else { 61 coor_x = x - w_widthOffset; 62 if (coor_x > w_width - 1) validPixel = false; 63 coor_y = y - w_heightOffset; 64 if (coor_y > w_height - 1) validPixel = false; 65 } 66 67 validPixel = validPixel && coor_x >= 0 && coor_y >= 0; 68 if (!validPixel) continue; 69 70 //根據顏色計算像素灰度,設定透明度 71 if (blackPixel) { 72 Color origin = blackImage.GetPixel(coor_x, coor_y); 73 int gray = (origin.R * 19595 + origin.G * 38469 + origin.B * 7472) >> 16; 74 Color finalColor = Color.FromArgb(255 - gray, Color.Black); 75 result.SetPixel(x, y, finalColor); 76 } else { 77 Color origin = whiteImage.GetPixel(coor_x, coor_y); 78 int gray = (origin.R * 19595 + origin.G * 38469 + origin.B * 7472) >> 16; 79 Color finalColor = Color.FromArgb(gray, Color.White); 80 result.SetPixel(x, y, finalColor); 81 } 82 } 83 } 84 85 return result; 86 } 87 }View Code