在保持透明背景的同时调整 HBITMAP 的大小
Resizing HBITMAP while keeping transparent background
我有一个加载透明背景图像的应用程序,然后我使用 StretchBlt
将其调整为所需的大小,使用 SetStretchBltMode
设置 HALFTONE
(我试过使用其他模式,在保持透明度不变的同时,也使调整后的图像看起来 'ugly').
但是,StretchBlt
将透明背景替换为不适合显示图像的 window 背景的颜色(黑色)。
所以我有两个选择:
1) 用window的背景颜色替换图片的透明背景,然后使用StretchBlt
调整大小
2) 在保持背景透明度的同时调整它的大小(首选选项)
我尝试寻找可提供任一功能的 WinAPI 函数,但我发现 none.
我如何使用普通 WinAPI 执行这些选项中的任何一个(替换透明度或在保持透明度的同时调整其大小)?
首先,BitBlt
、StretchBlt
和 TransparentBlt
不支持 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;
I tried looking for WinAPI function that would provide either
functionality, but I found none.
虽然其他人已经给出了一些建议,但据我所知 none 当前 (12/2017) 可用的原生 OS APIs 提供 high-质量 图像重采样。
有许多第三方库可用于重采样。如果图像处理不是您应用程序的主要工作,它们中的许多(大多数?)都非常复杂并且可能有点矫枉过正。
在下面的示例中,我使用的是 public 域、单头文件、无外部依赖性 "stb_image_resize.h" 库。只需 #include
它在您的项目中并完成它。该库不是最快的,但我不会说它特别慢。它的亮点在于易用性和多功能性。它也很容易扩展,如果你想要像 Lanczos 这样的额外过滤器(如果有兴趣,我可以提供代码)。尽管内置过滤器已经比 OS APIs.
的过滤器好得多。
以下示例程序 期望在当前目录的文件"flower.bmp" 中找到带有alpha 通道(非预乘)的32 bpp 位图。
使用 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 将使用 "alpha-weighted resampling"(有效预乘、重采样,然后取消预乘),除非您指定 STBIR_FLAG_ALPHA_PREMULTIPLIED
标志。因此,在加载图像后手动预乘一次时,您将获得性能优势。大多数可以显示透明图像的Windows APIs(如AlphaBlend
),期望alpha 通道无论如何都被预乘。
我有一个加载透明背景图像的应用程序,然后我使用 StretchBlt
将其调整为所需的大小,使用 SetStretchBltMode
设置 HALFTONE
(我试过使用其他模式,在保持透明度不变的同时,也使调整后的图像看起来 'ugly').
但是,StretchBlt
将透明背景替换为不适合显示图像的 window 背景的颜色(黑色)。
所以我有两个选择:
1) 用window的背景颜色替换图片的透明背景,然后使用StretchBlt
调整大小
2) 在保持背景透明度的同时调整它的大小(首选选项)
我尝试寻找可提供任一功能的 WinAPI 函数,但我发现 none.
我如何使用普通 WinAPI 执行这些选项中的任何一个(替换透明度或在保持透明度的同时调整其大小)?
首先,BitBlt
、StretchBlt
和 TransparentBlt
不支持 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;
I tried looking for WinAPI function that would provide either functionality, but I found none.
虽然其他人已经给出了一些建议,但据我所知 none 当前 (12/2017) 可用的原生 OS APIs 提供 high-质量 图像重采样。
有许多第三方库可用于重采样。如果图像处理不是您应用程序的主要工作,它们中的许多(大多数?)都非常复杂并且可能有点矫枉过正。
在下面的示例中,我使用的是 public 域、单头文件、无外部依赖性 "stb_image_resize.h" 库。只需 #include
它在您的项目中并完成它。该库不是最快的,但我不会说它特别慢。它的亮点在于易用性和多功能性。它也很容易扩展,如果你想要像 Lanczos 这样的额外过滤器(如果有兴趣,我可以提供代码)。尽管内置过滤器已经比 OS APIs.
以下示例程序 期望在当前目录的文件"flower.bmp" 中找到带有alpha 通道(非预乘)的32 bpp 位图。
使用 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 将使用 "alpha-weighted resampling"(有效预乘、重采样,然后取消预乘),除非您指定 STBIR_FLAG_ALPHA_PREMULTIPLIED
标志。因此,在加载图像后手动预乘一次时,您将获得性能优势。大多数可以显示透明图像的Windows APIs(如AlphaBlend
),期望alpha 通道无论如何都被预乘。