我正在用C#编写一个小型的隐写应用程序,并且能够在图像中隐藏文本。但是我使用的方法是GetPixel/SetPixel
方法,对于较大的图像,该方法要慢得多,在尝试将mp3文件隐藏在图像中后,我注意到了这种方法。经过一些Google搜索,我发现了有关LockBits
的信息。虽然速度确实有了极大的提高,但我发现无法提取隐藏在图像中的加密数据(密文)。
我不确定问题出在我如何插入数据或提取数据时。尝试提取Base64密文时,该文本会被破坏(随机符号和字符),并引发异常(不是Base64String)。我最终按照LockBits
文档中的内容更改了代码,将其粘贴在下面。
合并密文
public static unsafe void MergeEncryptedData(string data, Bitmap bmp, string output) {
State s = State.HIDING;
int height = bmp.Height;
int width = bmp.Width;
var bitmapData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat);
byte * scan0 = (byte * ) bitmapData.Scan0;
int bytesPerPixel = 4;
int dataIndex = 0;
byte dataValue = 0;
long colorUnitIndex = 0;
int zeros = 0;
byte R, G, B;
Parallel.For(0, height, (i, loopState) = > {
byte * currentLine = scan0 + (i * bitmapData.Stride);
for (int j = 0; j < (bitmapData.Width * bytesPerPixel); j += bytesPerPixel) {
R = currentLine[i + 2];
G = currentLine[i + 1];
B = currentLine[i];
for (int n = 0; n < 3; n++) {
if (colorUnitIndex % 8 == 0) {
if (zeros == 8) {
if ((colorUnitIndex - 1) % 3 < 2) {
currentLine[i + 2] = R;
currentLine[i + 1] = G;
currentLine[i] = B;
//bmp.SetPixel(j, i, Color.FromArgb(R, G, B));
}
loopState.Stop();
}
if (dataIndex >= data.Length) {
s = State.FILL_WITH_ZEROS;
} else {
dataValue = (byte) data[dataIndex++];
}
}
switch (colorUnitIndex % 3) {
case 0:
{
if (s == State.HIDING) {
B += (byte)(dataValue % 2);
dataValue /= 2;
}
}
break;
case 1:
{
if (s == State.HIDING) {
G += (byte)(dataValue % 2);
dataValue /= 2;
}
}
break;
case 2:
{
if (s == State.HIDING) {
R += (byte)(dataValue % 2);
dataValue /= 2;
}
currentLine[i + 2] = R;
currentLine[i + 1] = G;
currentLine[i] = B;
//bmp.SetPixel(j, i, Color.FromArgb(R, G, B));
}
break;
}
colorUnitIndex++;
if (s == State.FILL_WITH_ZEROS) {
zeros++;
}
}
}
});
bmp.UnlockBits(bitmapData);
bmp.Save(output, ImageFormat.Png);
}
提取密文
public static unsafe string ExtractData(Bitmap bmp) {
int height = bmp.Height;
int width = bmp.Width;
var bitmapData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bmp.PixelFormat);
byte * scan0 = (byte * ) bitmapData.Scan0.ToPointer();
int bytesPerPixel = 4;
int colorUnitIndex = 0;
int charValue = 0;
string extractedText = String.Empty;
Parallel.For(0, height, (i, loopState) = > {
byte * currentLine = scan0 + (i * bitmapData.Stride);
for (int j = 0; j < (bitmapData.Width * bytesPerPixel); j += bytesPerPixel) {
for (int n = 0; n < 3; n++) { //this particular loop feels incorrect
switch (colorUnitIndex % 3) {
case 0:
{
charValue = charValue * 2 + currentLine[i] % 2;
}
break;
case 1:
{
charValue = charValue * 2 + currentLine[i + 1] % 2;
}
break;
case 2:
{
charValue = charValue * 2 + currentLine[i + 2] % 2;
}
break;
}
colorUnitIndex++;
if (colorUnitIndex % 8 == 0) {
charValue = reverseBits(charValue);
if (charValue == 0) {
loopState.Stop();
}
char c = (char) charValue;
extractedText += c.ToString();
}
}
}
});
bmp.UnlockBits(bitmapData);
return extractedText;
}
抛出错误时提取的密文的外观示例:
I$I$I$I$I$I$I$I$I$I$I$I$I$I䥉II!J$$
。
它应该是Base-64字符串
仅供参考,我使用LUT PNG图像隐藏数据,因此与原始图像相比,我可以看到颜色略有不同。所以我知道RGB值的确在改变。
答案 0 :(得分:0)
您需要考虑:
您提到了RGB PNG,但是您在代码(ARGB)中使用了4个通道,请仔细检查。
这里是一个示例方法,它将获得与使用慢速Bitmap GetPixel相同的数据,但速度非常快。根据此示例,您可以将代码修改为:
代码:
/// <summary>
/// Get pixel directly from unmanaged pixel data based on the Scan0 pointer.
/// </summary>
/// <param name="bmpData">BitmapData of the Bitmap to get the pixel</param>
/// <param name="p">Pixel position</param>
/// <returns>Pixel value</returns>
public static byte[] GetPixel(BitmapData bmpData, Point p)
{
if ((p.X > bmpData.Width - 1) || (p.Y > bmpData.Height - 1))
throw new ArgumentException("GetPixel Point p is outside image bounds!");
int bitsPerPixel = ((int)bmpData.PixelFormat >> 8) & 0xFF;
int channels = bitsPerPixel / 8;
byte[] data = new byte[channels];
int id = p.Y * bmpData.Stride + p.X * channels;
unsafe
{
byte* pData = (byte*)bmpData.Scan0;
for (int i = 0; i < data.Length; i++)
{
data[i] = pData[id + i];
}
}
return data;
}