Tuesday, April 14, 2009

Is IImage caching a good idea?

On my previous post I described a very simple solution to improve the performance of IImage::Draw - caching the converted image. This does speed up the process of drawing the alpha blended picture but has one drawback: you store the image twice. From what I understood from the IImagingFactory::CreateImageFromBuffer online documentation, the supplied buffer is not immediately used and must be there throughout the IImage object lifetime. So when you cache the image you are essentially storing two representations of the same thing (the original encoding  and the decoded version). While this may not be a big issue for small images, this becomes a serious problem when using lots of images and / or larger images. So what can we do about this?

The idea is to have a single 32 bpp ARGB HBITMAP in memory per image. As I showed before, using the shell API to load the images is a bad idea because it shrinks them to 16 bpp. Is there any other alternative to this? We can try to create a 32 bpp bitmap in memory and then use the IImage object to paint it. After releasing the IImage object, we should get a properly-formatted 32 bpp ARGB bitmap. On my next post I will illustrate this technique that was validated by master guru Alex Feinman

12 comments:

OzzY said...

Well, I used to do it this way, it's much faster than IImage::Draw, but still quite slow to me. If you know any faster way or at least simple way to load PNG with alpha, without using IMAGING API, please let me know...

//Static handles for image cache
static HDC hDCI = NULL;
static HBITMAP hImage = NULL;
static RECT rcImage = {0, 0, LINKS_WIDTH, LINKS_HEIGHT};

//Function to load png a place it into DC containing 32-bit DIB
void InitializeImage()
{
IImagingFactory *pFactory = NULL;
IImage *pImage = NULL;
BITMAPINFO bInfo;

if(CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&pFactory) == S_OK)
{
if(pFactory->CreateImageFromFile(L"\\Program Files\\OzzY\\Links.png", &pImage) == S_OK)
{
ZeroMemory(&bInfo.bmiColors, 4);
bInfo.bmiHeader.biClrImportant = bInfo.bmiHeader.biClrUsed = 0;
bInfo.bmiHeader.biBitCount = 32;
bInfo.bmiHeader.biCompression = BI_RGB;
bInfo.bmiHeader.biXPelsPerMeter = bInfo.bmiHeader.biYPelsPerMeter = 3780;
bInfo.bmiHeader.biSizeImage = 0;
bInfo.bmiHeader.biHeight = LINKS_HEIGHT;
bInfo.bmiHeader.biWidth = LINKS_WIDTH;
bInfo.bmiHeader.biPlanes = 1;
bInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

hDCI = CreateCompatibleDC(NULL);
hImage = CreateDIBSection(hDCI, &bInfo, DIB_RGB_COLORS, NULL, NULL, NULL);
SelectObject(hDCI, hImage);
pImage->Draw(hDCI, &rcImage, NULL);
pImage->Release();
}
pFactory->Release();
}
}

//The way I do the painting, on today screen
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
BLENDFUNCTION bfAlpha = {AC_SRC_OVER, 0, 0xFF, AC_SRC_ALPHA};

SetBkMode(hDC, TRANSPARENT);
AlphaBlend(hDC, 0, 0, LINKS_WIDTH, LINKS_HEIGHT, hDCI, 0, 0, LINKS_WIDTH, LINKS_HEIGHT, bfAlpha);
EndPaint(hwnd, &ps);

João Paulo Figueira said...

This is pretty much what I wanted to do as there seems to be no other way (using the Windows Mobile API, that is) to load a 32 bpp ARGB PNG file. Have you tried creating a simple 32 bpp bitmap (not a DIB section) and then painting the bitmap there?

I think that if you need more speed you may have to go down and use Direct Draw and even maybe paint the pixels yourself...

OzzY said...

Thanks for reply!

I tried using standard 32-bit bitmap now, using CreateBitmap which I haven't used before because of a line in reference (http://msdn.microsoft.com/en-us/library/aa929704.aspx), which says "lpvBits - If this parameter is NULL, the new bitmap is undefined.", a I have thought it mean that without initializing the bitmap with data from this pointer, the bitmap will not have a data structure.

Of course, it's rather different, and bitmap seems OK and I can use it normally. It saved me couple of LOCs (thanks!), but speed have not changed.

I never used DDraw nor raw framebuffer operations, do you think it will be faster than simple BitBlt? Well, maybe I'll give it a try, someday... Do you know any good tutorials?

ARNavPoch said...

Hi,

The ImageList API is alpha aware on mobile as on Win32.

See my CP blog at http://www.codeproject.com/script/Membership/View.aspx?mid=161329

cheers,
AR

João Paulo Figueira said...

Thanks for pointing this out, but does this perform per-pixel alpha blending?

ARNavPoch said...

I think so if the imagelist is created with ILC_COLOR32. That's why I first create it and then add a bitmap loaded from a png resource by ::SHLoadImageResource().

See the code at http://www.codeproject.com/Members/Alain-Rist#_comments

João Paulo Figueira said...

Hi Alain,

I also looked at the image list as a possibility, but when I looked at the Draw flags, none showed up as a likely candidate for per-pixel alpha blending. There are options to blend the whole bitmap, but none seemed appropriate for per-pixel. Well, I will just have to use your code and give it a go! ;-)

João Paulo Figueira said...

There's another issue that I came up with: SHLoadImageResource squashed the PNG file down to 16 bpp. Did you ever manage to load a 32 bpp PNG file and keep the 32 bits per pixel?

ARNavPoch said...

If the png is of type GIF it is loaded as 32 bits. I will check it anyway.

João Paulo Figueira said...

On VS 2008 I put the PNG files on the resource as PNG, not GIF. That might be the reason why I was seeing that behavior...

ARNavPoch said...

Apologies :(

SHLoadImageResource() loads the resource into a 16 bits DIB.

Formerly I used a more complicated approach and noticed I got as good results with this one, but it was probably just luck.

So my approach would be: Load the resource to IImage, ->Draw it to a 32bit DIB of requested size, and add the DIB to a ILC_COLOR32 ImageList.

ImageList_Draw should honour the alpha bits at pixel level.

Hopefully ;)

Kevin Tse said...

Hi, Thanks for the article.
I have a question, what does "bInfo.bmiHeader.biYPelsPerMeter = 3780" mean? where does the number 3780 come from?