views:

598

answers:

2

I have started to toy around with GDI+ in Delphi 2009. Among the things that I wanted to do was to load a PNG resource and apply a Color Conversion to it when drawing it to the Graphics object. I am using the code provided in http://www.bilsen.com/gdiplus/. To do that I just added a new constructor to TGPBitmap that uses the same code found in <www.codeproject.com>/KB/GDI-plus/cgdiplusbitmap.aspx (C++) or <www.masm32.com>/board/index.php?topic=10191.0 (MASM) converted to Delphi.

For reference, the converted code is as follows:

constructor TGPBitmap.Create(const Instance: HInst; const PngName: String; dummy : PngResource_t);
const
    cPngType : string = 'PNG';

var
    hResource : HRSRC;
    imageSize : DWORD;
    pResourceData : Pointer;
    hBuffer : HGLOBAL;
    pBuffer : Pointer;
    pStream : IStream;

begin
    inherited Create;

    hResource := FindResource(Instance, PWideChar(PngName), PWideChar(cPngType));
    if hResource = 0 then Exit;

    imageSize := SizeofResource(Instance, hResource);
    if imageSize = 0 then Exit;

    pResourceData := LockResource(LoadResource(Instance, hResource));
    if pResourceData = nil then Exit;

    hBuffer := GlobalAlloc(GMEM_MOVEABLE, imageSize);

    if hBuffer <> 0 then
    begin
        try
            pBuffer := GlobalLock(hBuffer);

            if pBuffer <> nil then
            begin
                try
                    CopyMemory(pBuffer, pResourceData, imageSize);

                    if CreateStreamOnHGlobal(hBuffer, FALSE, pStream) = S_OK then
                    begin
                        GdipCheck(GdipCreateBitmapFromStream(pStream, FNativeHandle));
                    end;
                finally
                    GlobalUnlock(hBuffer);
                    pStream := nil;
                end;
            end;
        finally
            GlobalFree(hBuffer);
        end;
    end;
end;

The code seems to work fine as I am able to draw the loaded image without any problems. However, if I try to apply a Color Conversion when drawing it, then I get a lovely error: (GDI+ Error) Out of Memory.

If I load the bitmap from a file, or if I create a temporary to which I draw the initial bitmap and then use the temporary, then it works just fine.

What bugs me is that if I take the C++ project from codeproject, add the same PNG as resource and use the same color conversion (in other words, do the exact same thing I am doing in Delphi in the same order and with the same function calls that happen to go to the same DLL), then it works.

The C++ code looks like this:

    const Gdiplus::ColorMatrix cTrMatrix = {
        {
            {1.0, 0.0, 0.0, 0.0, 0.0},
            {0.0, 1.0, 0.0, 0.0, 0.0},
            {0.0, 0.0, 1.0, 0.0, 0.0},
            {0.0, 0.0, 0.0, 0.5, 0.0},
            {0.0, 0.0, 0.0, 0.0, 1.0}
        }
    };

    Gdiplus::ImageAttributes imgAttrs;
    imgAttrs.SetColorMatrix(&cTrMatrix, Gdiplus::ColorMatrixFlagsDefault, Gdiplus::ColorAdjustTypeBitmap);
    graphics.DrawImage(*pBitmap, Gdiplus::Rect(0, 0, pBitmap->m_pBitmap->GetWidth(), pBitmap->m_pBitmap->GetHeight()), 0, 0, pBitmap->m_pBitmap->GetWidth(), pBitmap->m_pBitmap->GetHeight(), Gdiplus::UnitPixel, &imgAttrs);

The Delphi counterpart is:

const
  cTrMatrix: TGPColorMatrix = (
    M: ((1.0, 0.0, 0.0, 0.0, 0.0),
        (0.0, 1.0, 0.0, 0.0, 0.0),
        (0.0, 0.0, 1.0, 0.0, 0.0),
        (0.0, 0.0, 0.0, 0.5, 0.0),
        (0.0, 0.0, 0.0, 0.0, 1.0)));

var
    lImgAttrTr : IGPImageAttributes;
    lBitmap : IGPBitmap;

begin
    // ...
    lImgAttrTr := TGPImageAttributes.Create;
    lImgAttrTr.SetColorMatrix(cTrMatrix, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap);

        aGraphics.DrawImage
        (
            lBitmap,
            TGPRect.Create
            (
                0, 0, lBitmap.Width, lBitmap.Height
            ),
            0, 0, lBitmap.Width, lBitmap.Height,
            UnitPixel,
            lImgAttrTr
        );

I am completely clueless as to what may be causing the issue, and Google has not been of any help. Any ideas, comments and explanations are highly appreciated.

+1  A: 

I can't fully follow your code, so consider this a more general statement: the Out of memory error is often seen with color conversions if not both the source and the target bitmap are 32bbp.

danbystrom
I am comparing both the loaded from PNG resource bitmap and the temporary bitmap, and they only differ in the DPI and Flags. The pixel format is PixelFormat32bppARGB in both. The image is quite small as well (16x16) in case an automatic conversion had to be performed.DPI original: 72.008995056DPI temp: 96Flags original: [ImageFlagsHasAlpha, ImageFlagsHasTranslucent, ImageFlagsColorSpaceRGB, ImageFlagsHasRealDPI, ImageFlagsHasRealPixelSize, ImageFlagsReadOnly]Flags temp: [ImageFlagsHasAlpha]I just checked the Flags in C++ and they match. What else could it be?
Paul
+2  A: 

I had the same problem. The pixel format of the image loaded from resource in fact is NOT PixelFormat32bppARGB. Should be, but seems that it's not. My solution that works is:

// Create local bimap with PixelFormat32bppARGB flag
Gdiplus::Bitmap local_bmp(WIDTH, HEIGHT, PixelFormat32bppARGB);
Gdiplus::Graphics gfx(&local_bmp);

// Redraw the original bitmap on the local without transformation.
gfx.DrawImage(&resource_bmp, 0, 0);

// ... set the matrix and attributes

// Do the transformation on the local_bmp
screenGfx.DrawImage(&local_bmp,Rect(0, 0, WIDTH, HEIGHT), 0, 0, WIDTH, HEIGHT, Gdiplus::UnitPixel, &imgAttrs);
AOI Karasu
That is the same "solution" I am using at the moment, but it still bugs me why it works in C++ but not in Delphi on my end. What is kind of curious is that it fails for you in C++.As commented before, PixelFormat32bppARGB is the pixel format I am getting, yet it still fails.Could you try the test project from codeproject applying a Color Conversion? That is the one that works for me. The trick may be somewhere in that project if it works for you as well.
Paul
I'll try to do some more tests with the code later today. And yes, it fails for me in C++. My code that loads the image is almost identical as in CGdiPlusBitmapResource::Load (from CodeProject).
AOI Karasu