C++操作图像、图片

今天想说的是如何用C++语言操作图片(其实案例代码是用C++写的,如果想用别的语言操作图片,看完本片就会了)。更准确的说是如何从图片文件本身去操作,而不受限于用什么语言。可能这句话有很多人不是很理解,下面我将仔细阐述一下。

就拿我第一各带图形的程序来说吧。例如下面这个五子棋游戏,下面这张图就是这个项目的主界面。在这里插入图片描述
当时,我是用的C语言图形库叫Easxy(感兴趣的可以了解一下)。里面有个loadimage函数,是专门读取jpg格式的图片文件。我当时费劲半天找到这个函数,以为自己会操作图片了。结果没过多久,我又用C++ Win32想写个带图形的程序,结果费牛劲找到了GDI、GDI+两个图像库,里面分别有LoadBitmap函数和Image对象是处理图形的,才解决了问题。后来也用其他编程语言都写过带图形的程序,反正都很费劲才解决我想要的问题。

对本人而言,更倾向于底层造轮子的。所以,后来专门对图片进行研究了一番,才知道我对图片理解错了。我们在编程时操作图片时,不要依赖于程序语言的提供的函数接口,而更应该是从图片文件本身来处理问题。哪些各种编程语言之所以提供这些接口,更多是提高开发效率,而且大多数程序员也不关注图片具体原理。目前我们熟悉的图片格式有BMP(又称位图,计算机原始图,未经压缩),JPG(JPEG编码),JPGE(JPEG),PNG(没有具体了解,感兴趣的可以了解一下)等。所以我们要想操作图片,我们必须了解这些格式的图片是怎么存储的(编码),这样我们才能对图片进行解析。

下面BMP格式的图片进行举例说明,分别对缩放,裁剪,灰度等(三个进行说明)。因为BMP是最容易处理的,知其一,后续任何格式都知道怎么操作了(就是麻烦点而已)。

一问:如何读取、存储BMP图片文件?

首先,我们得知道BMP的编码格式,不知道到去网上查。BMP是包含三个部分(文件头[14字节],图片信息头[40字节],调色板数据[1024字节][可选],颜色数据)。调色板数据,只有位深(位深,简单来说一个像素点的大小,比如8位的图就是灰度图;24位的图片,一般是RGB彩色图;32位的图片就是RGBA带透明度的彩色图)为8位的图片才有。知道这些后,我设计出数据结构,然后按照二进制方式读取、写入文件即可。

//灰度类型
enum GrayType
    {
        GT_NIL,     //无类型
        GT_AVE,     //平均值灰度
        GT_STD,     //标准值灰度
        GT_BYTE8    //8字节灰度(24和32才有意义)
    };

//图片数据结构
class PictureData
    {
    public:
        PictureFormat m_enumType;       //类型
        unsigned int m_unHeight;        //高度
        unsigned int m_unWidth;         //宽度
        unsigned long m_ulPixelLen;     //像素长度
        unsigned char *m_ptuchPixel;    //像素数据
        unsigned short m_unPixelBit;    //像素位数
    public:
        bool m_bIsGray;                 //是否灰度处理过
    public:
        //构造 赋值 拷贝 析构
        PictureData(void);
        PictureData& operator =(const PictureData& obj);
        PictureData(const PictureData& obj);
        virtual ~PictureData(void);
    };

下面是读取代码:

//byteData 图片文件的二进制数据
//byteLen 字节长度
//PictureData 保存图片信息的数据结构
//erroInfo 错误信息
//这里之所以这样传参,是因为本人进行封装了的,拆分一下应该不难,如果觉得有困难,那说明你还得继续学习啊
bool Picture::LoadBMP(const unsigned char *byteData, unsigned long byteLen, PictureData &pictureData, string* errorInfo)
    {
        //偏移量
        unsigned long offset = 0;
        char szLog[1024] = { 0 };

        //格式
        pictureData.m_enumType = PictureFormat::PF_BMP;

        //位图头部结构(2字节对齐)
#pragma pack(push, 2)
        struct S_BMP_FILE_HEADER {
            unsigned short bfType;
            unsigned long bfSize;
            unsigned short bfReserved1;
            unsigned short bfReserved2;
            unsigned long bfOffBits;
        };
#pragma pack(pop)

        //位图信息结构
        struct S_BMP_INFO_HEADER {
            unsigned long biSize;
            long biWidth;
            long biHeight;
            unsigned short biPlanes;
            unsigned short biBitCount;
            unsigned long biCompression;
            unsigned long biSizeImage;
            long biXPelsPerMeter;
            long biYPelsPerMeter;
            unsigned long biClrUsed;
            unsigned long biClrImportant;
        };

        //读取文件头
        S_BMP_FILE_HEADER fileHeader;
        ::memset(&fileHeader, 0, sizeof(S_BMP_FILE_HEADER));
        ::memcpy(&fileHeader, byteData + offset, sizeof(S_BMP_FILE_HEADER));
        offset += sizeof(S_BMP_FILE_HEADER);

        //读取信息头
        S_BMP_INFO_HEADER infoHeader;
        ::memset(&infoHeader, 0, sizeof(S_BMP_INFO_HEADER));
        ::memcpy(&infoHeader, byteData + offset, sizeof(S_BMP_INFO_HEADER));
        offset += sizeof(S_BMP_INFO_HEADER);
        pictureData.m_unWidth = infoHeader.biWidth;
        pictureData.m_unHeight = infoHeader.biHeight;
        pictureData.m_unPixelBit = infoHeader.biBitCount;

        //判断位深
        if (infoHeader.biBitCount != 8 && infoHeader.biBitCount != 24 && infoHeader.biBitCount != 32) {
            sprintf(szLog, "Picture - LoadBMP - Format Error %d Bit! Only Supports 8-Bit, 24-Bit and 32-Bit!", pictureData.m_unPixelBit);
            *errorInfo = szLog;
            return false;
        }

        //判断尺寸大小
        if (infoHeader.biWidth == 0 || infoHeader.biHeight == 0) {
            sprintf(szLog, "Picture - LoadBMP - Size Error! Width Is Zero Or Height Is Zero!");
            *errorInfo = szLog;
            return false;
        }

        //判断是否是灰度图(灰度需要读取颜色表4 * 256)
        unsigned char* ptColorTable = new unsigned char[4 * 256];
        if (8 == infoHeader.biBitCount) {
            ::memcpy(ptColorTable, byteData + offset, 4 * 256);
            offset += (4 * 256);
        }
        delete[]ptColorTable;
        ptColorTable = nullptr;

        //读取颜色数据
        unsigned int lineBytes = ((infoHeader.biWidth * (infoHeader.biBitCount / 8) + 3) >> 2) << 2;
        pictureData.m_ulPixelLen = lineBytes * infoHeader.biHeight;
        pictureData.m_ptuchPixel = new unsigned char[pictureData.m_ulPixelLen];
        ::memcpy(pictureData.m_ptuchPixel, byteData + offset, pictureData.m_ulPixelLen);

        return true;
    }

下面是存储代码:

//byteData 图片文件的二进制数据
//byteLen 字节长度
//PictureData 保存图片信息的数据结构
//erroInfo 错误信息
//这里之所以这样传参,是因为本人进行封装了的,拆分一下应该不难,如果觉得有困难,那说明你还得继续学习啊
bool Picture::SaveBMP(unsigned char* &byteData, unsigned long &byteLen, const PictureData &pictureData, string* errorInfo)
    {
        //偏移量
        unsigned long offset = 0;
        char szLog[1024] = { 0 };

        //位图头部结构(2字节对齐)
#pragma pack(push, 2)
        struct S_BMP_FILE_HEADER {
            unsigned short bfType;
            unsigned long bfSize;
            unsigned short bfReserved1;
            unsigned short bfReserved2;
            unsigned long bfOffBits;
        };
#pragma pack(pop)

        //位图信息结构
        struct S_BMP_INFO_HEADER {
            unsigned long biSize;
            long biWidth;
            long biHeight;
            unsigned short biPlanes;
            unsigned short biBitCount;
            unsigned long biCompression;
            unsigned long biSizeImage;
            long biXPelsPerMeter;
            long biYPelsPerMeter;
            unsigned long biClrUsed;
            unsigned long biClrImportant;
        };

        byteLen = sizeof(S_BMP_FILE_HEADER) + sizeof(S_BMP_INFO_HEADER) + (8 == pictureData.m_unPixelBit ? (4 * 256) : 0) + pictureData.m_ulPixelLen;
        byteData = new unsigned char[byteLen];

        unsigned short depthBytes = pictureData.m_unPixelBit / 8;
        unsigned int lineBytes = ((pictureData.m_unWidth * depthBytes + 3) >> 2) << 2;
        unsigned int colorBytes = lineBytes * pictureData.m_unHeight;
        unsigned long headerSize = sizeof(S_BMP_FILE_HEADER) + sizeof(S_BMP_INFO_HEADER);
        unsigned long fileSize = headerSize + colorBytes;

        //写入文件头
        S_BMP_FILE_HEADER fileHeader;
        ::memset(&fileHeader, 0, sizeof(S_BMP_FILE_HEADER));
        fileHeader.bfType = 0x4D42;
        fileHeader.bfSize = fileSize;
        fileHeader.bfOffBits = headerSize;
        ::memcpy(byteData + offset, &fileHeader, sizeof(S_BMP_FILE_HEADER));
        offset += sizeof(S_BMP_FILE_HEADER);

        //写入信息头
        S_BMP_INFO_HEADER infoHeader;
        ::memset(&infoHeader, 0, sizeof(S_BMP_INFO_HEADER));
        infoHeader.biSize = 40;
        infoHeader.biWidth = pictureData.m_unWidth;
        infoHeader.biHeight = pictureData.m_unHeight;
        infoHeader.biPlanes = 1;
        infoHeader.biBitCount = pictureData.m_unPixelBit;
        infoHeader.biSizeImage = colorBytes;
        ::memcpy(byteData + offset, &infoHeader, sizeof(S_BMP_INFO_HEADER));
        offset += sizeof(S_BMP_INFO_HEADER);

        //判断是否为灰度图(灰度图需要写入颜色表4 * 256)
        if (8 == pictureData.m_unPixelBit) {
            int count = 4 * 256;
            unsigned char *ptColorTable = new unsigned char[count];
            unsigned char j = 0;
            for (int i = 0; i < count; i += 4, j++) {
                ptColorTable[i] = j;
                ptColorTable[i + 1] = j;
                ptColorTable[i + 2] = j;
                ptColorTable[i + 3] = 0;
            }
            ::memcpy(byteData + offset, ptColorTable, count);
            offset += count;

            delete[]ptColorTable;
            ptColorTable = nullptr;
        }

        //写入颜色数据
        ::memcpy(byteData + offset, pictureData.m_ptuchPixel, pictureData.m_ulPixelLen);

        return true;
    }

二问:如何操作BMP图片文件?

我们读取BMP文件之后就已经拿到这个图片基本信息了,比如长度、宽度、深度、颜色数据等。接下来操作无非是对这些数据进行操作而已。

裁剪操作:(具体怎么做不是很难,可以自己对着代码看,文字不好描述)

//x 横向偏移坐标(就是从哪里开始裁剪)
//y 纵向偏移坐标(就是从哪里开始裁剪)
//w 裁剪宽度
//h 裁剪高度
//PictureData 保存图片信息的数据结构
//erroInfo 错误信息
//这里之所以这样传参,是因为本人进行封装了的,拆分一下应该不难,如果觉得有困难,那说明你还得继续学习啊
bool PictureOperate::Crop(unsigned int x, unsigned int y, unsigned int w, unsigned int h, PictureData &pictureData, string* errorInfo)
    {
        char szLog[1024] = { 0 };
        if (x + w > pictureData.m_unWidth || y + h > pictureData.m_unHeight || x < 0 || y < 0 || w <= 0 || h <= 0) {
            sprintf(szLog, "PictureOperate - Crop - Crop Size Error!");
            *errorInfo = szLog;
            return false;
        }

        unsigned int depthBytes = pictureData.m_unPixelBit / 8;
        unsigned int lineBytes = ((pictureData.m_unWidth * depthBytes + 3) >> 2) << 2;

        unsigned int dstLineBytes = ((w * depthBytes + 3) >> 2) << 2;
        unsigned long dstPixelLen = dstLineBytes * h;
        unsigned char *dstColorData = new unsigned char[dstPixelLen];

        for (unsigned int i = 0; i < h; i++) {
            memcpy(dstColorData + i * dstLineBytes, pictureData.m_ptuchPixel + (i + pictureData.m_unHeight - h - y) * lineBytes + x * depthBytes, dstLineBytes);
        }
        delete[] pictureData.m_ptuchPixel;
        pictureData.m_ptuchPixel = dstColorData;
        pictureData.m_ulPixelLen = dstPixelLen;
        pictureData.m_unHeight = h;
        pictureData.m_unWidth = w;

        return true;
    }

缩放操作:(这个计算稍微有点麻烦,处理不好,容易溢出。但是也别害怕,方法总比困难多。这里用到了个双线插值法(这里不做解释,不知道的去百度))

//factorX 横向缩放(0-1)
//factorY 纵向缩放(0-1)
//PictureData 保存图片信息的数据结构
//erroInfo 错误信息
//这里之所以这样传参,是因为本人进行封装了的,拆分一下应该不难,如果觉得有困难,那说明你还得继续学习啊
bool PictureOperate::Zoom(double factorX, double factorY, PictureData &pictureData, string* errorInfo)
    {
        char szLog[1024] = { 0 };
        //判断位深
        if (pictureData.m_unPixelBit != 8 && pictureData.m_unPixelBit != 24 && pictureData.m_unPixelBit != 32) {
            sprintf(szLog, "PictureOperate - Zoom - Format Error %d Bit! Only Supports 8-Bit, 24-Bit and 32-Bit!", pictureData.m_unPixelBit);
            *errorInfo = szLog;
            return false;
        }

        //判断缩放因子
        if (factorX <= 0 || factorY <= 0) {
            sprintf(szLog, "PictureOperate - Zoom - Factor Error! Cannot Be Negative!");
            *errorInfo = szLog;
            return false;
        }

        double factorReciprocalX = 1 / factorX;
        double factorReciprocalY = 1 / factorY;
        unsigned int dstWidth = unsigned int(pictureData.m_unWidth * factorX);
        unsigned int dstHeight = unsigned int(pictureData.m_unHeight * factorY);

        unsigned int depthBytes = pictureData.m_unPixelBit / 8;
        unsigned int dstLineBytes = ((dstWidth * depthBytes + 3) >> 2) << 2;
        unsigned char *dstBytes = new unsigned char[dstLineBytes * dstHeight];
        unsigned int lineBytes = ((pictureData.m_unWidth * depthBytes + 3) >> 2) << 2;

        for (unsigned int h = 0; h < dstHeight; h++) {
            for (unsigned int w = 0; w < dstWidth; w++) {
                //原图像的真实位置
                double srcRealX = (w + 0.5) * factorReciprocalX - 0.5;
                double srcRealY = (h + 0.5) * factorReciprocalY - 0.5;

                //原图像对应的像素点位置
                int srcX = (int)srcRealX;
                int srcY = (int)srcRealY;

                //原图像位置偏移量
                double offsetX = srcRealX - srcX;
                double offsetY = srcRealY - srcY;

                //原图像位置的临近4个点
                int leftUp = srcY * lineBytes + srcX * depthBytes;
                int rightUp = srcY * lineBytes + (srcX + 1) * depthBytes;
                int leftDown = (srcY + 1) * lineBytes + srcX * depthBytes;
                int rightDown = (srcY + 1) * lineBytes + (srcX + 1) * depthBytes;

                if (srcY + 1 == dstHeight - 1) {
                    leftDown = leftUp;
                    rightDown = rightUp;
                }
                if (srcX + 1 == dstWidth - 1) {
                    rightUp = leftUp;
                    rightDown = leftDown;
                }

                //目的图像像素位置索引
                int index = h * dstLineBytes + w * depthBytes;
                for (unsigned int i = 0; i < depthBytes; i++) {
                    double part1 = pictureData.m_ptuchPixel[leftUp + i] * (1 - offsetX) * (1 - offsetY);
                    double part2 = pictureData.m_ptuchPixel[rightUp + i] * offsetX * (1 - offsetY);
                    double part3 = pictureData.m_ptuchPixel[leftDown + i] * offsetY * (1 - offsetX);
                    double part4 = pictureData.m_ptuchPixel[rightDown + i] * offsetY * offsetX;

                    dstBytes[index + i] = unsigned char(part1 + part2 + part3 + part4);
                }
            }
        }

        delete[]pictureData.m_ptuchPixel;
        pictureData.m_ptuchPixel = dstBytes;
        pictureData.m_ulPixelLen = dstLineBytes * dstHeight;
        pictureData.m_unWidth = dstWidth;
        pictureData.m_unHeight = dstHeight;

        return true;
    }

灰度操作:(这个有多种方式,比如单通道法(就是只有保留一个通道的颜色),平均值法(所有颜色通道值都一样)。本人给的样例是平均值法)

//gt 灰度类型 
//PictureData 保存图片信息的数据结构
//erroInfo 错误信息
//这里之所以这样传参,是因为本人进行封装了的,拆分一下应该不难,如果觉得有困难,那说明你还得继续学习啊
bool PictureOperate::Gray(GrayType gt, PictureData &pictureData, string* errorInfo)
    {
        char szLog[1024] = { 0 };
        if (gt != GT_AVE && gt != GT_STD && gt != GT_BYTE8) {
            sprintf(szLog, "PictureOperate - Gray - Type Error! Value: %d", gt);
            *errorInfo = szLog;
            return false;
        }

        if (GT_BYTE8 == gt) {
            unsigned int depthBytes = pictureData.m_unPixelBit / 8;
            unsigned int lineBytes = ((pictureData.m_unWidth * depthBytes + 3) >> 2) << 2;

            unsigned int newLineBytes = ((pictureData.m_unWidth + 3) >> 2) << 2;
            unsigned char* colorData = new unsigned char[newLineBytes * pictureData.m_unHeight];
            for (unsigned int h = 0; h < pictureData.m_unHeight; h++) {
                for (unsigned int w = 0; w < pictureData.m_unWidth; w++) {
                    int index = h * lineBytes + w * depthBytes;
                    unsigned int value = 0;
                    for (int i = 0; i < 3; i++) {
                        value += unsigned int(pictureData.m_ptuchPixel[index + i]);
                    }
                    value /= 3;

                    int newIndex = h * pictureData.m_unWidth + w;
                    colorData[newIndex] = value;
                }
            }

            delete[] pictureData.m_ptuchPixel;
            pictureData.m_ptuchPixel = colorData;
            pictureData.m_ulPixelLen = newLineBytes * pictureData.m_unHeight;
            pictureData.m_unPixelBit = 8;
        }
        else {
            unsigned int depthBytes = pictureData.m_unPixelBit / 8;
            unsigned int lineBytes = ((pictureData.m_unWidth * depthBytes + 3) >> 2) << 2;

            for (unsigned int h = 0; h < pictureData.m_unHeight; h++) {
                for (unsigned int w = 0; w < pictureData.m_unWidth; w++) {

                    int index = h * lineBytes + w * depthBytes;
                    unsigned int value = 0;
                    //a值不参与计算
                    if (GT_AVE == gt) {
                        for (int i = 0; i < 3; i++) {
                            value += unsigned int(pictureData.m_ptuchPixel[index + i]);
                        }
                        value /= 3;
                    }
                    else if (GT_STD == gt) {
                        unsigned int r = unsigned int(pictureData.m_ptuchPixel[index]);
                        unsigned int g = unsigned int(pictureData.m_ptuchPixel[index + 1]);
                        unsigned int b = unsigned int(pictureData.m_ptuchPixel[index + 2]);

                        value = unsigned int(0.30 * r + 0.59 * g + 0.11 * b);
                    }

                    //不更改a值
                    for (int i = 0; i < 3; i++) {
                        pictureData.m_ptuchPixel[index + i] = value;
                    }
                }
            }
        }
        pictureData.m_bIsGray = true;

        return true;
    }

最后,需要说明的是,在操作BMP图片时一定要注意8位图得特殊处理,因位这种图片多1024字节的调色板数据。还有读取、写入文件时,图片每一行的字节数都必须是4的整数倍,这个一定得注意,不然颜色数据就偏移了。而且本人也只给了三种简单方式操作图片,有兴趣可以挑战PS、以及各种美图软件的对图片的操作,看看能不能实现。也算是对自己的一种锻炼。所以,了解图片本身之后,其他编程语言一样的也就很容易实现了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值