首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >图像隐写术

图像隐写术
EN

Code Review用户
提问于 2014-09-24 10:43:17
回答 1查看 2.3K关注 0票数 13

为了一个学校的项目,我在一个小图书馆工作,做隐写术。我刚刚完成了第一种隐写方法,我想我会得到一些反馈。

这种隐写方法通过将每个像素的每个通道的最后一个比特更改为文件的一个位来工作。从图像读取文件会执行反向操作。

我不确定的第一点是参数和返回类型。用户将能够在图像中隐藏任何文件,因此我目前要求一个Bitmap和一个带有文件位置的字符串。我应该问两个文件路径吗?或者我应该要求一个Bitmap和一个Path?我没有把握。返回类型呢?我应该返回一个byte[],还是应该返回一个文件?

此外,还欢迎大家发表一般性意见。如果有人需要,整个项目都在GitHub上。

代码语言:javascript
运行
复制
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;
        }
    }

帮助者班:

代码语言:javascript
运行
复制
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);
        }
EN

回答 1

Code Review用户

回答已采纳

发布于 2014-09-24 11:49:24

  • 大多数评论都说明了为什么要做一些事情。
  • 输入和本地使用的参数被命名为good。
  • 方法和参数名称符合命名指导方针。

Bad

  • 其中一些评论是无用的,因为它们讲述的是像e.g //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()

由于您不确定要使用哪些输入参数和哪些复述类型,为什么不添加一些方法的重载?

代码语言:javascript
运行
复制
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()方法。

代码语言:javascript
运行
复制
private  static Bitmap InternalEmbed(Bitmap target, byte[] fileBytes)
{
    // your former code without 
    // byte[] fileBytes = File.ReadAllBytes(inputFilePath);
}        

在考虑了重载并查看了方法的签名之后,我意识到,如果我们返回一个Bitmap并以一个Bitmap作为输入参数,那么我们就不会假定传递的Bitmap会被更改。由于重载也带有string,所以不能将返回类型更改为void,因此需要以不同的方式调用InternalEmbed()方法。

代码语言:javascript
运行
复制
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了。

票数 9
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/63753

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档