【问题标题】:What would make a static Picture Control display some pixels as transparent (bg color)什么会使静态优化校准显示某些像素为透明(bg 颜色)
【发布时间】:2019-11-05 02:44:43
【问题描述】:

我正在更新问题以删除不相关的详细信息。我得出的结论是,如果存在有效的 alpha 通道,它会支持它,但如果不存在(比如 24 位 PNG w/o alpha 通道),它会使用 F0F0F0 作为透明颜色。

我在对话框中将图像加载到静态“图片控件”(在 Visual Studio 中选择)。我注意到颜色 0xF0F0F0 被显示为“透明”颜色(对话框的背景渗出)。位图通过 CStatic::SetBitmap 加载。

优化校准透明标志设置为 false。

图像是通过 CImage::Load 加载的。

如果我想从通过 SetBitmap 设置的 CStatic 位图中屏蔽颜色,我该怎么做?我没有,但也许这会帮助我找到原因。

下面的最小示例。我用 VS 向导创建了一个对话框项目,并在主对话框中添加了一个图片控件。然后我只添加了以下代码:

//header code added
CPngImage logoImage;
CStatic pictureCtrl;
CBrush bgBrush;
....
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);

//cpp code added
DDX_Control(pDX, IDC_STATICIMG, pictureCtrl);
....
ON_WM_CTLCOLOR()
....
bgBrush.CreateSolidBrush(RGB(0, 255, 0));
logoImage.LoadFromFile(_T("C:\\temp\\logo.png"));
pictureCtrl.SetBitmap(logoImage);
....
HBRUSH CMFCApplication1Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) {
    return bgBrush;
}

这是我正在测试的图像文件。

这是对话框上的样子:

// MFCApplication1Dlg.h : header file
//

#pragma once


// CMFCApplication1Dlg dialog
class CMFCApplication1Dlg : public CDialogEx
{
// Construction
public:
    CMFCApplication1Dlg(CWnd* pParent = nullptr);   // standard constructor
    CPngImage logoImage;
    CStatic pictureCtrl;
    CBrush bgBrush;

// Dialog Data
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_MFCAPPLICATION1_DIALOG };
#endif

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


// Implementation
protected:
    HICON m_hIcon;


    // Generated message map functions
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
};




// MFCApplication1Dlg.cpp : implementation file
//

#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CAboutDlg dialog used for App About

class CAboutDlg : public CDialogEx
{
public:
    CAboutDlg();

// Dialog Data
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_ABOUTBOX };
#endif

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

// Implementation
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CMFCApplication1Dlg dialog



CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_STATICIMG, pictureCtrl);
}

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_WM_CTLCOLOR()
END_MESSAGE_MAP()


// CMFCApplication1Dlg message handlers

BOOL CMFCApplication1Dlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // Add "About..." menu item to system menu.

    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != nullptr)
    {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon

    bgBrush.CreateSolidBrush(RGB(0, 255, 0));
    logoImage.LoadFromFile(_T("C:\\temp\\logo.png"));
    pictureCtrl.SetBitmap(logoImage);

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CMFCApplication1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialogEx::OnSysCommand(nID, lParam);
    }
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CMFCApplication1Dlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}



HBRUSH CMFCApplication1Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) {
    return bgBrush;
}

【问题讨论】:

  • 您确定PNG文件中没有定义透明度吗?即使 PNG 没有 alpha 通道,它也可以定义一个 transparency color key
  • 即使使用 24 bpp BMP 文件(通过 CImage::Load() 加载),我也可以重现该问题。我从头开始创建了一个新位图,并用F0F0F0 部分填充了它。 CStatic 将此颜色显示为透明,类似于您的屏幕截图。
  • 进一步实验表明这与CImage/CPngImage无关。我创建了一个内存 DC,并用0xF0F0F0 的实心画笔绘制了一个椭圆。将内存 DC 中的位图分配给 CStatic。同样的行为,颜色0xF0F0F0 变为透明。
  • My repro code。我的OnCtlColor() 是从你的那里复制粘贴过来的。
  • 顺便说一句,在我的机器上0xF0F0F0 等于GetSysColor(COLOR_BTN_FACE),这是默认的对话框背景颜色。

标签: winapi mfc


【解决方案1】:

在我的系统(Windows 10)上,颜色0xF0F0F0 等于GetSysColor(COLOR_BTNFACE),这是默认的对话框背景颜色。绘制时,静态控件似乎将背景图像中的这种颜色替换为从父窗口的OnCtlColor() 处理程序返回的画笔。这确实具有功能而不是错误的味道(尽管我在参考中找不到任何指定此行为的内容)。

Here is a code snippet 重现此问题,即使不使用 CPngImageCImage,只需在内存 DC 中绘制颜色 0xF0F0F0

由于该行为仅在源图像不包含 Alpha 通道时出现,解决方案是将源图像转换为 32-bpp ARGB 格式。这样我们就不必覆盖CStatic::OnPaint()

// Set the alpha channel of a 32-bpp ARGB image to the given value.
HRESULT SetAlphaChannel( CImage& image, std::uint8_t alpha )
{
    if( ! image.GetBits() || image.GetBPP() != 32 )
        return E_INVALIDARG;

    GdiFlush(); // Make sure GDI has finished all drawing in source image.

    for( int y = 0; y < image.GetHeight(); ++y )
    {
        DWORD* pPix = reinterpret_cast<DWORD*>( image.GetPixelAddress( 0, y ) );
        for( int x = 0; x < image.GetWidth(); ++x, ++pPix )
        {
            *pPix = ( *pPix & 0xFFFFFF ) | ( alpha << 24 );
        }
    }

    return S_OK;        
}

// Load an image and convert to 32-bpp ARGB format, if necessary.
HRESULT LoadImageAndConvertToARGB32( CImage& image, LPCWSTR pFilePath )
{
    CImage tempImage;
    HRESULT hr = tempImage.Load( pFilePath );
    if( FAILED( hr ) )
        return hr;

    if( tempImage.GetBPP() == 32 )  // Assume 32 bpp image already has an alpha channel
    {
        image.Attach( tempImage.Detach() );
        return S_OK;
    }

    if( ! image.Create( tempImage.GetWidth(), tempImage.GetHeight(), 32, CImage::createAlphaChannel ) )
        return E_FAIL;

    HDC const imageDC = image.GetDC();
    BOOL const bitBltSuccess = tempImage.BitBlt( imageDC, 0, 0, SRCCOPY );
    image.ReleaseDC();

    if( ! bitBltSuccess )
        return E_FAIL;

    SetAlphaChannel( image, 255 );  // set alpha to opaque

    return S_OK;
}

用法

将对CImage::Load()的调用替换为:

LoadImageAndConvertToARGB32( m_image, filePath );

注意事项

当您将具有非零 Alpha 通道的 32-bpp 位图分配给控件时,还有另一个静态控件讨厌¹(正如您在遵循我的解决方案时所做的那样)。在这种情况下,静态控件将复制您传入的位图,而负责销毁此副本!

必读的 OldNewThing

When will the static control automatically delete the image loaded into it, and when is it the responsibility of the application?

¹) 更准确地说:使用第 6 版通用控件时,如今几乎所有应用程序都这样做。

【讨论】:

  • 很好的答案。我在其他 cmets 中确认 32 位文件没问题。我确实怀疑它与 syscolor 有关——尽管即使覆盖 DC 的背景或前景色也没有效果。我将此标记为“正确”答案,因为它允许将 24 位 PNG 图像与非子类 CStatic 一起使用。
  • 顺便说一下,你有一个额外的下划线:它是 COLOR_BTNFACE。
  • @Dan 已修复,谢谢!鉴于静态控件的所有怪癖(请参阅添加的 OldNewThing 链接),我实际上更愿意创建一个从普通 CWnd 派生的自定义控件以显示位图。尽管在某些情况下可能没有选择,例如。 G。维护现有代码时。
【解决方案2】:

这不是 PNG 问题,而是颜色深度问题。

根据你的代码,我通过格式转换工具将8位PNG图片转换为8位BMP图片,图片仍然显示背景色。

所以我将 8 位 PNG 图片保存为 32 位 png 图片,这样就可以了。

这是为什么呢?

答案:GIF 文件有两部分:颜色表和图像像素数据。颜色表是该图像中使用的颜色列表(8 位 GIF 在颜色表中最多可以有 2^8 = 256 种颜色,但 4 位 GIF 只能有 2^4 = 16 种颜色) ,并且为每种颜色分配一个编号。图像像素数据用于图像本身,每个像素都分配有一个数字,该数字指向其在颜色表中的颜色。例如,如果颜色表中的颜色#10 为红色(#FF0000),则图像中编号为 10 的任何像素都将显示为红色。颜色表中的颜色会根据图像本身因 GIF 文件而异;颜色 #10 并不总是红色。颜色表是渲染该图像所需的最多 256 种颜色的集合。

当我们添加索引透明度时,除了颜色数据(即 RGB 值)之外,颜色表中的每种颜色都会被指定一个透明度:

零(布尔代数中的o = False)表示不显示此颜色,或 一(布尔代数中的 1 = True)表示显示此颜色。 没有中间混浊;颜色要么显示,要么不显示。最终结果是不会显示具有索引透明度颜色的像素,并且该像素后面的背景中的任何内容都会显示出来。例如,如果颜色 #10 为红色 (#FF0000) 并指定为透明(索引透明度 = 0),则颜色 #10 的任何像素都不会显示,背景会透出来。

索引透明度中可以有多种透明颜色,因为颜色表中的每种颜色都有不透明 (1) 或透明 (0) 的指定。大多数图形程序假定画布颜色(通常为白色,但它可以是任何颜色)是默认透明颜色,但您可以将任何颜色(或任意数量的颜色)指定为透明或不透明。

这种类型的透明度在 GIF 和 PNG8 文件中很常见并且很容易识别,因为没有褪色,没有部分透明的像素,并且边缘通常被描述为“硬”或“像素化”。

【讨论】:

  • 我仍在努力阅读您的答案,但作为记录,颜色为 F0F0F0 的 24 位 BMP 的行为与我的 8 位 PNG 完全相同。
  • 我可以确认 32 位 PNG 按预期工作。 24 位的则没有。我看不出如何认为 24​​ 位 PNG 的位数太少而无法正确显示。文件没有 Alpha 通道并不是用透明度替换任意颜色的借口!
【解决方案3】:

恐怕以下是我将得到的最佳答案。 MFC/Win32 做了一些有趣的事情,目前还不清楚为什么。但是如果手动绘制,图像显示没有问题。

我创建了一个自定义(非常粗糙的)CStatic 类并在下面添加了 OnPaint。在屏幕截图中,第一个是使用我的类,第二个是使用常规 CStatic。两者都使用相同的图像(24 位 BMP)。

struct AFX_CTLCOLOR {
    HWND hWnd;
    HDC hDC;
    UINT nCtlType;
};

void CMyStatic::OnPaint() {
    CPaintDC dc(this); // device context for painting
    CRect r;
    GetClientRect(r);

    WPARAM w = (WPARAM)&dc;
    AFX_CTLCOLOR ctl;
    ctl.hWnd = this->GetSafeHwnd();
    ctl.nCtlType = CTLCOLOR_STATIC;
    ctl.hDC = dc.GetSafeHdc();
    HBRUSH bg=(HBRUSH)::SendMessage(GetParent()->GetSafeHwnd(), WM_CTLCOLORSTATIC, w, (LPARAM)&ctl);
    CBrush cbg;
    cbg.Attach(bg);
    dc.FillRect(r, &cbg);
    cbg.Detach();

    HBITMAP hbmp=GetBitmap();
    BITMAP binfo;

    CBitmap* cbmp = CBitmap::FromHandle(hbmp);
    cbmp->GetBitmap(&binfo);
    CDC dcMem;
    dcMem.CreateCompatibleDC(&dc);
    dcMem.SelectObject(cbmp);

    dc.BitBlt((r.Width()-binfo.bmWidth)/2, (r.Height() - binfo.bmHeight) / 2, binfo.bmWidth, binfo.bmHeight, &dcMem, 0, 0, SRCCOPY);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-02
    • 1970-01-01
    • 2021-08-19
    • 2021-01-21
    • 1970-01-01
    • 2013-11-27
    • 1970-01-01
    • 2012-09-30
    相关资源
    最近更新 更多