Сообщений 5    Оценка 396 [+2/-0]         Оценить  
Система Orphus

Как вывести на экран картинку в JPEG/GIF/PNG/др. формате?

7 способов как это сделать

Автор: Павел Блудов

Версия текста: 1.0b

Демонстрационное приложение (WTL) DrawImg (50kb)


Приложение DrawImg

Сегодня практически все программы используют различные картинки в качестве элементов интерфейса. Даже существует API функция ::LoadImage(), умеющая загружать файлы в формате bmp, ico и cur. Этого достаточно для панелей управления и диалогов. Но если размер картинки превышает 100x100 пикселов и их нужно несколько, файлы формата bmp использовать не удобно. Хочется что-то вроде jpg или gif.

Тут ::LoadImage() нам уже не помошник. Придется использовать специальные библиотеки. Наибольшей популярностью пользуются:

Все они хорошо документированны и снабжены примерами. Подробное рассмотрение этих библиотек займет слишком много времени и выходит за рамки этой статьи. Давайте лучше уделим внимание некоторым специальным API, предназначенным для работы с файлами изображений.

Способ 1 (OleLoadPicture)

Самый "официальный" способ. Появился вместе с OLE32 и работает до сих пор. Функции OleLoadPicture(Ex) и OleLoadPicturePath умеют загружать картинки в формате BMP, GIF, JPEG, ICO, WMF, и EMF:

#include <olectl.h>
    
    HRESULT Load(LPCTSTR szFile)
    {
        CComPtr<IStream> pStream;
        
        // Load the file to a memory stream
        HRESULT hr = FileToStream(szFile, &pStream);

        if (SUCCEEDED(hr))
        {
            // Decode the picture
            hr = ::OleLoadPicture(
                    pStream,            // [in] Pointer to the stream that contains picture's data
                    0,                    // [in] Number of bytes read from the stream (0 == entire)
                    true,                // [in] Loose original format if true
                    IID_IPicture,        // [in] Requested interface
                    (void**)&m_pPicture // [out] IPictire object on success
                    );
        }

        return hr;
    }

    HRESULT DrawImg(HDC hdc, const RECT& rcBounds)
    {
        if (m_pPicture)
        {
            // Get the width and the height of the picture
            long hmWidth = 0, hmHeight = 0;
            m_pPicture->get_Width(&hmWidth);
            m_pPicture->get_Height(&hmHeight);

            // Convert himetric to pixels
            int nWidth    = MulDiv(hmWidth, ::GetDeviceCaps(hdc, LOGPIXELSX), HIMETRIC_INCH);
            int nHeight    = MulDiv(hmHeight, ::GetDeviceCaps(hdc, LOGPIXELSY), HIMETRIC_INCH);

            // Display the picture using IPicture::Render
            return m_pPicture->Render(
                hdc,                            // [in] Handle of device context on which to render the image
                rcBounds.left,                    // [in] Horizontal position of image in hdc
                rcBounds.top,                    // [in] Vertical position of image in hdc
                rcBounds.right - rcBounds.left,    // [in] Horizontal dimension of destination rectangle
                rcBounds.bottom - rcBounds.top, // [in] Vertical dimension of destination rectangle
                0,                                // [in] Horizontal offset in source picture
                hmHeight,                        // [in] Vertical offset in source picture
                hmWidth,                        // [in] Amount to copy horizontally in source picture
                -hmHeight,                        // [in] Amount to copy vertically in source picture
                &rcBounds                        // [in, optional] Pointer to position of destination for a metafile hdc
                );
        }

        return E_UNEXPECTED;
    }

Достоинства: правильно работает с прозрачными картинками.

Недостатки: не поддерживает анимированный GIF (см. также CPicturEx). Не поддерживает PNG.

Способ 2 (GDI+)

Недостаток ::LoadImage() с лихвой исправили в GDI+. Объект Gdiplus::Image умеет загружать картинки в формате BMP, GIF, JPEG, PNG, TIFF, EXIF, WMF, и EMF:

#include <gdiplus.h>

    HRESULT Load(LPCTSTR szFile)
    {
        USES_CONVERSION;
        // Create new Gdiplus::Image object
        m_pImage = new Gdiplus::Image(T2CW(szFile));
        ATLASSERT(m_pImage);

        // Check for success
        if (Gdiplus::Ok == m_pImage->GetLastStatus())
            return S_OK;

        // Cleanup on failure
        Destroy();
        return E_FAIL;
    }

    HRESULT DrawImg(HDC hdc, RECT& rcBounds)
    {
        if (m_pImage)
        {
            // Create Gdiplus::Graphics object from HDC
            Gdiplus::Graphics graphics(hdc);
            // Create Gdiplus::Rect object from RECT
            Gdiplus::Rect rc(rcBounds.left, rcBounds.top, rcBounds.right, rcBounds.bottom);

            // Draw the image
            return Gdiplus::Ok == graphics.DrawImage(
                m_pImage,                        // [in] Gdiplus::Image object
                rc                                // [in] Position and dimensions
                ) ? S_OK : E_FAIL;
        }

        return E_UNEXPECTED;
    }

Достоинства: понимает множество форматов, в том числе анимированный GIF, правильно работает с прозрачными картинками.

Недостатки: На сегодняшний момент реализован только в WindowsXP. Хотя простое копирование gdiplus.dll в system32 делает ее доступной, как минимум, в Windows2000. Скорее всего, в обозримом будущем ожидаются версии и для Win9x.

Способ 3 (IImgCtx)

Не так давно Майкрософт предоставила заголовочные и библиотечные файлы к объекту ImgCtx, появившемуся еще в Internet Explorer 4.0. Он умеет заргужать картинки в формате BMP, GIF, JPEG, ICO, WMF, EMF, PNG, XBM, ICO, TIFF и, возможно, некоторых других:

#include <IImgCtx.h>

    HRESULT Load(LPCTSTR szFile)
    {
        // Create IImgCtx object
        HRESULT hr = ::CoCreateInstance(CLSID_IImgCtx, NULL, CLSCTX_ALL, IID_IImgCtx, (void**)&m_pImage);
        if (SUCCEEDED(hr))
        {
            // Load URL
            USES_CONVERSION;
            hr = m_pImage->Load(
                    T2COLE(szFile),            // [in] URL
                    0                        // [in] Flags and preffered color format
                    );
        }

        return hr;
    }

    HRESULT DrawImg(HDC hdc, RECT& rcBounds)
    {
        if (m_pImage)
        {
            // Check download state
            DWORD dwState = 0;
            HRESULT hr = m_pImage->GetStateInfo(&dwState, NULL, true);
            if (SUCCEEDED(hr))
            {
                if (IMGLOAD_LOADING & dwState)
                {
                    // Still loading - wait 50 msec and request again
                    ::DrawText(hdc, _T("Loading, please wait..."), -1, &rcBounds, DT_SINGLELINE);
                    ::Sleep(50);
                    Invalidate(false);

                    hr = S_FALSE;
                }
                else if (IMGLOAD_COMPLETE & dwState)
                {
                    // Download successfully complete
                    hr = m_pImage->Draw(
                        hdc,                // [in] Handle of device context on which to render the image
                        &rcBounds            // [in] Position and dimensions
                        );
                }
                else
                {
                    // Download failed
                    hr = E_UNEXPECTED;
                }
            }
            return hr;
        }

        return E_UNEXPECTED;
    }

Достоинства: правильно работает с прозрачными и анимированными картинками. Понимает URL (даже res:// и sysimage://).

Недостатки: не поддерживает загрузку из IStream. Не умеет загружать файлы синхронно.

ПРИМЕЧАНИЕ
Форматов, распознаваемых этим объектом, может быть меньше, например, если при установке IE4 пользователь отключил поддержку PNG файлов.

Способ 4 (DirectXTransform)

Не смотря на название, эта технология не имеет ничего общего с DirectX. Зато является частью Internet Explorer, внутри которого даже имется набор простеньких классов, реализующих IDirectDraw для нужд DirectXTransform. Этот способ поддерживает тот же набор форматов, что и предыдущий, более того, для этого используется один и тот же код. Разве что синхронно и на выходе получается IDXSurface объект.

    #include <dxtrans.h>

    HRESULT DrawImg(HDC hdc, const RECT& rcBounds)
    {
        if (m_pDCLock)
        {
            HDC hdcImage = m_pDCLock->GetDC();

            // Get the bitmap
            HGDIOBJ hObj = ::GetCurrentObject(hdcImage, OBJ_BITMAP);
            BITMAP bm = {0};

            // Get the size of the bitmap
            if (hObj && ::GetObject(hObj, sizeof(BITMAP), &bm))
            {
                // Draw the image
                return ::StretchBlt(
                    hdc,
                    rcBounds.left, rcBounds.top,
                    rcBounds.right - rcBounds.left,
                    rcBounds.bottom - rcBounds.top,
                    hdcImage,
                    0, 0,
                    bm.bmWidth, bm.bmHeight,
                    SRCCOPY
                    ) ? S_OK : E_FAIL;
            }
        }

        return E_UNEXPECTED;
    }

    HRESULT Load(LPCTSTR szFile)
    {
        CComPtr<IDXTransformFactory> pTransFact;
        CComPtr<IDXSurfaceFactory> pSurfFact;

        // Create the Transform Factory.
        HRESULT hr = ::CoCreateInstance(CLSID_DXTransformFactory, NULL,
            CLSCTX_INPROC, IID_IDXTransformFactory,    (void **)&pTransFact);

        if (SUCCEEDED(hr))
            hr = pTransFact->QueryService(SID_SDXSurfaceFactory,
                    IID_IDXSurfaceFactory, (void **)&pSurfFact);

        if (SUCCEEDED(hr))
        {
            CComBSTR bstrFile(szFile);
            CComPtr<IDXSurface> pDXSurf;

            // Load DX surface.
            hr = pSurfFact->LoadImage(bstrFile, NULL, NULL,
                NULL, IID_IDXSurface, (void**)&pDXSurf);

            if (SUCCEEDED(hr))
            {
                // Get IDXDCLock object 
                hr = pDXSurf->LockSurfaceDC(NULL, INFINITE, DXLOCKF_READ, &m_pDCLock);
            }
        }
        return hr;
    }

Достоинства: Прост в использовании. Поддерживает загрузку из IStream.

Недостатки: Медленный и ресурсоемкий. Это связянно с тем, что сначала для картинки создается обертка в виде IDirectDrawSurface, а затем еще одна для IDXSurface, которые нам совершенно не нужны.

Способ 5 (Фильтры импорта)

Многие программы (например PaintBrush или WinWord) при инсталляции кладут в каталог %ProgramFiles%\Common Files\Microsoft Shared\Grphflt некоторое количество файлов, предназначенных для чтения файлов картинок. Способ не документированный и сильно устаревший. Полный список установленных в системе фильтров находится в реестре по адресу SOFTWARE\\Microsoft\\Shared Tools\\Graphics Filters\\Import

Я не буду рассматривать этот способ подробно, поскольку он сильно устарел и очень неудобен. Тем не менее, в приложении DrawImg этот способ реализован наравне с другими.

Способ 6 (Снова Фильтры импорта)

Майкрософт Офис, начиная с версии 8.0 (97) использует новый API с теми же фильтрами.

    HRESULT Load(LPCTSTR szFile)
    {
        HMODULE hModule = g_pMapExtToFilter->LoadFilter(szFile);

        if (NULL == hModule)
            return E_FAIL;

        struct NameStruct
        {
            DWORD    dwHead[2];
            char    szName[MAX_PATH];
            DWORD    dwTail[2];
        };

        typedef DWORD (__stdcall *GetFilterInfo_t)(DWORD dwVersion, DWORD dwReserved, HGLOBAL *phFilterData, DWORD dwReserved2);
        typedef DWORD (__stdcall *SetFilterPref_t)(HGLOBAL hFilterData, LPCSTR szOption, LPCSTR szValue, DWORD dwReserved2, DWORD dwReserved1);
        typedef DWORD (__stdcall *ImportGr_t)(DWORD dwReserved, NameStruct *pFile, ImgInfo *pInfo, HGLOBAL hFilterData);

        GetFilterInfo_t pGetFilterInfo = (GetFilterInfo_t)::GetProcAddress(hModule, "GetFilterInfo");
        SetFilterPref_t pSetFilterPref = (SetFilterPref_t)::GetProcAddress(hModule, "SetFilterPref");

        ImportGr_t pImportGr = (ImportGr_t)::GetProcAddress(hModule, "ImportGr");
        if (NULL == pImportGr)
            pImportGr = (ImportGr_t)::GetProcAddress(hModule, "ImportGR");

        if (pImportGr)
        {
            NameStruct name = {0};
            HGLOBAL hFilterData = NULL;

            if (pGetFilterInfo)
            {
                DWORD dwVer = pGetFilterInfo(2, 0, &hFilterData, 0x00170000);
                ATLASSERT(2 == dwVer);

                if (2 != dwVer)
                {
                    ::FreeLibrary(hModule);
                    return E_UNEXPECTED;
                }
            }

            //    PB 01/26/2001 Turn off dialogs
            if (pSetFilterPref)
            {
                pSetFilterPref(hFilterData, "ShowProgressDialog", "No", 2, 1);
                pSetFilterPref(hFilterData, "ShowOptionsDialog", "No", 2, 1);
            }

            USES_CONVERSION;
            ::lstrcpynA(name.szName, T2CA(szFile), MAX_PATH);

            DWORD dwRet = pImportGr(0, &name, &m_Image, hFilterData);
            
            if (hFilterData)
                ::GlobalFree(hFilterData);

            if (0 != dwRet || NULL == m_Image.hObj)
            {
                ::FreeLibrary(hModule);
                return E_FAIL;
            }

            if (OBJ_METAFILE != ::GetObjectType(m_Image.hObj))
            {
                HGLOBAL hObj = (HGLOBAL)m_Image.hObj;
                LPBYTE pObj = (LPBYTE)::GlobalLock(hObj);

                m_Image.hObj = ::SetMetaFileBitsEx(::GlobalSize(hObj), pObj);

                ::GlobalUnlock(hObj);
                ::GlobalFree(hObj);
            }

            if (NULL == m_Image.hObj)
            {
                ::FreeLibrary(hModule);
                return E_FAIL;
            }

            return S_OK;
        }
        
        ::FreeLibrary(hModule);
        return E_UNEXPECTED;
    }

    HRESULT DrawImg(HDC hdc, const RECT& rcBounds)
    {
        if (m_Image.hObj)
        {
            ::SetMapMode(hdc, MM_ANISOTROPIC);
            ::SetViewportExtEx(hdc,
                rcBounds.right - rcBounds.left,
                rcBounds.bottom - rcBounds.top,
                NULL);
            ::PlayMetaFile(hdc, m_Image.hObj);
            
            return S_OK;
        }

        return E_UNEXPECTED;
    }

Достоинства: понимает очень редкие форматы. Например wpg или cdr

Недостатки: Нет никакой гарантии, что на компьютере пользователя будет установлен нужный фильтр.

Способ 7 (Direct3D)

Direct3D версии 8.0 и выше умеет загружать картинки в формате BMP, JPEG, PNG:

#include <d3dx8.h>
HRESULT hr = ::D3DXCreateTextureFromFile(m_pD3DDevice, szFile, &ppTexture);

Достоинства: если вы разрабатываете 3D-приложение, то это наиболее удобный способ создания текстур (D3DXCreateTextureFromFile автоматически создает необходимое количество MipMap уровней).

Недостатки: если вы не разрабатываете 3D-приложение, то этот способ крайне неудобен, так как предназначен для работы с 3D объектами. На входе нужен IDirect3DDevice8 объект, а на выходе получаем IDirect3DTexture8, который очень не просто вывести в HDC.

Не реализован в демонстрационном приложении.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 5    Оценка 396 [+2/-0]         Оценить