圖片隱寫技術介紹:
【如何在圖片中塞入二維碼不被發現】 https://www.bilibili.com/video/BV12R4y1G72d/?share_source=copy_web&vd_source=d944df449598b7e51bbc29cddb033275
png圖片格式介紹:
https://www.cnblogs.com/senior-engineer/p/9548347.html
https://zhuanlan.zhihu.com/p/23890809
圖片隱寫,藉助的是圖片的每個畫素點的RGB值,比如取RGB中的B值,這個值的二級制的最低位是0還是1,在肉眼上看看不出絲毫區別,我們就可以利用這個資料位,我們知道二維碼一般都是兩種顏色,黑色和白色,黑色的畫素點用1表示,白色用0表示,我們就把二維碼的每個畫素點轉換成1和0,在把這些1和0存到承載二維碼的本體圖片的畫素的一個顏色值通道的二進位制值的最低位上。這就是大概原理。
如果要從載體圖片讀取二維碼,需要知道二維碼的長和寬,以便讀取正確的二維碼的1和0值,然後在把1和0反轉成黑色或白色,就能解析出構成二維碼了。所以我們需要把二維碼的長和寬寫入到載體圖片中。
這就需要png格式的圖片一定是要符合png的標準,否則在這一步會讓圖片損壞。要辨別png格式,不能只看檔案的副檔名為png,需要看檔案的署名是否是png規定的署名。符合png標準的png的檔案用二進位制編輯器開啟,最開始的8個位元組一定是:0x89 0x50 0x4e 0x47 0x0d 0x0a 0x1a 0x0a。只有先確認了這樣的位元組,才能進行接下來的步驟。
我們需要把二維碼圖片的長和寬的值寫入png檔案中,藉助png的輔助資料塊的格式tEXt,可以實現該目的。我們還需要知道一個IEND資料塊,它一定是位於png二級制檔案的最後,資料格式是固定的{x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82}。知道這些,我們就可以把tExt塊寫入iEND塊之前。為了後續讀取這個tExt塊,需要把tExt的塊的資料區域的最後一個位置寫成整個tExt塊陣列的長度。具體看程式碼。
這就是我對實現png格式圖片隱寫二維碼的總結。
擴充套件:處理隱藏二維碼,其實還可以把一些資料編碼成二級制也是可以隱藏這些資訊的。RGB三個通道都可以同時做這樣的寫值處理。如果二維碼不只是兩種顏色,而且想要還原的時候保持與原二維碼圖一致的話,我的思路是,隱寫二維碼的畫素點的顏色值前23位,載體的每個畫素的RGB值的最低位按順序寫入二維碼的23個二進位制,這要求載體圖片的長和寬大概是二維碼圖片的長和寬的8倍甚至更多,思路是一樣的,只不過解析的時候,需要做畫素之間的對應關係。
全部程式碼:程式碼中的路徑使用了硬編碼,如果需要執行程式碼,要自己準備圖片和改路徑名稱
public class PicHide { private Bitmap _map = null; private Bitmap _qrMap = null; private int _w = 0; private int _h = 0; private string _path = string.Empty; private string _qrPath = string.Empty; private int _qw = 0, _qh = 0; public PicHide(string picName, string qrPath) { _path = picName; _qrPath = qrPath; _map = new Bitmap(picName); _w = _map.Width; _h = _map.Height; _qrMap = new Bitmap(qrPath); _qw = _qrMap.Width; _qh = _qrMap.Height; if (_qw > _w || _qh > _h) { throw new Exception("隱寫的二維碼長寬必須小於圖片本身的長寬"); } } public Color GetRGB(int x, int y) { var c = _map.GetPixel(x, y); return c; } public void HidePic() { for (int i = 0; i < _w; i++) { for (int j = 0; j < _h; j++) { if (i < _qw && j < _qh) { var rgb = _qrMap.GetPixel(i, j); int cBit = 0; if (rgb.R >= 128 && rgb.B >= 128 && rgb.G >= 128) { cBit = 1; } var curColor = _map.GetPixel(i, j); var hideColor = convertRGB(curColor, cBit); setColor(i, j, hideColor); } else { var c = GetRGB(i, j); var nc = convertRGB(c); setColor(i, j, nc); } } } _map.Save("new1.png"); _map.Dispose(); _qrMap.Dispose(); _qrMap = null; _map = null; AddQrCodeWH("new1.png", _qw, _qh); } public int b(int x, int y) { return GetRGB(x, y).B; } public static string GetHideQrCode(string path) { var str = GetWh(path); var arr = str.Split(","); int w = int.Parse(arr[0].Replace("w=", "")); int h = int.Parse(arr[1].Replace("h=", "")); var map = new Bitmap(path); var qrMap = new Bitmap(w, h); for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { var color = map.GetPixel(i, j); int val = GetqrColorbit(color); if (val == 0) { qrMap.SetPixel(i, j, Color.Black); } else { qrMap.SetPixel(i, j, Color.White); } } } map.Dispose(); qrMap.Save("newCode.png"); qrMap.Dispose(); map = null; qrMap = null; return ""; } public static string GetWh(string path) { var file = File.OpenRead(path); var iEnd = new byte[12] { 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; file.Seek(file.Length - iEnd.Length - 5, SeekOrigin.Begin); var allLenArr = new byte[1]; file.Read(allLenArr, 0, 1); var allLen = allLenArr[0]; file.Seek(file.Length - iEnd.Length - allLen, SeekOrigin.Begin); var lenArr = new byte[4]; file.Read(lenArr, 0, 4); file.Seek(4, SeekOrigin.Current); var len = (lenArr[0] << 24) | (lenArr[1] << 16) | (lenArr[2] << 8) | (lenArr[3]); var payload = new byte[len - 1]; file.Read(payload, 0, len - 1); var str = System.Text.Encoding.UTF8.GetString(payload); file.Close(); file.Dispose(); return str; } public int bRemoveLast(int b) { var val = 0xFE; return b & val; } public Color convertRGB(Color c) { Color removeColor = Color.FromArgb(c.A, c.R, c.G, bRemoveLast(c.B)); return removeColor; } public Color convertRGB(Color c, int i) { Color hideColor = Color.FromArgb(c.A, c.R, c.G, (bRemoveLast(c.B) | i)); return hideColor; } public void setColor(int x, int y, Color c) { _map.SetPixel(x, y, c); } public static int GetqrColorbit(Color c) { var x = 0x01; return c.B & x; } private uint getCrc32(byte[] inStr) { uint[] crc32Table = new uint[256]; uint i, j; uint crc; // 生成 CRC32 表 for (i = 0; i < 256; i++) { crc = i; for (j = 0; j < 8; j++) { if ((crc & 1) != 0) { crc = (crc >> 1) ^ 0xEDB88320; } else { crc >>= 1; } } crc32Table[i] = crc; } crc = 0xffffffff; for (i = 0; i < inStr.Length; i++) { crc = (crc >> 8) ^ crc32Table[(crc & 0xFF) ^ inStr[i]]; } crc ^= 0xFFFFFFFF; return crc; } public void AddQrCodeWH(string fileName, int w, int h) { var file = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite); string payLoad = $"w={w},h={h}"; var payArr = System.Text.Encoding.UTF8.GetBytes(payLoad); var len = payArr.Length + 1; var buf = new byte[12 + payArr.Length + 1]; buf[0] = (byte)(len >> 24 & 0xff); buf[1] = (byte)(len >> 16 & 0xff); buf[2] = (byte)(len >> 8 & 0xff); buf[3] = (byte)(len & 0xff); buf[4] = Convert.ToByte('t'); buf[5] = Convert.ToByte('E'); buf[6] = Convert.ToByte('X'); buf[7] = Convert.ToByte('t'); for (int j = 0; j < payArr.Length; j++) buf[j + 8] = payArr[j]; buf[payArr.Length + 8] = (byte)buf.Length; var crc = this.getCrc32(payArr); buf[len + 8] = (byte)(crc >> 24 & 0xff); buf[len + 9] = (byte)(crc >> 16 & 0xff); buf[len + 10] = (byte)(crc >> 8 & 0xff); buf[len + 11] = (byte)(crc & 0xff); var iEnd = new byte[12] { 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; var lenArr = new byte[4]; file.Seek(file.Length - iEnd.Length, SeekOrigin.Begin); file.Write(buf, 0, buf.Length); file.Write(iEnd, 0, iEnd.Length); file.Close(); file.Dispose(); file = null; } //public string testwhInfo() //{ // var file = File.OpenRead(_path); // var iEnd = new byte[12] { 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; // file.Seek(file.Length - iEnd.Length-5, SeekOrigin.Begin); // var allLenArr = new byte[1]; // file.Read(allLenArr, 0, 1); // var allLen = allLenArr[0]; // file.Seek(file.Length - iEnd.Length - allLen, SeekOrigin.Begin); // var lenArr = new byte[4]; // file.Read(lenArr, 0, 4); // file.Seek(4, SeekOrigin.Current); // var len = (lenArr[0] << 24) | (lenArr[1] << 16) | (lenArr[2] << 8) | (lenArr[3]); // var payload=new byte[len-1]; // file.Read(payload, 0, len-1); // var str=System.Text.Encoding.UTF8.GetString(payload); // return str; //} //public void AddQrCodeWH(int w, int h) //{ // var file = File.Open(_path, FileMode.Open, FileAccess.ReadWrite); // string payLoad = $"w={w},h={h}"; // var payArr = System.Text.Encoding.UTF8.GetBytes(payLoad); // var len = payArr.Length + 1; // var buf = new byte[12 + payArr.Length + 1]; // buf[0] = (byte)(len >> 24 & 0xff); // buf[1] = (byte)(len >> 16 & 0xff); // buf[2] = (byte)(len >> 8 & 0xff); // buf[3] = (byte)(len & 0xff); // buf[4] = Convert.ToByte('t'); // buf[5] = Convert.ToByte('E'); // buf[6] = Convert.ToByte('X'); // buf[7] = Convert.ToByte('t'); // for (int j = 0; j < payArr.Length; j++) // buf[j + 8] = payArr[j]; // buf[payArr.Length + 8] = (byte)buf.Length; // var crc = this.getCrc32(payArr); // buf[len + 8] = (byte)(crc >> 24 & 0xff); // buf[len + 9] = (byte)(crc >> 16 & 0xff); // buf[len + 10] = (byte)(crc >> 8 & 0xff); // buf[len + 11] = (byte)(crc & 0xff); // var iEnd = new byte[12] { 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; // var lenArr = new byte[4]; // file.Seek(file.Length - iEnd.Length, SeekOrigin.Begin); // file.Write(buf, 0, buf.Length); // file.Write(iEnd, 0, iEnd.Length); // file.Close(); // file.Dispose(); // file = null; //} }
internal class Program { static PicHide picHide = null; static void Main(string[] args) { Console.WriteLine("Hello, World!"); picHide = new PicHide("o.png", "qrCode.png"); picHide.HidePic(); Console.WriteLine("hiden ok"); Thread.Sleep(3000); PicHide.GetHideQrCode("new1.png"); } }