什么会使静态优化校准将某些像素显示为透明(背景颜色)

What would make a static Picture Control display some pixels as transparent (bg color)

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

我在对话框中将图像加载到静态 "picture control"(在 visual studio 中选择)。我注意到颜色 0xF0F0F0 显示为 "transparent" 颜色(对话框的背景渗出)。位图通过 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的问题,是颜色深度的问题。

根据你的代码,我用格式转换工具把8位PNG图片转成了8位BMP图片,图片还是显示背景色。

于是我把8位的PNG图片存为32位的PNG图片,就可以了。

这是为什么?

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

当我们添加索引透明度时,颜色 table 中的每种颜色除了其颜色数据(即 RGB 值)外,还被赋予透明度名称:

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

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

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

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

我创建了一个自定义(非常粗糙)的 CStatic class 并在下面添加了 OnPaint。在屏幕截图中,第一个是使用我的 class,第二个是使用常规 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);
}

在我的系统 (Windows 10) 上,颜色 0xF0F0F0 等于 GetSysColor(COLOR_BTNFACE),这是默认的对话框背景颜色。绘制时,静态控件似乎用父 window 的 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 );

备注:

当您将带有 non-zero alpha 通道的 32-bpp 位图分配给控件时,还有另一个静态控件问题¹(就像您在遵循我的解决方案时所做的那样)。在这种情况下,静态控件将复制您传入的位图,而负责销毁该副本!

必读旧新事物:

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

¹) 更准确地说:当使用版本 6 的公共控件时,现在几乎所有应用程序都这样做。