为了一个学校的项目,我在一个小图书馆工作,做隐写术。我刚刚完成了第一种隐写方法,我想我会得到一些反馈。
这种隐写方法通过将每个像素的每个通道的最后一个比特更改为文件的一个位来工作。从图像读取文件会执行反向操作。
我不确定的第一点是参数和返回类型。用户将能够在图像中隐藏任何文件,因此我目前要求一个Bitmap
和一个带有文件位置的字符串。我应该问两个文件路径吗?或者我应该要求一个Bitmap
和一个Path
?我没有把握。返回类型呢?我应该返回一个byte[]
,还是应该返回一个文件?
此外,还欢迎大家发表一般性意见。如果有人需要,整个项目都在GitHub上。
public class SteganoBMP
{
//Embed a file in an image. Because Bitmap is an abstract representation of an image,
//it can then be saved in any image format. Though the file will not be retrievable from
//a lossy format.
public static Bitmap Embed(Bitmap target, String inputFilePath)
{
BitmapData bmpData = PrepareImage(target);
//Math.Abs because Stride can be negative if the image is saved upside down
//i.e. The first pixel in memory is the bottom right one.
int imageSize = Math.Abs(bmpData.Stride) * bmpData.Height;
//Get all the bytes from the file we want to embed, and save it in a byte array
byte[] fileBytes = File.ReadAllBytes(inputFilePath);
//If the file we want to embed is larger than 8 times the size of the image, we can't store it.
//This is because We need one byte in the image to store each bit of the file
if (fileBytes.Length * 8 > imageSize)
throw new FileTooLargeException("The file you are trying to embed needs an image of at least" + fileBytes.Length * 8 + "bytes large");
bool fileWritten = false;
int lastByte = 0;
unsafe
{
byte* ptr = (byte*)bmpData.Scan0;
for (int i = 0; i < imageSize; i++)
{
//We need to do this step 8 times per iteration because we need to access 8 bytes
//in the image for each byte in the file.
for (int j = 0; j < 8; j++)
{
//Go out of the loop once we've written the entire file.
if (i >= fileBytes.Length)
{
fileWritten = true;
break;
}
//AND the current value with ~1 (inverse of 1: 11111110).
//This wil set the last bit to 0.
//Then OR with the new bit
//Helper.GetBitAsByte extracts a single bit from a byte.
//And converts it to a byte, so we can do boolean arithmetic with it.
ptr[i * 8 + j] = (byte)(ptr[i * 8 + j] & ~1 | Helper.GetBitAsByte(fileBytes[i], 7 - j));
}
//We've now written all bytes, remember position so we can write EOF there.
if (fileWritten)
{
lastByte = i;
break;
}
}
//Write an EOF character (0xff) after our data.
//This is the last byte in our file, so we've used 8 times as many in our image, hence times 8
for (int i = lastByte * 8; i < lastByte * 8 + 8; i++)
{
ptr[i] = (byte)(ptr[i] | 1);
}
}
target.UnlockBits(bmpData);
return target;
}
//Extract an embedded file from an image.
//Returns a byte[], so the consumer can do with the data whatever he likes.
public static byte[] Extract(Bitmap source)
{
BitmapData bmpData = PrepareImage(source);
int imageSize = Math.Abs(bmpData.Stride) * bmpData.Height;
//We use a List, because we don't know in advance how big the enbedded file is.
List<byte> fileBytes = new List<byte>();
bool fileRead = false;
unsafe
{
//Get a pointer to the first pixel;
byte* ptr = (byte*)bmpData.Scan0;
for (int i = 0; i < imageSize; i++)
{
fileBytes.Add(0x0);
for (int j = 0; j < 8; j++)
{
//Set the last bit of the current file byte to the last bit of the image byte.
//Then shift it one spot to the left.
//This allows us to do this:
//0000 0001
//0000 0011
//0000 0110
//0000 1101
//etc.
fileBytes[i] <<= 1;
fileBytes[i] = (byte)(fileBytes[i] & ~1 | Helper.GetBitAsByte(ptr[i * 8 + j], 0));
if (fileBytes[i] == 0xff)
{
fileRead = true;
break;
}
}
if (fileRead)
break;
}
}
//Remove our last byte, this is the EOF byte.
fileBytes.RemoveAt(fileBytes.Count - 1);
//Convert it to a byte array
byte[] byteArray = fileBytes.ToArray<byte>();
return byteArray;
}
//This is some boilerplate code to prepare our image for unmanaged access.
private static BitmapData PrepareImage(Bitmap image)
{
//We don't require a specific area to be locked, but we still need to specify one
//So we choose to lock the entire image.
Rectangle lockArea = new Rectangle(0, 0, image.Width, image.Height);
//We use LockBits instead of GetPixel, because it is much faster.
//As a consequence we need to work with unmanaged data.
BitmapData bmpData = image.LockBits(lockArea,
System.Drawing.Imaging.ImageLockMode.ReadWrite,
image.PixelFormat);
return bmpData;
}
}
帮助者班:
static class Helper
{
//Does a bitshift, but wraps the overflow
public static byte RotateLeft(byte value, int count)
{
return (byte)((value << count) | (value >> (8 - count)));
}
//Does a bitshift, but wraps the overflow
public static byte RotateRight(byte value, int count)
{
return (byte)((value >> count) | (value << (8 - count)));
}
//Gets the bit at a certain position in a byte
public static bool GetBit(byte b, int bitNumber)
{
return (b & (1 << bitNumber)) != 0;
}
//Gets the bit at a certain position in a byte,
//And presents it as a byte: 00000000 or 00000001
public static byte GetBitAsByte(byte b, int bitNumber)
{
//This is the same function as GetBit, but we add a conversion to Byte.
//This is possible because a boolean also takes a byte of memory.
return Convert.ToByte((b & (1 << bitNumber)) != 0);
}
发布于 2014-09-24 11:49:24
//Get all the bytes from the file we want to embed, and save it in a byte array byte[] fileBytes = File.ReadAllBytes(inputFilePath);
这样的操作,而不是为什么要做一些事情。if
语句{}
Extract()
方法不调用UnlockBits()
由于您不确定要使用哪些输入参数和哪些复述类型,为什么不添加一些方法的重载?
public static Bitmap Embed(string imageFilePath, string inputFilePath)
{
return InternalEmbed(new Bitmap(imageFilePath), File.ReadAllBytes(inputFilePath));
}
public static Bitmap Embed(Bitmap target, string inputFilePath)
{
return InternalEmbed(target, File.ReadAllBytes(inputFilePath));
}
public static Bitmap Embed(Bitmap target, byte[] content)
{
return InternalEmbed(target, content);
}
并将以前的公共Embed()
方法重命名为私有InternalEmbed()
方法。
private static Bitmap InternalEmbed(Bitmap target, byte[] fileBytes)
{
// your former code without
// byte[] fileBytes = File.ReadAllBytes(inputFilePath);
}
在考虑了重载并查看了方法的签名之后,我意识到,如果我们返回一个Bitmap
并以一个Bitmap
作为输入参数,那么我们就不会假定传递的Bitmap
会被更改。由于重载也带有string
,所以不能将返回类型更改为void
,因此需要以不同的方式调用InternalEmbed()
方法。
public static Bitmap Embed(Bitmap target, String inputFilePath)
{
return InternalEmbed(new Bitmap(target), File.ReadAllBytes(inputFilePath));
}
public static Bitmap Embed(Bitmap target, byte[] content)
{
return InternalEmbed(new Bitmap(target), content);
}
现在我们不再修改已通过的Bitmap
了。
https://codereview.stackexchange.com/questions/63753
复制相似问题