关于PNG图片转HBITMAP时涉及的一个知识点--预乘透明度

365 阅读2分钟

预乘透明度是什么?

预乘透明度(Premultiplied Alpha)是计算机图形中一种常用的颜色表示方式,用于处理图像和图形的透明度效果。它是一种颜色编码模型,结合了颜色值和透明度值,以提供更准确的颜色混合和合成效果。

在预乘透明度中,每个像素的颜色值(红、绿、蓝通道)都乘以一个透明度值(通常在0到1之间),然后再进行渲染。这样做的目的是在保持透明度效果的同时,避免颜色混合时产生不可预期的结果。预乘透明度可以减少图像合成和混合过程中的伽马校正计算,提高图形渲染的性能。

与预乘透明度相对的是直接透明度(Straight Alpha),在直接透明度中,颜色值和透明度值是独立的,它们分开存储并在渲染时进行合成。直接透明度更容易理解和处理,但在某些情况下,如图像合成、混合和过渡效果等,预乘透明度能够提供更好的视觉结果和效率。

总结起来,预乘透明度是一种在计算机图形中处理透明度效果的方法,通过将颜色值乘以透明度值来实现,以提供更准确和高效的颜色混合和合成。

说简单点就是透过玻璃等半透明后的物质的颜色。

直接分享代码,重点想说的就是中文注释部分

HRESULT NativeContextMenuNgPlugin::CreateHBitmapFromPNGBytes(const std::vector<uint8_t>& pngBytes, HBITMAP& hBitmap, int width, int height) {
    HRESULT hr = S_OK;

    IWICImagingFactory* pFactory = nullptr;
    IWICBitmapDecoder* pDecoder = nullptr;
    IWICBitmapFrameDecode* pFrame = nullptr;
    IWICFormatConverter* pConverter = nullptr;
    IWICBitmapScaler* pScaler = nullptr;

    // Create a WIC factory.
    hr = CoCreateInstance(
        CLSID_WICImagingFactory,
        nullptr,
        CLSCTX_INPROC_SERVER,
        IID_IWICImagingFactory,
        reinterpret_cast<void**>(&pFactory)
    );

    // Create a decoder.
    hr = pFactory->CreateDecoderFromStream(
        SHCreateMemStream(pngBytes.data(), static_cast<UINT>(pngBytes.size())),
        nullptr,
        WICDecodeMetadataCacheOnLoad,
        &pDecoder
    );

    // Retrieve the first frame of the image
    hr = pDecoder->GetFrame(0, &pFrame);

    // Create a format converter
    hr = pFactory->CreateFormatConverter(&pConverter);

    // Initialize a format converter.
    hr = pConverter->Initialize(
        pFrame,
        GUID_WICPixelFormat32bppBGRA,  // The target pixel format, 32-bit BGRA.
        WICBitmapDitherTypeNone,
        nullptr,
        0.0,
        WICBitmapPaletteTypeCustom
    );

    // Create a bitmap scaler
    hr = pFactory->CreateBitmapScaler(&pScaler);

    // Set the target size of the scaler
    hr = pScaler->Initialize(pConverter, width, height, WICBitmapInterpolationModeLinear);

    // Retrieve the scaled image data.
    UINT scaledWidth = 0;
    UINT scaledHeight = 0;
    hr = pScaler->GetSize(&scaledWidth, &scaledHeight);

    // create bitmap
    HDC hdc = GetDC(nullptr);
    HDC hdcMem = CreateCompatibleDC(hdc);
    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = static_cast<LONG>(scaledWidth);
    bmi.bmiHeader.biHeight = -static_cast<LONG>(scaledHeight);
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    void* pBits = nullptr;
    HBITMAP hBitmapTemp = CreateDIBSection(hdcMem, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0);
    ReleaseDC(nullptr, hdcMem);
    ReleaseDC(nullptr, hdc);
    DeleteDC(hdcMem);

    if (hBitmapTemp) {
        std::vector<uint8_t> tmpBits(scaledWidth * scaledHeight * 4);
        hr = pScaler->CopyPixels(nullptr, scaledWidth * 4, scaledWidth * scaledHeight * 4, tmpBits.data());
        if (SUCCEEDED(hr)) {
            // 预乘透明度。没有它的话图标会有毛刺,这个问题困扰了我好久好久。
            for (size_t i = 0; i < tmpBits.size(); i += 4) {
                float alpha = tmpBits[i + 3] / 255.0f;
                tmpBits[i] = static_cast<uint8_t>(tmpBits[i] * alpha); // Blue
                tmpBits[i + 1] = static_cast<uint8_t>(tmpBits[i + 1] * alpha); // Green
                tmpBits[i + 2] = static_cast<uint8_t>(tmpBits[i + 2] * alpha); // Red
            }
            memcpy(pBits, tmpBits.data(), tmpBits.size());

            hBitmap = hBitmapTemp;
            return S_OK;
        }
    }

    return E_FAIL;
}