【问题标题】:Resizing HBITMAP while keeping transparent background在保持透明背景的同时调整 HBITMAP 的大小
【发布时间】:2018-06-06 18:51:11
【问题描述】:

我有一个应用程序可以加载具有透明背景的图像,然后我使用StretchBlt 将其调整为所需的大小,并使用HALFTONE 设置SetStretchBltMode(我尝试使用其他模式,同时保持透明度完好,也使调整大小的图像看起来“难看”)。
但是,StretchBlt 将透明背景替换为不适合显示图像的窗口背景的颜色(黑色)。

所以我有两个选择:
1) 将图片的透明背景替换为窗口的背景色,然后使用StretchBlt调整大小
2) 在保持背景透明度的同时调整其大小(首选选项)

我尝试寻找可以提供任一功能的 WinAPI 函数,但没有找到。

如何使用普通 WinAPI 执行任何这些选项(替换透明度或在保留透明度的同时调整其大小)?

【问题讨论】:

  • “透明背景”非常不具体 - 请告诉我们确切的图像格式,例如。 G。 “具有预乘 alpha 的 32 bpp ARGB”。此外,显示您当前的代码会很有帮助。
  • 是GDI功能,GDI卡在24bpp,不支持32bpp的图片格式。考虑 GDI+,gdiplus.h 头文件。
  • 看看WIC API。这些天,GDI 不太好(而且 GDI+ 相当慢)。
  • 使图像透明的一种方法是使用 GetDIBits 获取单个像素,然后将它们与窗口背景颜色(逐个像素)合并。

标签: c++ winapi gdi hbitmap


【解决方案1】:

首先,BitBltStretchBltTransparentBlt 不支持 alpha 通道..

TransparentBlt 的工作原理是使您想要的任何指定颜色透明。

如果您需要 Alpha 通道和混合支持,您需要:AlphaBlend

您可以执行以下操作:

BLENDFUNCTION fnc;
fnc.BlendOp = AC_SRC_OVER;
fnc.BlendFlags = 0;
fnc.SourceConstantAlpha = 0xFF;
fnc.AlphaFormat = AC_SRC_ALPHA;

//You need to create a memDC.. and an HBITMAP..

//Select the hBitmap into the memDC.
HGDIOBJ obj = SelectObject(memDC, hBmp);

//Render with alpha blending..
AlphaBlend(DC, rect.X, rect.Y, rect.Width, rect.Height, memDC, 0, 0, Width, Height, fnc);

//Restore the memDC to original state..
SelectObject(memDC, obj);

或者通过自己计算通道颜色来进行自己的预乘 Alpha 渲染..

或者,您可以尝试 GDI+,看看效果如何:

ULONG_PTR GdiImage::GDIToken = 0;
Gdiplus::GdiplusStartupInput GdiImage::GDIStartInput = NULL;
Gdiplus::GdiplusStartup(&GdiImage::GDIToken, &GdiImage::GDIStartInput, NULL);

Gdiplus::Image* Img = Gdiplus::Image::FromFile(L"PathToImage.ext"); //where ext can be png, bmp, etc..

Gdiplus::Graphics graphics(DC);
//graphics.SetSmoothingMode(SmoothingModeHighSpeed);
graphics.SetInterpolationMode(Gdiplus:: InterpolationModeBilinear); //InterpolationModeNearestNeighbor
//graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHalf);
graphics.DrawImage(Img, x, y, w, h);

delete Img;
Gdiplus::GdiplusShutdown(GdiImage::GDIToken);
GdiImage::GDIStartInput = NULL;
GdiImage::GDIToken = 0;

【讨论】:

  • 这行得通,但生成的图像很丑,就像我的问题中所述的StretchBlt 不使用HALFTONE
  • 好吧,我不确定您所说的“看起来很丑”是什么意思。你不想使用HALFTONE,但你想放大..你有前后图像吗? (缩放前、半色调缩放、无半色调缩放)。
  • @あぶらだこ;我添加了GDIPlus以防万一你想尝试一下。GDIPlus可以支持透明位图、png等。它还具有插值、平滑、半色调等效果和变换。.
【解决方案2】:

我尝试寻找可以提供以下任一功能的 WinAPI 函数 功能,但我没有找到。

尽管其他人已经提出了一些建议,但据我所知,当前 (12/2017) 可用的原生操作系统 API 均不提供高质量图像重采样。

有许多第三方库可用于重采样。如果图像处理不是您的应用程序的主要工作,那么其中许多(大多数?)都非常复杂并且可能过度杀伤力。

在以下示例中,我使用公共域、单头文件、无外部依赖项 "stb_image_resize.h" 库。只需在您的项目中使用#include 并完成它。图书馆不是最快的,但我不会说它特别慢。它的亮点在于易用性和多功能性。如果您想拥有像 Lanczos 这样的额外过滤器(如果有兴趣,我可以提供代码),它也很容易扩展。虽然内置过滤器已经比操作系统 API 的过滤器好很多。

以下示例程序希望在当前目录的文件“flower.bmp”中找到一个 32 bpp 位图,其中包含一个 alpha 通道(非预乘)。 使用 OS API LoadImageW() 加载图像,使用 stbir_resize_uint8() 重新采样到原始尺寸的三分之一,并使用 GDI+ 在当前目录中存储为“flower_resized.bmp”。

#include <Windows.h>
#include <iostream>
#include <gdiplus.h>
#pragma comment( lib, "gdiplus" )
namespace gp = Gdiplus;

#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"

int main()
{
    // Using LR_CREATEDIBSECTION flag to get direct access to the bitmap's pixel data. 
    HBITMAP hBmpIn = reinterpret_cast<HBITMAP>(
        LoadImageW( NULL, L"flower.bmp", IMAGE_BITMAP, 0, 0, 
                    LR_LOADFROMFILE | LR_CREATEDIBSECTION ) );
    if( !hBmpIn )
    {
        std::cout << "Failed to load bitmap.\n";
        return 1;
    }

    // Getting bitmap information including a pointer to the bitmap's pixel data 
    // in infoIn.dsBm.bmBits.
    // This will fail if hBmpIn is not a DIB. In this case you may call GetDIBits() 
    // to get a copy of the bitmap's pixel data instead.
    DIBSECTION infoIn{};
    if( !GetObject( hBmpIn, sizeof( infoIn ), &infoIn ) )
    {
        std::cout << "Bitmap is not a DIB.\n";
        return 1;
    }

    // Some sanity checks of the input image.
    if( infoIn.dsBm.bmBitsPixel != 32 || infoIn.dsBm.bmPlanes != 1 ||
        infoIn.dsBmih.biCompression != BI_RGB )
    {
        std::cout << "Bitmap is not 32 bpp uncompressed.\n";
        return 1;
    }

    // Create a DIB for the output. We receive a HBITMAP aswell as a writable 
    // pointer to the bitmap pixel data.
    int out_w = infoIn.dsBm.bmWidth / 3, out_h = infoIn.dsBm.bmHeight / 3;
    BITMAPINFO infoOut{};
    auto& hdr = infoOut.bmiHeader;
    hdr.biSize = sizeof(hdr);
    hdr.biBitCount = 32;
    hdr.biCompression = BI_RGB;
    hdr.biWidth = out_w;
    hdr.biHeight = out_h;  // negate the value to create top-down bitmap
    hdr.biPlanes = 1;
    unsigned char* pOutPixels = nullptr;
    HBITMAP hBmpOut = CreateDIBSection( NULL, &infoOut, DIB_RGB_COLORS, 
        reinterpret_cast<void**>( &pOutPixels ), NULL, 0 );
    if( !hBmpOut )
    {
        std::cout << "Could not create output bitmap.\n";
        return 1;
    }

    // Resample the input bitmap using the simplest API. 
    // These functions use a "default" resampling filter defined at compile time 
    // (currently "Mitchell" for downsampling and "Catmull-Rom" for upsampling). 
    // To change the filter, you can change the compile-time defaults 
    // by #defining STBIR_DEFAULT_FILTER_UPSAMPLE and STBIR_DEFAULT_FILTER_DOWNSAMPLE, 
    // or you can use the medium-complexity API.
    // Consult "stb_image_resize.h" which contains the documentation.

    stbir_resize_uint8( 
        reinterpret_cast< unsigned char const* >( infoIn.dsBm.bmBits ), 
        infoIn.dsBm.bmWidth,
        infoIn.dsBm.bmHeight,
        0,  // input_stride_in_bytes, 0 = packed continously in memory
        pOutPixels,
        out_w,
        out_h,
        0,  // output_stride_in_bytes, 0 = packed continously in memory
        4   // num_channels
    );

    // Use GDI+ for saving the resized image to disk.

    gp::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdipToken = 0;
    gp::GdiplusStartup( &gdipToken, &gdiplusStartupInput, nullptr );

    {
        gp::Bitmap bmpOut( hBmpOut, nullptr );
        // I'm taking a shortcut here by hardcoding the encoder CLSID. Check MSDN to do it by-the-book:
        // https://msdn.microsoft.com/en-us/library/windows/desktop/ms533843(v=vs.85).aspx
        class __declspec(uuid("{557cf400-1a04-11d3-9a73-0000f81ef32e}")) BmpEncoderId;
        bmpOut.Save( L"flower_resized.bmp", &__uuidof(BmpEncoderId) );
    }

    // Cleanup
    gp::GdiplusShutdown( gdipToken );
    DeleteObject( hBmpIn );
    DeleteObject( hBmpOut );

    std::cout << "All done.\n";

    return 0;
}

注意事项:

重采样透明图像时,通常建议使用premultiplied alpha channel。否则,重新采样的图像可能会出现伪影,通常沿着形状的边缘很明显。除非您指定 STBIR_FLAG_ALPHA_PREMULTIPLIED 标志,否则 STBIR 将使用“alpha 加权重采样”(有效地进行预乘、重采样,然后取消预乘)。因此,在加载图像后手动预乘一次时,您将获得性能优势。大多数可以显示透明图像的 Windows API(如AlphaBlend)都希望 alpha 通道无论如何都会被预乘。

【讨论】:

    猜你喜欢
    • 2012-10-15
    • 1970-01-01
    • 2020-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-30
    • 2014-06-03
    相关资源
    最近更新 更多