4
votes

I am having a problem making alpha images print to a printer device context (real or XPS document writer). They work great on the screen context and in print preview but when I print to file or to the printer they show up as black squares when they are positioned on top of another image. I was using the CImage::Draw before and had a similar result (black or clear squares) as using thes GDI+ API directly:

    Gdiplus::Graphics g(hDestDC) ;
    ...
    // Edit: the image value here was one aquired from a ATL::CImage not 
    // a Gdiplus::Image (see solution)
    g.DrawImage(image,rect,0,0,GetWidth(),GetHeight(),Gdiplus::UnitPixel,0,0,0);

The device caps seem to indicate that the context supports blending via

GetDeviceCaps(hDestDC, SB_PIXEL_ALPHA)

The nature of the images do not seem to matter either, here is the format of the two I am using:

PNG image data, 256 x 256, 8-bit/color RGBA, non-interlaced
PNG image data, 192 x 64, 1-bit colormap, non-interlaced

Both yield the same results using the GDI+ interfaces with CImage data. What is the best way to have alpha images act the same way on the print context as they do on the screen? Could the device capabilities be misrepresenting something because alpha works using a BitmapMatrix and using a blend for the entire image?

Edit: 3/4/2013

My new approach is to do all the alpha blending in memory my thought was if the printer did not support alpha blending I would create a memory context to blend and then just copy the blended result to the context. The important parts of the code look like this:

int width = rectDest.right - rectDest.left;
int height = rectDest.bottom - rectDest.top;

BLENDFUNCTION blendFunction;
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.BlendFlags =  0;
blendFunction.SourceConstantAlpha = 0xFF;
blendFunction.AlphaFormat = AC_SRC_ALPHA;

HDC memDC = CreateCompatibleDC(hDestDC);
HBITMAP bitmap = CreateCompatibleBitmap(hDestDC,width,height);

SelectBitmap(memDC,bitmap);
//sample the underying area and copy it to memDC
::BitBlt(memDC, 0,0, width, height, hDestDC, rectDest.left, rectDest.top, SRCCOPY);
//now blend the image in memory onto the area.
GdiAlphaBlend(memDC,0,0, width, height,GetDC(), 0, 0, GetWidth(), GetHeight(),blendFunction);
//now just BitBlt the blended data to the context
::BitBlt(hDestDC,rectDest.left, rectDest.top,width,height,memDC,0,0,SRCCOPY);

... to my surprise I get almost the same result. Actually I blit the intermediate steps along the left of the screen just to make sure everything is operating correctly. Both the background it grabs and the alpha blended result (that I blit to the printer context) look great on the screen. Could this be a bug? I guess BitBlt leaves the alpha values intact from the prior blend so is it the actual alpha values in the pixel data that is throwing the printer device context? If so how can I remove the alpha before the final BitBlt?

Edit: 3/5/2013
Now I have tried the following:
1. Using a Device-independent bitmap to create the HBITMAP reference.
2. Using CreateDiscardableBitmap to create the HBITMAP (with the most success).
3. Manually setting the alpha channel for each pixel to 0xFF and 0x00.

BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biBitCount = 32; 
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader); 
bitmapInfo.bmiHeader.biWidth = width; 
bitmapInfo.bmiHeader.biHeight = height;
bitmapInfo.bmiHeader.biSizeImage = bitmapSizeBytes;

HDC memDC = CreateCompatibleDC(hDestDC);
//was HBITMAP bitmap = CreateCompatibleBitmap(hDestDC,width,height);
//also tried HBITMAP bitmap = CreateDiscardableBitmap(hDestDC,width, height);
HBITMAP bitmap = CreateDIBSection(memDC, &bitmapInfo,DIB_RGB_COLORS,&imageBits,NULL,0x00);

Using the disposable bitmap at least let the image render but with black for the areas where alpha should be.

3
By any chance is your printer a Postscript printer? Postscript has limited support for transparencies. - Carey Gregory
Turns out the printer being tested on is a PCL 6 printer. It exhibits the same behavior in the XPS document writer too (print to file). - Ray Pendergraph
Ah, well, you can disregard my answer then. Unless you post your code, I don't know how anyone can help. - Carey Gregory

3 Answers

2
votes

Ok, I think I found the solution here. Well it does not explain why the ways discussed above were not functioning but it provides a way forward. Our classes were all based off of ATL::CImage. It looks like the real key to the solution is that ATL::CImage somehow mistreats the image data of some of these alpha images when it loads them in. For instance for images that reported the same format it would invert the colors of one and not the other or not display one of them... things like this. As a test I stored the same data that was read into CImage::Load into a Gdiplus::Image instance and used that to draw onto a graphics instance (code below). This fixed all of the issues so it sort of seems like the moral of the story at this point is to replace the ATL based code with the newer stuff as it does not do the correct thing with alpha images.

Here is the latest code:

   int width = rectDest.right - rectDest.left;
   int height = rectDest.bottom - rectDest.top;

   Gdiplus::Graphics gfx(hDestDC);
   //These next two lines allow resizing and printing of JPG to a printer
   //without them GDI+ seems to have trouble resizing and repositioning
   //JPEG files to fit onto the printer context and renders them off screen
   //and distorted.
   gfx.SetInterpolationMode(Gdiplus::InterpolationModeHighQuality);
   gfx.SetPageUnit(Gdiplus::UnitPixel);
   Gdiplus::Rect destination(rectDest.left, rectDest.top,width, height);
   Gdiplus::ImageAttributes attributes;
   //The color matrix has to be set onto the attributes or otherwise 
   //alpha will not work even though it's the identity. Also you 
   //can tweak the position at [4,4] to adjust alpha for the whole image
   //and have per-pixel alpha as well.
   Gdiplus::ColorMatrix matrix = {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, 1.0, 0.0,
                                  0.0, 0.0, 0.0, 0.0, 1.0};
   attributes.SetColorMatrix(&matrix,Gdiplus::ColorMatrixFlagsDefault, Gdiplus::ColorAdjustTypeBitmap);
   gfx.DrawImage(gdiImage, destination, 0,0, GetWidth(),GetHeight(),Gdiplus::UnitPixel, &attributes, NULL, NULL);
1
votes

Test it on a PCL printer. I expect you'll find it's the printer, not your code. If that's the case, the only solution is to render the entire page to a single bitmap and then blit that bitmap to the printer. It's not very desirable performance-wise, but it will work reliably on all printers, Postscript or otherwise.

If you'd like to understand the problem with Postscript and transparencies in more depth, Wikipedia has a pretty good summary of the issue.

0
votes

Try filling the target area with white first, and then do the blend.

It's possible that you think it's right on screen, because you happen to be blending onto a window that has been "erased" while opaque white pixels.

The printer may be trying to blend to pixels that haven't been explicitly initialized. Perhaps the printer driver assumes those pixels are initially black. That's a reasonable (if counterintuitive) assumption. The fact that the printer paper is white leads one to assume that printing "transparent" pixels will leave that area white, but, technically, those pixels are uninitialized.