BMP 位图隐藏数据
本文最后更新于 297 天前,其中的信息可能已经有所发展或是发生改变。

效果展示

  图床原因,此展示不是原图,800 × 800 像素的位图中藏了一整本三国演义!

  • 位图文件大小:1.75 MB
  • 三国演义字节数:1794598
  • 三国演义字符数:611464

阅读以下内容,你需要了解 BMP文件结构 以及 C++ 生成 BMP 位图

原理

  图像文件一般以像素为计算单位,图像分辨率就等于 水平像素数 X 垂直像素数,图像的分辨率越高,所包含的像素就越多,图像就越清晰,占用空间也越大。
  像素的色彩由 RGB 通道决定。RGB 色彩模式[1]是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB 即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。
  24 位真彩色 BMP 位图,图像数据区一个像素(RGB)以 3 个字节存储,32 位真彩色 BMP 位图,图像数据区一个像素(RGBA)以 4 个字节存储。隐藏数据的方法和加密存储道理差不多。
  加密:写出 BMP 图像数据时,循环读取文本内容或文件内容的字符 utf-8 编码[2]
,拆分写出到保存像素的字节。
  解密:读取 BMP 图像数据时,按原加密方式逆向解密像素字节。

  例如:一个中文汉字“国”,它的 utf-8 编码为 56FD,针对 24 位真彩色,以高两位 56 及低两位 FD 写入一个像素的任意两个颜色通道,剩余一个通道保留作为最终图像整体的偏向色(保留 B 通道,图像整体颜色偏蓝)。

源码


  从 C++ 生成 BMP 位图 文章处复制新建svbmp.hpp,新建 BMP位图隐藏数据.cpp,源码中 三国演义.txt 可自行更换成想要隐藏数据的文件名。

#include <cmath>
#include <iostream>
#include <locale>

#include "svbmp.hpp"

#define BUF 1024

using namespace std;

// 解密数据
void readBMP(char *destFileName, char *bmpFileName) {
    // 来源输入文件
    FILE *sourceFile = fopen(bmpFileName, "rb");

    // 判断来源文件是否存在
    if (sourceFile == NULL) {
        printf("%s 文件不存在!", bmpFileName);
        return;
    }

    // 目标输出文件
    FILE *destFile = fopen(destFileName, "w");

    // 加密数据结果
    RGB data[BUF];
    // 单个解密字符
    wchar_t wstr;

    // 跳过文件头信息
    fseek(sourceFile, 54L, SEEK_SET);

    setlocale(LC_ALL, "");

    // 循环解密
    while (!feof(sourceFile)) {
        fread(data, sizeof(RGB), BUF, sourceFile);

        if (feof(sourceFile)) {
            break;
        }

        // 循环合并编码
        for (int i = 0; i < BUF; i++) {
            wstr = data[i].b & 0x00;
            wstr += data[i].g << 8;
            wstr += data[i].r;

            // 跳过多余纯白区域
            if (wstr != 0xffff) {
                fwprintf(destFile, L"%lc", wstr);
            }
        }
    }

    fclose(sourceFile);
    if (destFile != NULL) {
        fclose(destFile);
    }
}

// 加密存储
void writeBMP(char *bmpFileName, char *sourcFileName) {
    // 来源输入文件
    FILE *sourceFile = fopen(sourcFileName, "r");

    // 判断来源文件是否存在
    if (sourceFile == NULL) {
        printf("%s 文件不存在!", sourcFileName);
        return;
    }

    // 目标输出文件
    FILE *destFile = fopen(bmpFileName, "wb");

    // 加密数据结果
    RGB data[BUF];
    // 内容缓冲区
    wchar_t lineBuf[BUF + 1] = {0x00};

    // 图像长宽
    int total = 0;
    int width = 0;
    int height = 0;

    // 位图对象
    BMP bmp;
    bmp.setBmpFile(destFile);
    bmp.setBiBitCount(24);
    // 先创建空图像
    bmp.initHead(width, height);

    // 循环读取
    while (!feof(sourceFile)) {
        // 判断文件是否为空
        lineBuf[0] = fgetwc(sourceFile);
        if (feof(sourceFile)) {
            break;
        }

        // 存入缓冲区
        for (int i = 1; i < BUF; i++) {
            lineBuf[i] = fgetwc(sourceFile);
        }

        // 缓冲区长度
        int len = wcslen(lineBuf);

        // 循环拆分编码并存入结果
        for (int i = 0; i < len; i++) {
            data[i].r = lineBuf[i] & 0xff;
            data[i].g = (lineBuf[i] & 0xff00) >> 8;
            data[i].b = 0xff;  // 保留,图像整体色将偏蓝
        }

        // 计算图像大小,1:1 存储,多余部分为纯白色
        if (total >= width * height) {
            width += (int)sqrt(len);
            height += (int)sqrt(len);
        }

        // 写出数据
        bmp.append(data, len);

        total += len;
    }
    // 再完善文件头信息
    bmp.initHead(width, height);

    fclose(sourceFile);
    if (destFile != NULL) {
        fclose(destFile);
    }
}

int main() {
    writeBMP("test.bmp", "三国演义.txt");
    readBMP("三国演义副本.txt", "test.bmp");

    return 0;
}

参考

  1. ^https://baike.baidu.com/item/RGB/342517
  2. ^https://baike.baidu.com/item/UTF-8/481798
文章作者: xzakota
文章链接: https://blog.xzakota.com/archives/160
版权声明: 本博客内所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 xzakota
上一篇
下一篇