使用GDI +加载JPG和PNG资源的CGdiPlusBitmap类

翻译来源:https://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI

作者:Joe Woodbury

GDI +无法加载存储为资源的JPG或PNG图像,使用此类就可以加载资源了。

同样效果的文章:GDI+从资源文件中加载图片

下载demo,S2015打开stdafx.h请加上


#ifndef WINVER				// Allow use of features specific to Windows 95 and Windows NT 4 or later.
#define WINVER 0x0501		// Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINNT		// Allow use of features specific to Windows NT 4 or later.
#define _WIN32_WINNT 0x0501		// Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif
#pragma warning(disable:4996)


一个类,以方便从资源使用GDI +加载JPG和PNG文件
  • 下载演示项目 - 121 Kb
  • 下载源 - 1 Kb

介绍

最近我需要显示一些JPG和PNG文件。我有一个旧版的LeadTools和这两种格式的开源库,但希望我的可执行文件尽可能的小。所以我决定给GDI +一个尝试。我很快发现GDI +设计不好,非常古怪,但它对我的目的很好,直到我发现我的恐惧,GDI +无法加载存储为资源的JPG或PNG图像!

像我相信,其他开发人员面临这个问题,我不相信文档,试图Bitmap::FromResource无济于事。在浏览可用的Bitmap方法的同时,我碰到过Bitmap::FromStream

经过一些测试和几个错误,主要是由于可怕的GDI +文档,我想出了工作代码。休息一晚后,我决定将代码封装在一个简单的类中,以确保内存被释放。其结果是两类:CGdiPlusBitmapCGdiPlusBitmapResource

Gotcha

在讨论代码本身之前,有一个需要解决的GDI +的警告。使用JPG,某些TIFF和其他格式,原始图像信息必须始终可用。换句话说,如果您打开位图Bitmap::FromFile,则在图像打开时您不能删除或以其他方式更改该文件。同样的限制适用于CGdiPlusBitmapResource(我的测试发现,PNG和BMP文件似乎没有遵循这一概括,虽然我不知道这是标准行为还是与我的文件只是一个侥幸。)

GDI +初始化

在进行任何GDI +调用之前,需要初始化GDI +。我建议将以下数据成员添加到派生自CWinApp

ULONG_PTR m_gdiplusToken;

InitInstance(),添加以下调用:

Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

ExitInstance(),添加以下内容:

Gdiplus::GdiplusShutdown(m_gdiplusToken);

我创建了两个类,基类是非常简单的封装 Bitmap和封装全局内存的派生类。我想,如果我有耐心和愿望,我可以延长这个类,但我没有必要这样做。(如果你好奇为什么我不是简单地从ATL类派生出来的CImage,那是因为代码被用在一个没有使用MFC或ATL的程序中,但代码很简单,很容易被修改为使用CImage作为基类。)

我不会干扰CGdiPlusBitmap 类,除非说它有一个公共的数据成员Bitmap* m_pBitmap(在类中,我使用Gdiplus命名空间来开发GDI +对象,以防开发人员不想声明using namespace Gdiplus;。)

CGdiPlusBitmapResource类有几个构造函数和几个重载Load功能。重载功能简单地允许像我一样的惰性程序员不需要键入MAKEINTRESOURCELoad函数将资源名称和类型作为字符串,并且是类的关键。本代码全部如下:

inline
bool CGdiPlusBitmapResource::Load(LPCTSTR pName, LPCTSTR pType, 
                                  HMODULE hInst)
{
    Empty();

    HRSRC hResource = ::FindResource(hInst, pName, pType);
    if (!hResource)
        return false;
    
    DWORD imageSize = ::SizeofResource(hInst, hResource);
    if (!imageSize)
        return false;

    const void* pResourceData = ::LockResource(::LoadResource(hInst, 
                                              hResource));
    if (!pResourceData)
        return false;

    m_hBuffer  = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
    if (m_hBuffer)
    {
        void* pBuffer = ::GlobalLock(m_hBuffer);
        if (pBuffer)
        {
            CopyMemory(pBuffer, pResourceData, imageSize);

            IStream* pStream = NULL;
            if (::CreateStreamOnHGlobal(m_hBuffer, FALSE, &pStream) == S_OK)
            {
                m_pBitmap = Gdiplus::Bitmap::FromStream(pStream);
                pStream->Release();
        if (m_pBitmap)
        { 
          if (m_pBitmap->GetLastStatus() == Gdiplus::Ok)
            return true;

          delete m_pBitmap;
          m_pBitmap = NULL;
        }
            }
            m_pBitmap = NULL;
            ::GlobalUnlock(m_hBuffer);
        }
        ::GlobalFree(m_hBuffer);
        m_hBuffer = NULL;
    }
    return false;
}

我发现代码自作聪明,虽然知道返回值的::LoadResource是一个HGLOBAL可能会发现明显的双重复制使用CopyMemory混乱。简而言之,CreateStreamOnHGlobal 需要使用标志HGLOBAL分配句柄。通过GlobalAlloc使用 GMEM_MOVEABLE标志

演示

该演示是具有ANSI和UNICODE构建的Visual Studio .NET 2003项目。它允许您加载重新采样的JPG或PNG资源(好奇的是,我在瓦胡岛,夏威夷拍摄了两张照片,同时拍摄了多媒体产品的内容,一个是莱湾,另一个是从威基基观看的夕阳。 )

CGdiPlusBitmapResource* pBitmap = new CGdiPlusBitmapResource;
	if (pBitmap->Load(_T("IDB_BAY"), _T("PNG")))
//如果资源ID没有双引号,直接Load(IDB_BAY,_T("PNG"));这里我找了很久才知道原因在这里
	{
		CClientDC dc(this);
		Gdiplus::Graphics graphics(dc);
		graphics.DrawImage(*pBitmap, 0, 0);
	}
	else
	{
		AfxMessageBox("Failure loading image");
	}
	delete pBitmap;

GDI +免责声明

我不是一个GDI +专家,也不是一个大粉丝,即使我发现GDI +偶尔非常有用。请不要问我有关的问题。

为什么不是图片?

我被问到为什么我没有使用IPicture答案是三重的 首先,IPicture不支持PNG图像。其次,IPicture 几乎只是一个图像加载程序,比标准的GDI位图调用功能更多。第三,IPicture立即对图像数据进行解码。JPG和GIF图像将使用比这个类更多的内存。

更新

2004年4月22日

CreateStreamOnHGlobal现在使用FALSE第二个参数,因为Bitmap要求保存至少JPG图像的内存,我决定在谨慎的一边。有趣的是,我的测试显示这个标志经常被忽略,但是我收到的报告说,这并不总是这样,技术上是错误的。

另外,在修复这个bug的时候,我意识到我没有在失败时清除全局内存。这导致代码正在重新排列。

2004年6月15日

如果Gdiplus::Bitmap::FromStream失败,我补充说,我认为是多余的,但更正确的代码在处理m_pBitmap(不幸的是,关于是否在失败时始终返回NULL的情况下,文档中没有提到这个主题。)

2004年9月3日

添加了一个GDI +初始化部分。

修复了示例应用程序中图像负载故障时的潜在内存泄漏。



你可能感兴趣的:(使用GDI +加载JPG和PNG资源的CGdiPlusBitmap类)