前言在這
最近有很強烈的產品欲望,目標很雜亂。忽然有一天,腦海里出現了一副畫面,一張大大畫像,美輪美奐的夕陽,夕陽下有人靠在樹邊,遙望遠方。正看著呢,突然畫里的人說話了,某年某日做了什么事什么的。
然后就有了近來的想法,把聲音加載到圖片里,留存下來??墒俏矣植幌胱鲆曨l。只想要圖片,然后發現了隱寫術。可以將一種信息隱藏在另一種介質中,同時不影響該介質的表現。正式我想要的。于是就有了這篇文章。
隱寫術是什么呢?下面維基百科的解釋。
隱寫術是一門關于信息隱藏的技巧與科學,所謂信息隱藏指的是不讓除預期的接收者之外的任何人知曉信息的傳遞事件或者信息的內容。
效果圖
原理分析
這里討論的圖片格式是bitmap。 有兩張圖A和B,要把圖B的信息寫入到圖A里,同時不影響圖A的顯示。兩個過程,一個是把信息寫入目標文件中,另一個是把信息從目標文件中提取展示。
bitmap是由無數個像素點組成的。從左到右,從上到下,是一個個的像素點。每一個像素點里包含r/g/b/alpha信息,一般是4個字節表示,每個字節有8位bit。
為求簡單易懂,這里提取RGB像素的最低有效位,用于展示信息 -- 本文里是讀取像素Red字段的最低位用于存儲信息。如果是最低位值是1,說明信息是我們想要的;如果最低位值是0,則不是我們想要的,丟棄。
這種最低有效位思想,英文叫做LSB( Last Significant Bit )。參見維基百科
最低有效位(英語:Least Significant Bit,lsb)是指一個二進制數字中的第0位(即最低位),權值為2^0,可以用它來檢測數的奇偶性.
參考文章見這里,只不過這個小哥是用JS寫的,我這里用Swift實現。
像素
在iOS中,RGB像素由4個字節表示,分別是red、green、blue、alpha。如一個紅色不透明的像素點,用16進制表示就是0xFF0000FF。
假如一個像素點的值0xFAFF00FF,那么紅色字段值就是0xFA, 因為0xFA = 0b11111010, 所以最低位的值是0。
獲取所有像素值
iOS中,獲取bitmap圖片的像素值,可以使用Quartz 2D提供的方法: CGBitmapContextGetData
。最近,發現它真的很強大。之前只是用來做動畫。其實還有很多用途。
簡單說明他的用法:
只有一個參數:CGContext
,這是一個CoreGraphics
的數據類型,用來展示和操作相關信息。你可以把他想成一個畫板容器??梢援嫯?、修改。
返回參數是一個void *
的指針,當然在swift
中是UnsafeMutablePointer<Void>
。指針指向像素空間的第一個像素。當想要操縱所有像素的時候,就需要我們去操作指針+1,來逐個獲取了。
修改某個像素值
通過修改上一步返回的指針指向的值,來達到修改像素值得目的。
示例
獲取上下文CGContext
func getContext(inputImage: UIImage) -> CGContext? {
let inputCGImage = inputImage.CGImage
let colorSpace = CGColorSpaceCreateDeviceRGB()
let width = CGImageGetWidth(inputCGImage)
let height = CGImageGetHeight(inputCGImage)
//每個像素有4個字節
let bytesPerPixel = 4
//每個字節有8個bit
let bitsPerComponent = 8
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = RGBA32.bitmapInfo
guard let context = CGBitmapContextCreate(nil, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo) else {
print("unable to create context")
return nil
}
CGContextDrawImage(context, CGRect(x: 0, y: 0, width: CGFloat(width),height: CGFloat(height)), inputCGImage)
return context
}
//像素值結構體
struct RGBA32: Equatable {
var color: UInt32
var red: UInt8 {
return UInt8((color >> 24) & 255)
}
var green: UInt8 {
return UInt8((color >> 16) & 255)
}
var blue: UInt8 {
return UInt8((color >> 8) & 255)
}
var alpha: UInt8 {
return UInt8((color >> 0) & 255)
}
init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
color = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
}
static let bitmapInfo = CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue
}
func ==(lhs: RGBA32, rhs: RGBA32) -> Bool {
return lhs.color == rhs.color
}
解碼
//MARK: 解碼 - 取red字段最低位,1 隱藏的信息, 0 無用的信息
func decodeImage(inputImage: UIImage, context: CGContext) -> UIImage {
let pixelBuffer = UnsafeMutablePointer<RGBA32>(CGBitmapContextGetData(context))
var currentPixel = pixelBuffer
for _ in 0..<imageHeight(inputImage) {
for _ in 0..<imageHeight(inputImage) {
//獲取當前指針指向的值
let orgin = currentPixel.memory
//用0b0000 0001 與操作,獲取最低位值
let flag = orgin.red & 0b00000001
//設定red值
var redValue:UInt8 = 0
if flag == 1 {
redValue = 255
}
let newColor = RGBA32(red: redValue, green: 0, blue: 0, alpha: orgin.alpha)
currentPixel.memory = newColor
currentPixel += 1
}
}
//輸出image
let outputCGImage = CGBitmapContextCreateImage(context)
let outputImage = UIImage(CGImage: outputCGImage!, scale: inputImage.scale, orientation: inputImage.imageOrientation)
return outputImage
}
編碼
將一段信息寫入到圖片中。稍微有點麻煩,就是需要先獲得展示信息圖B的像素位置。然后,再將圖A位置的像素點Red最低位置為1,其他的位置設置成0。
//MARK: 獲取關鍵點像素坐標, 目標圖像 - 白底黑字
func getKeyPositionsWithImage(inputImage: UIImage, inputContext: CGContext) -> [(Int, Int)] {
let pixelBuffer = UnsafeMutablePointer<RGBA32>(CGBitmapContextGetData(inputContext))
var currentPixel = pixelBuffer
var result: [(Int, Int)] = [(0,0)]
for i in 0..<imageHeight(inputImage) {
for j in 0..<imageHeight(inputImage) {
//獲取黑字像素的坐標
let black = RGBA32(red: 0, green: 0, blue: 0, alpha: 255)
if currentPixel.memory == black {
result.append((j,i))
}
currentPixel += 1
}
}
return result
}
//MARK: 編碼,寫入圖像
func encodeSteganographyWithImage(inputImage: UIImage, context: CGContext, position: [(Int,Int)] ) -> UIImage {
let pixelBuffer = UnsafeMutablePointer<RGBA32>(CGBitmapContextGetData(context))
var currentPixel = pixelBuffer
var index = 0
for height in 0..<imageHeight(inputImage) {
for width in 0..<imageHeight(inputImage) {
let originColor = currentPixel.memory
var newRewValue:UInt8 = originColor.red
if positions != nil && index < positions?.count {
if (width,height) == positions![index] {
index += 1
newRewValue = setLastBitOne(originColor.red)
} else {
newRewValue = setLastBitZero(originColor.red)
}
}
currentPixel.memory = RGBA32(red: newRewValue, green: originColor.green, blue: originColor.blue, alpha: originColor.alpha)
currentPixel += 1
}
}
let outputCGImage = CGBitmapContextCreateImage(context)
let outputImage = UIImage(CGImage: outputCGImage!, scale: inputImage.scale, orientation: inputImage.imageOrientation)
return outputImage
}
引用:
不能說的秘密——前端也能玩的圖片隱寫術 :一個小哥用JS寫的,簡單實用。
附錄