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

时间:2019-06-22 16:22:23

标签: winapi mfc

我正在更新问题,以删除无关的细节。我得出的结论是,如果存在有效的Alpha通道,它将认可它,但是如果不存在(例如,一个不带Alpha通道的24位PNG),它将使用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;
}

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

https://i.imgur.com/OA9CCh8.png

这是对话框上的样子:

enter image description here

// 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;
}

3 个答案:

答案 0 :(得分:1)

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

Here is a code snippet甚至不使用CPngImageCImage来重现此问题,只需绘制颜色为0xF0F0F0的内存DC。

由于该行为仅在源图像不包含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 );

注释

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

必读OldNewThing

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

¹ )更准确地说:使用通用控件的版本6时,如今几乎所有应用程序都在使用它。

答案 1 :(得分:0)

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

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

因此我将8位PNG图片保存为32位png图片,没关系。 1

2

为什么?

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

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

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

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

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

3

答案 2 :(得分:0)

恐怕以下是我要获得的最佳答案。 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);
}

enter image description here