C++ Gdi+ 将图像转换为灰度

C++ Gdi+ convert image to grayscale

正在尝试将 32、24、16、8 位图像转换为灰度显示。我阅读了有关使用 BitBlt 的信息,但可能存在一些内置机会 在 GDI+ 中?

代码:

 #include <vector>
...

class gdiplus_init
{
    ULONG_PTR token;
public:
    gdiplus_init()
    {
        Gdiplus::GdiplusStartupInput tmp;
        Gdiplus::GdiplusStartup(&token, &tmp, NULL);
    }
    ~gdiplus_init()
    {
        Gdiplus::GdiplusShutdown(token);
    }
};

bool getbits(const wchar_t *filename, Gdiplus::PixelFormat pixelformat, 
    std::vector<BYTE> &bitmapinfo, std::vector<BYTE> &bits, int &w, int &h)
{
    gdiplus_init init;

    WORD bpp = 0;
    int usage = DIB_RGB_COLORS;
    int palettesize = 0;

    switch(pixelformat)
    {
    case PixelFormat8bppIndexed: 
        bpp = 8; 
        usage = DIB_PAL_COLORS;  
        palettesize = 256 * sizeof(RGBQUAD);
        break;
    case PixelFormat16bppRGB555: bpp = 16; break;
    case PixelFormat16bppRGB565: bpp = 16; break;
    case PixelFormat24bppRGB: bpp = 24; break;
    case PixelFormat32bppRGB: bpp = 32; break;
    default:return false;
    }

    auto src = Gdiplus::Bitmap::FromFile(filename);
    if(src->GetLastStatus() != Gdiplus::Status::Ok)
        return false;

    auto dst = src->Clone(0, 0, src->GetWidth(), src->GetHeight(),
        pixelformat);

    w = src->GetWidth();
    h = src->GetHeight();

    HBITMAP hbitmap;
    Gdiplus::Color color;
    dst->GetHBITMAP(color, &hbitmap);

    //allocate enough memory for bitmapinfo and initialize to zero
    //it's sizeof BITMAPINFO structure + size of palette
    bitmapinfo.resize(sizeof(BITMAPINFO) + palettesize, 0);

    //fill the first 6 parameters
    BITMAPINFO* ptr = (BITMAPINFO*)bitmapinfo.data();
    ptr->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); //don't skip
    ptr->bmiHeader.biWidth = w;
    ptr->bmiHeader.biHeight = h;
    ptr->bmiHeader.biPlanes = 1;
    ptr->bmiHeader.biBitCount = bpp;
    ptr->bmiHeader.biCompression = BI_RGB;

    //magic formula to calculate the size:
    //this is roughly w * h * bytes_per_pixel, it's written this way
    //to account for "bitmap padding"
    DWORD size = ((w * bpp + 31) / 32) * 4 * h;

    //allocate memory for image
    bits.resize(size, 0);

    //finally call GetDIBits to fill bits and bitmapinfo
    HDC hdc = GetDC(0);
    GetDIBits(hdc, hbitmap, 0, h, &bits[0], (BITMAPINFO*)&bitmapinfo[0], usage);
    ReleaseDC(0, hdc);

    //cleanup
    delete src;
    delete dst;

    return true;
}

void CMFCApplicationColorsView::OnDraw(CDC* pDC)
{
    ...
    std::vector<BYTE> bi; //automatic storage
    std::vector<BYTE> bits; 
    int w, h;

    //24-bit test
    if(getbits(L"c:\test\24bit.bmp", PixelFormat24bppRGB, bi, bits, w, h))
        StretchDIBits(dc, 0, 0, w, h, 0, 0, w, h, 
            bits.data(), (BITMAPINFO*)bi.data(), DIB_RGB_COLORS, SRCCOPY);

    //8-bit test
    if(getbits(L"c:\test\8bit.bmp", PixelFormat8bppIndexed, bi, bits, w, h))
        StretchDIBits(dc, 0, 220, w, h, 0, 0, w, h, 
            bits.data(), (BITMAPINFO*)bi.data(), DIB_PAL_COLORS, SRCCOPY);
}

您可以通过各种变换直接绘制GDI+。使用 Gdiplus::Graphics 绘制设备上下文。

对于灰度转换,RGB 值都必须相同。 Gdiplus::ColorMatrix可以变换颜色。绿色通常更重要,它的权重更大。

void draw(CDC *pdc)
{
    //this line should be in OnCreate or somewhere other than paint routine
    Gdiplus::Bitmap source(L"file.jpg");

    //gray scale conversion:
    Gdiplus::ColorMatrix matrix =
    {
        .3f, .3f, .3f,   0,   0,
        .6f, .6f, .6f,   0,   0,
        .1f, .1f, .1f,   0,   0,
        0,   0,   0,   1,   0,
        0,   0,   0,   0,   1
    };
    Gdiplus::ImageAttributes attr;
    attr.SetColorMatrix(&matrix,
        Gdiplus::ColorMatrixFlagsDefault, Gdiplus::ColorAdjustTypeBitmap);

    Gdiplus::Graphics gr(pdc->GetSafeHdc());
    Gdiplus::REAL w = (Gdiplus::REAL)source.GetWidth();
    Gdiplus::REAL h = (Gdiplus::REAL)source.GetHeight();
    Gdiplus::RectF rect(0, 0, w, h);
    gr.DrawImage(&source, rect, 0, 0, w, h, Gdiplus::UnitPixel, &attr);
}

注意,我对灰度矩阵使用了粗略值。请参阅 the answer mentioned in comment 以获得更好的矩阵。

转换文件,过程类似,只是使用Gdiplus::Graphics创建内存dc并保存。

int GetEncoderClsid(const WCHAR* format, CLSID* clsid)
{
    int result = -1;
    UINT num = 0;  // number of image encoders
    UINT size = 0;  // size of the image encoder array in bytes
    Gdiplus::GetImageEncodersSize(&num, &size);
    if(size)
    {
        Gdiplus::ImageCodecInfo* codec = (Gdiplus::ImageCodecInfo*)(malloc(size));
        GetImageEncoders(num, size, codec);
        for(UINT j = 0; j < num; ++j)
            if(wcscmp(codec[j].MimeType, format) == 0)
            {
                *clsid = codec[j].Clsid;
                result = j;
            }
        free(codec);
    }
    return result;
}

bool convert_grayscale(const wchar_t *file_in, const wchar_t *file_out)
{
    CStringW extension = PathFindExtensionW(file_out);
    extension.Remove(L'.');
    extension.MakeLower();
    if(extension == L"jpg") extension = L"jpeg";
    extension = L"image/" + extension;

    CLSID clsid;
    if(GetEncoderClsid(extension, &clsid) == -1)
        return false;

    Gdiplus::Bitmap source(file_in);
    if(source.GetLastStatus() != Gdiplus::Status::Ok)
        return false;

    Gdiplus::REAL w = (Gdiplus::REAL)source.GetWidth();
    Gdiplus::REAL h = (Gdiplus::REAL)source.GetHeight();
    Gdiplus::RectF rect(0, 0, w, h);
    Gdiplus::Bitmap copy((INT)w, (INT)h, source.GetPixelFormat());

    Gdiplus::ColorMatrix matrix =
    {
        .3f, .3f, .3f,   0,   0,
        .6f, .6f, .6f,   0,   0,
        .1f, .1f, .1f,   0,   0,
        0,   0,   0,   1,   0,
        0,   0,   0,   0,   1
    };

    Gdiplus::ImageAttributes attr;
    attr.SetColorMatrix(&matrix, 
            Gdiplus::ColorMatrixFlagsDefault, Gdiplus::ColorAdjustTypeBitmap);
    Gdiplus::Graphics gr(&copy);
    gr.DrawImage(&source, rect, 0, 0, w, h, Gdiplus::UnitPixel, &attr);

    auto st = copy.Save(file_out, &clsid);
    return st == Gdiplus::Status::Ok;
}

...
convert_grayscale(L"source.jpg", L"destination.jpg");