iOS LSB图片存信息|暗水印

iOS图片隐藏信息/暗水印

LSB

LSB 是“Least Significant Bit”的缩写,意为“最低有效位”。在二进制中,LSB 是一个二进制数中的最右边(最低位)的位,通常在从右向左数第 0 位。LSB 所在的位置对于数值的大小影响最小,因为它代表的是最小的权值。
举例来说,对于二进制数 1011:

  • 最右边的位是 LSB,即 1,表示 1。
  • 最左边的位是最高有效位(Most Significant Bit,MSB),即 1,表示 8。

效果展示

演示视频链接

原理

将图片转化成bitMap,对每一个像素点的数据的最低位进行修改。改成我们需要的数据。
比如这是utf8编码下的 l 字符

我们使用8个像素点,对每个像素点的最低位进行修改,变成 l 字符对应的数据。这样每8个像素点就可以存一个byte。对于每个像素点,只改动了红色分量最低位的,基本上不会影响图片的显示效果。

encodeMsg

// 将信息存进image
func encodeMsgIntoImg(msg: String, image: UIImage) -> UIImage {
    guard let cgImg = image.cgImage else { return image }
    let width = cgImg.width
    let height = cgImg.height
    guard let colorSpace = cgImg.colorSpace else { return image }
    // 创建的上下文是 每个分量8Bit,每个像素四个字节,bitMapInfo是 RGBX
    var imagePixel = [UInt32](repeating: 0, count: width * height)
    // data: 指向 imagePixel 数组的指针
    guard let context = CGContext(data: &imagePixel,
                                  width: width,
                                  height: height,
                                  bitsPerComponent: 8,
                                  bytesPerRow: 4 * width,
                                  space: colorSpace,
                                  bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else {
        return image
    }
    // 将图片绘制到上下文中
    context.draw(cgImg, in: CGRect(x: 0, y: 0, width: width, height: height))
    
    // 存数据需要有个key用来判断该图片是否存了数据,我们在取的时候,优先把前面的key的字节数据读出来
    // 如果数据解析跟我们的msgKey相同,说明该图片有我们的数据
    let msgKey = "lcm"
    // 用来记录我们存进去的信息有多少字节
    // 默认使用2字节=16bytes来记录内容长度,即最大65535
    // 但真正能记录的内容长度需要由载体图片的大小解决。
    // 以 iPhone4 最小的分辨率 640 x 960,其能记录的隐体信息数值最大为: 614400,已经远大于 65535
    // 所以根据需求 可以使用 更多的字节来记录内容长度
    let contentLenByteCount = 2 // 两个字节 记录 内容长度
    
    // 把 key 和 content分别转换成二进制数据
    let keyData = msgKey.data(using: .utf8)!
    let keyDataCount = keyData.count
    
    let contentData = msg.data(using: .utf8)!
    let contentDataCount = contentData.count
    
    // 我们把 key和content长度字节称为header 内容称为content
    // markCount 是header 和content的总的字节长度
    let markCount = keyDataCount + contentLenByteCount + contentDataCount

    // 循环 heander + content,每次存一个字节
    for i in 0..<markCount {
      // 声明一个 UInt8,表示当前存的字节
      var cotInt8: UInt8 = 0
      if i < keyDataCount { // 下标小于keyDataCount 说明存key
        cotInt8 = keyData[i]
      } else if i < keyDataCount + contentLenByteCount { // 下标小于 header 的长度,存内容长度的那两个字节
        let lenOffset = i - keyDataCount
        let lenData: UInt16 = UInt16(contentDataCount) // 内容长度的数据
        // 根据下标确定是第一个字节还是第二个字节
        cotInt8 = UInt8((lenData >> (8*(contentLenByteCount - lenOffset - 1))) & 0xff)
      } else if i < markCount {
        // 内容字节
        let conOffset = i - keyDataCount - contentLenByteCount
        cotInt8 = contentData[conOffset]
      }
      
      let beginPixelIndex = i*8
      let endPixelIndex = i*8 + 7
      for j in beginPixelIndex...endPixelIndex { // 对应像素点的下标,遍历存每一个bit
          let bitIndex = j - beginPixelIndex
          let targetBit = cotInt8 & (0x01 << (7 - bitIndex)) == 0 ? 0 : 1
          imagePixel[j] = ((imagePixel[j] >> 1) << 1) | UInt32(targetBit)
      }
    }
    
    guard let finalImg = context.makeImage() else {
        return image
    }
    return UIImage(cgImage: finalImg)
}

decodeMsg

func decodeMsg(image: UIImage) -> String? {
  guard let cgImg = image.cgImage else { return nil }
  let width = cgImg.width
  let height = cgImg.height
  guard let colorSpace = cgImg.colorSpace else { return nil }
  var imagePixel = [UInt32](repeating: 0, count: width * height)
  
  guard let context = CGContext(data: &imagePixel,
                                width: width,
                                height: height,
                                bitsPerComponent: 8,
                                bytesPerRow: 4 * width,
                                space: colorSpace,
                                bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else {
      return nil
  }
  context.draw(cgImg, in: CGRect(x: 0, y: 0, width: width, height: height))
  
  let msgKey = "lcm"
  let contentLenByteCount = 2 // 两个字节 记录 内容长度
  
  let keyData = msgKey.data(using: .utf8)!
  let keyDataCount = keyData.count
  
  // 计算 header的字节长度
  let headCount = keyDataCount + contentLenByteCount
  if imagePixel.count < headCount { return nil }
  var headData: [UInt8] = []
  // 根据header的字节长度 把每个像素点的最后一位取出来
  for i in 0..<headCount {
    var item: UInt8 = 0
    for j in 0..<8 {
      let index = i*8 + j
      let bit = imagePixel[index] & 0x01
      item = (item << 1) | UInt8(bit)
    }
    headData.append(item)
  }
  
  // 遍历key的每个字节,判断是否相等,不想等说明该图片没有存信息 return
  for i in 0..<keyDataCount {
    if headData[i] != keyData[i] { return nil }
  }
  
  // 取出内容长度
  let conCount = (Int(headData[keyDataCount]) << 8) + Int(headData[keyDataCount + 1])
  if conCount == 0 { return nil }
  
  // 根据内容长度 取出内容
  var resultData: [UInt8] = []
  for i in headCount..<(headCount + conCount) {
    var item: UInt8 = 0
    for j in 0..<8 {
      let index = i*8 + j
      let bit = imagePixel[index] & 0x01
      item = (item << 1) | UInt8(bit)
    }
    resultData.append(item)
  }
  
  // 将取出来的内容decode
  if let decodedString = String(bytes: resultData, encoding: .utf8) {
    return decodedString
  } else {
    return nil
  }
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值