如何从PNG创建图像列表?

12

我在这里看到了可以创建具有透明度的图像列表。它确实有效...但是效果一般。

我使用这个方法为列表控件创建了一个图像列表。结果令人有些失望:

实际列表图片 列表应该显示的图片

左边的是应该看起来的样子。右边的是列表控件显示的结果。看起来它只试图将 alpha 用作掩模,而任何混合区域都会尝试通过抖动来近似。有没有办法让它更好,以便我获得真正的 alpha 混合图像?

如果有影响,这里是源代码:

class CDlg : public CDialog
{
    DECLARE_DYNCREATE(CDlg)

public:
    CDlg(CWnd* pParent = NULL);   // standard constructor
    virtual ~CDlg();

    // Dialog Data
    enum { IDD = IDD_BS_PRINT };
    CGdiPlusBitmapResource m_pBitmap;

protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    virtual BOOL OnInitDialog();

    DECLARE_MESSAGE_MAP()
public:
    CListCtrl m_printOptions;
};

BOOL CDlg::OnInitDialog()
{
    __super::OnInitDialog();

    m_pBitmap.Load(IDB_RIBBON_HOMELARGE, _T("PNG"), AfxGetResourceHandle());
    HBITMAP hBitmap;
    m_pBitmap.m_pBitmap->GetHBITMAP(RGB(0, 0, 0), &hBitmap);

    CImageList *pList = new CImageList;
    CBitmap bm;
    bm.Attach(hBitmap);
    pList->Create(32, 32, ILC_COLOR32, 0, 4);
    pList->Add(&bm, RGB(255, 0, 255));
    m_printOptions.SetImageList(pList, LVSIL_NORMAL);

//...
    return TRUE;
}

IMPLEMENT_DYNCREATE(CDlg, CDialog)

CBSPrintDlg::CBSPrintDlg(CWnd* pParent /*=NULL*/)
: CBCGPDialog(CBSPrintDlg::IDD, pParent)
{
}

CBSPrintDlg::~CBSPrintDlg()
{
}

void CBSPrintDlg::DoDataExchange(CDataExchange* pDX)
{
    CBCGPDialog::DoDataExchange(pDX);

    DDX_Control(pDX, IDC_PRINT_OPTIONS, m_printOptions);
}

如果需要CGdiPlusBitmapResource实现的源代码,请参考这里

原始带透明度的图片如下:enter image description here

@Barmak尝试了一张不同的图片,看起来效果不错。我认为这是因为透明度靠近边缘而不在图像内部。请参见此处:

enter image description here


您绝对可以在工具栏上拥有透明按钮。我不熟悉CGdiPlusBitmapResource,但调用GetHBITMAP似乎有问题。提供颜色键暗示着正在去除透明度。 - arx
我无法复制那个显示错误。你确定使用的是*.png图像吗? - Barmak Shemirani
我没有用过MFC包装器,但我知道可以将带有alpha的图像放入图像列表中。您的方法无法正常工作(至少部分原因是),因为您提供了一个COLORREF作为第二个参数,这告诉图像列表生成一个蒙版。您不想要蒙版,而是要一个带有alpha的32位图像。此外,请确保源图像中的像素值是通过alpha预乘的。 - Adrian McCarthy
我认为问题的一部分在于您无法将PNG加载到常规位图中,而必须是DIBSECTION。请参见https://msdn.microsoft.com/en-us/library/windows/desktop/bb761389(v=vs.85).aspx#icons。 - Adrian McCarthy
显示剩余2条评论
2个回答

6

编辑 ----------

Gdiplus::GetHBITMAP 中的第一个参数应该是背景颜色。使用 RGB(0, 0, 0) 作为背景颜色会导致半透明像素与黑色匹配。

使用 Gdiplus::Color(255,255,255,255)(白色)会改善外观(因为 ListView 的背景也是白色)。但最好将背景更改为 Gdiplus::Color(0,255,255,255)(透明),以匹配任何背景。

{
    CGdiPlusBitmapResource gdibmp;
    if (gdibmp.Load(IDB_RIBBON_HOMELARGE, _T("PNG"), AfxGetResourceHandle()))
    {
        HBITMAP hBitmap;
        gdibmp.m_pBitmap->GetHBITMAP(Gdiplus::Color::Transparent, &hBitmap);
        ImageList_AddMasked(*pList, hBitmap, 0);
    }
}

这里默认所有图片都是32x32像素。如果图片大小不同,则必须在添加到图像列表之前将其调整大小。
{
    CGdiPlusBitmapResource gdibmp;
    if (gdibmp.Load(id, _T("PNG"), AfxGetResourceHandle()))
    {
        //resize image to 32x32 pixels
        Gdiplus::Bitmap newBmp(32, 32, PixelFormat32bppPARGB);
        double oldh = (double)gdibmp.m_pBitmap->GetHeight();
        double oldw = (double)gdibmp.m_pBitmap->GetWidth();
        double neww = 32;
        double newh = 32;

        double ratio = oldw / oldh;
        if (oldw > oldh)
            newh = neww / ratio;
        else
            neww = newh * ratio;

        Gdiplus::Graphics graphics(&newBmp);
        graphics.SetInterpolationMode(Gdiplus::InterpolationMode::InterpolationModeHighQualityBicubic);
        graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
        graphics.DrawImage(gdibmp.m_pBitmap, 0, 0, (int)neww, (int)newh);

        //add `newBmp` to image list
        HBITMAP hBitmap;
        newBmp.GetHBITMAP(Gdiplus::Color::Transparent, &hBitmap);
        ImageList_AddMasked(m_ImageList, hBitmap, 0);
    }
}

使用GdiPlus :: GetHICON 获取图标句柄… 使用CGdiPlusBitmapResource 类,应该可以使用以下内容:
HICON hicon;
m_pBitmap.Load(IDB_RIBBON_HOMELARGE, _T("PNG"), AfxGetResourceHandle());
m_pBitmap.m_pBitmap->GetHICON(&hicon);
pList->Add(hicon);

或者使用GetHBITMAP

同时确保启用可视化样式以改善ListView图标的外观。

测试具有透明背景的图像:

输入图像描述


应该可以使用以下内容。不,它不起作用。:( 另外,我不理解你的第二个选项。PNG文件是在资源中还是外部文件?在.RC文件中,我应该把"image_name" RCDATA "path.png"放在哪里?这有关系吗? - Adrian
您可能会在*.rc文件旁边找到一个名为*.rc2的文件。右键单击*.rc2并使用文本编辑器打开它。您可以在*.rc2的末尾附近添加“ image_name” RCDATA“ file_path.png”,也可以在*.rc文件的末尾附近添加。这类似于您使用CGdiPlusBitmapResource添加其他PNG资源的方式。 - Barmak Shemirani
一定是输入有误,因为现在你的第一个解决方案至少显示了一张图片,尽管周围仍然有奇怪的抖动区域。另外,如果PNG包含多个图标,则通过将所有图像缩放以适应列表中的第一个图像来混淆它们。这适用于带有或不带有#pragma设置。 - Adrian
啊哈,成功了。你从哪里得到关于 m_pBitmap->GetHBITMAP(Gdiplus::Color::Transparent, &hBitmap); 的信息?那正是我一直在寻找的!一旦你发布参考资料,我就会给你奖励金。另外,我不需要 #pragma。也许它已经在我的当前环境中默认设置了。 - Adrian
那你是怎么知道该怎么做的呢? - Adrian
显示剩余9条评论

-1

PNG图像包含部分透明的像素(alpha < 255)。这是像Photoshop这样的程序中常见的意外,最可能的原因是将放大镜图像叠加在文档图像上并未正确合并图层。

如所给,该图像只有在浅灰色或白色背景上显示时才能看起来不错。但事实并非如此,背景是黑色的。现在,反锯齿像素周围的放大镜变得非常明显,它们根据其alpha值变成各种深灰色,并且不再与文档图像的白色背景融合。当您使用GDI函数时,这是非常典型的失误,它不喜欢alpha。

您可以使用GDI+进行修复,确保背景颜色正确。但这需要相当多的工作,并且仍然需要猜测原始背景颜色是否正确。

最好回到您使用的绘画工具并在那里解决问题。最快的解决方法应该是将其重新保存为24bpp BMP图像文件,效果因人而异。


虽然这个问题很有趣,但它并没有接近回答问题。原始问题指向一个使用GDI+尝试将半透明图像放入图像列表的类,但这并没有起作用,这就是问题所在,需要弄清楚为什么会出现这种情况。 - Adrian
它解释了出了什么问题以及最佳的解决方法。你当然可以追求不同的解决方案,但这既不是最好的也不是最简单的方式。如果你喜欢自己的方式,那就按照自己的方式去做吧,花上一年时间也没人会阻止你。最好与@Barmak交谈。 - Hans Passant
背景?什么背景?我从未说过任何一个背景是任何特定颜色。如果你说的是列表控件的背景,实际上是白色的。放大镜周围的区域实际上有半透明像素,但它们被显示不正确。我将追求一个不同的解决方案,只是因为你的答案没有回答问题,而没有其他原因。 - Adrian
图像列表完全支持32位带Alpha通道的颜色,尽管它们在底层使用GDI。不要使用任何涉及掩码的接口,并确保源图像具有预乘Alpha通道。(您试图将紫红色用作“透明”颜色的事实表明源图像未经过预乘处理。) - Adrian McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接