【问题标题】:Create CImage from Byte array从字节数组创建 CImage
【发布时间】:2011-10-05 04:46:37
【问题描述】:

我需要从一个字节数组创建一个CImage(实际上,它是一个unsigned char 的数组,但我可以转换为任何必要的形式)。字节数组的格式为“RGBRGBRGB...”。新图像需要包含图像字节的副本,而不是使用字节数组本身的内存。

我尝试了许多不同的方法来实现这一点——包括通过各种 HBITMAP 创建功能,尝试使用 BitBlt——但到目前为止没有任何效果。

要测试该功能是否有效,它应该通过这个测试:

BYTE* imgBits;
int width;
int height;
int Bpp;   // BYTES per pixel (e.g. 3)
getImage(&imgBits, &width, &height, &Bpp); // get the image bits

// This is the magic function I need!!!
CImage img = createCImage(imgBits, width, height, Bpp);

// Test the image
BYTE* data = img.GetBits();  // data should now have the same data as imgBits

到目前为止,createCImage() 的所有实现都以 data 指向一个空(零填充)数组而告终。

【问题讨论】:

  • 看起来你需要为你的 imgBits 填写一个 BITMAPINFO 结构,实例化一个 CImage,调用 Create() 来创建一个正确大小的空位图,GetDC() 来获取你的 CImage 的 DC,然后使用 ::SetDIBitsToDevice() 将您的 imgBits 绘制到您的 CImage。
  • @tinman:谢谢,看来这是要走的路。我敢肯定我以前也走这条路,但放弃了,因为我认为我的头球不正确。我再试一次……

标签: c++ mfc atl


【解决方案1】:

CImage 非常巧妙地支持 DIB,并且有一个 SetPixel() 方法,所以你大概可以做这样的事情(未编译、未经测试的代码!):

CImage img;
img.Create(width, height, 24 /* bpp */, 0 /* No alpha channel */);

int nPixel = 0;
for(int row = 0; row < height; row++)
{
    for(int col = 0; col < width; col++)
    {
        BYTE r = imgBits[nPixel++];
        BYTE g = imgBits[nPixel++];
        BYTE b = imgBits[nPixel++];
        img.SetPixel(row, col, RGB(r, g, b));
    }
}

也许不是最有效的方法,但我应该认为这是最简单的方法。

【讨论】:

  • @ATT:谢谢,这肯定会奏效,但我追求的是比这更有效的东西,因为我在@25 fps 的实时视频源上使用它
  • @ltn100 -- 很公平,如果速度是一个问题,你需要做一些更聪明的事情。我认为您仍然可以以相同的方式创建 CImage,然后使用 GetBits() 获取指向 DIB 字节数组的指针,并复制您的图像字节以动态修改 CImage。我认为(不由自主地)DIBSections 期望 BGR 而不是 RGB,因此不幸的是您可能需要对字节进行一些操作。
  • 我最初使用过这种方法一次(当我编写我的第一个图形/GDI 项目时 :))并且 SetPixel() 方法比使用原始字节数据缓冲区慢 100 到 250 倍,具体取决于一个一些变量,如视频卡等。
  • @Roel - 我一点也不惊讶,我过去也看到过类似的结果。它可以快速运行,但只有在性能不是问题的情况下才适用于最终代码。
【解决方案2】:

使用 memcpy 复制数据,然后根据您的需要使用 SetDIBits 或 SetDIBitsToDevice。但请注意,原始图像数据的扫描线在 4 字节边界上对齐(IIRC,我这样做已经有几年了),因此您从 GetDIBits 返回的数据永远不会与原始数据完全相同(可能会,具体取决于图像大小)。

所以你很可能需要逐个扫描线来memcpy。

【讨论】:

    【解决方案3】:

    谢谢大家,在你们的帮助下我终于解决了。它主要涉及@tinman 和@Roel 的使用SetDIBitsToDevice() 的建议,但它涉及一些额外的位旋转和内存管理,所以我想我会在这里分享我的终点。

    在下面的代码中,我假设设置了widthheightBppBytes 每像素),并且data 是指向RGB 数组的指针像素值。

    // Create the header info
    bmInfohdr.biSize = sizeof(BITMAPINFOHEADER);
    bmInfohdr.biWidth = width;
    bmInfohdr.biHeight = -height;
    bmInfohdr.biPlanes = 1;
    bmInfohdr.biBitCount = Bpp*8;
    bmInfohdr.biCompression = BI_RGB;
    bmInfohdr.biSizeImage = width*height*Bpp;
    bmInfohdr.biXPelsPerMeter = 0;
    bmInfohdr.biYPelsPerMeter = 0;
    bmInfohdr.biClrUsed = 0;
    bmInfohdr.biClrImportant = 0;
    
    BITMAPINFO bmInfo;
    bmInfo.bmiHeader = bmInfohdr;
    bmInfo.bmiColors[0].rgbBlue=255;
    
    // Allocate some memory and some pointers
    unsigned char * p24Img = new unsigned char[width*height*3];
    BYTE *pTemp,*ptr;
    pTemp=(BYTE*)data;
    ptr=p24Img;
    
    // Convert image from RGB to BGR
    for (DWORD index = 0; index < width*height ; index++)
    {
        unsigned char r = *(pTemp++);
        unsigned char g = *(pTemp++);
        unsigned char b = *(pTemp++);   
    
        *(ptr++) = b;
        *(ptr++) = g;
        *(ptr++) = r;
    }
    
    // Create the CImage
    CImage im;
    im.Create(width, height, 24, NULL);
    
    HDC dc = im.GetDC();
    SetDIBitsToDevice(dc, 0,0,width,height,0,0, 0, height, p24Img, &bmInfo, DIB_RGB_COLORS);
    im.ReleaseDC();
    
    delete[] p24Img;
    

    【讨论】:

    • 您确定这适用于各种尺寸的图像吗?我查了一下,MSDN 确实说“扫描线必须在 DWORD 上对齐,RLE 压缩位图除外。”我挖出了一些我曾经写过的代码,大致是这样的,我分配的内存比你做的多: int rowlength = 4 * ((nCols * 24 + 31) / 32); BYTE* pBits = (BYTE*)malloc(rowlength * nRows);然后像这样填充该数组中的值: pBits[(bitmap_r * rowlength) + bitmap_c] = GetBValue(color); (这会一一设置颜色分量,bitmap_c迭代过(3 * n_cols))
    • 这些 cmets 很烂,没有代码格式,甚至不能换行:(
    • 嗯,这很好。实际上我不确定,我所有的图像帧都是 640x480,所以我没有问题。但你是对的,其他尺寸可能会导致问题。
    • 哦,我明白了,是的,当您的图像始终是屏幕分辨率时,您应该很好 - 它们(当前)始终是 4 的倍数。
    【解决方案4】:

    这是一个更简单的解决方案。您可以使用 GetPixelAddress(...) 代替所有这些 BITMAPHEADERINFO 和 SedDIBitsToDevice。我解决的另一个问题是 8 位图像,需要定义颜色表。

    CImage outImage;
    outImage.Create(width, height, channelCount * 8);
    
    int lineSize = width * channelCount;
    if (channelCount == 1)
    {
        // Define the color table
        RGBQUAD* tab = new RGBQUAD[256];
        for (int i = 0; i < 256; ++i)
        {
            tab[i].rgbRed = i;
            tab[i].rgbGreen = i;
            tab[i].rgbBlue = i;
            tab[i].rgbReserved = 0;
        }
        outImage.SetColorTable(0, 256, tab);
        delete[] tab;
    }
    
    // Copy pixel values
    // Warining: does not convert from RGB to BGR
    for ( int i = 0; i < height; i++ )
    {
        void*       dst = outImage.GetPixelAddress(0, i);
        const void* src = /* put the pointer to the i'th source row here */;
        memcpy(dst, src, lineSize);
    }
    

    【讨论】:

      猜你喜欢
      • 2015-11-19
      • 2013-09-23
      • 2012-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-04
      • 2011-04-11
      相关资源
      最近更新 更多