隐写术的Swift实现

前言在这

最近有很强烈的产品欲望,目标很杂乱。忽然有一天,脑海里出现了一副画面,一张大大画像,美轮美奂的夕阳,夕阳下有人靠在树边,遥望远方。正看着呢,突然画里的人说话了,某年某日做了什么事什么的。
然后就有了近来的想法,把声音加载到图片里,留存下来。可是我又不想做视频。只想要图片,然后发现了隐写术。可以将一种信息隐藏在另一种介质中,同时不影响该介质的表现。正式我想要的。于是就有了这篇文章。

隐写术是什么呢?下面维基百科的解释。

隐写术是一门关于信息隐藏的技巧与科学,所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。

效果图

效果图
图A
图B

原理分析

这里讨论的图片格式是bitmap。 有两张图A和B,要把图B的信息写入到图A里,同时不影响图A的显示。两个过程,一个是把信息写入目标文件中,另一个是把信息从目标文件中提取展示。

bitmap是由无数个像素点组成的。从左到右,从上到下,是一个个的像素点。每一个像素点里包含r/g/b/alpha信息,一般是4个字节表示,每个字节有8位bit。

为求简单易懂,这里提取RGB像素的最低有效位,用于展示信息 -- 本文里是读取像素Red字段的最低位用于存储信息。如果是最低位值是1,说明信息是我们想要的;如果最低位值是0,则不是我们想要的,丢弃。

最低有效位

这种最低有效位思想,英文叫做LSB( Last Significant Bit )。参见维基百科

最低有效位(英语:Least Significant Bitlsb)是指一个二进制数字中的第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写的,简单实用。

附录

demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容