2

I don't use c++ or MFC/ATL etc so assume I know nothing.

The whole picture is that I need to take accept a PNG with a transparency layer and write it out as a JPEG with specified compression to pass to another system.

What I would like to know here, is how can I initialize a target CImage structure with solid white?

I've done this so far (yes I know it has other stylistic issues)

    void ClipBoardFuncs::savePNGASJPEG(char filePath[256], char errorBuff[256]) {
    int sizeOfErrorBuff = 256;

        CImage imagePNG;
        CImage imageJPG;
        int xPNG, yPNG = 0;
        imagePNG.Load(filePath);

        xPNG = imagePNG.GetWidth();
        yPNG = imagePNG.GetHeight();

        //Create my target JPG same size and bit depth specifiying that there is no alpha channel (dwflag last param)
        imageJPG.Create(xPNG, yPNG, imagePNG.GetBPP(), 0);

        //Let there be white....
        for (int x = 0; x <= xPNG; x++)
        {
            for (int y = 0; y <= yPNG; y++)
            {
                imageJPG.SetPixelRGB(x, y, 255, 255, 255);
            }
        }

        //Copy the image over onto on the white
        //BitBlt copies everything....
        //imagePNG.BitBlt(imageJPG.GetDC(), 0, 0);
        //Draw is more like the C# draw examples I've seen
        imagePNG.Draw(imageJPG.GetDC(), 0, 0);

        imageJPG.ReleaseDC();

        HRESULT hr = NULL;
        hr = imageJPG.Save(filePath, Gdiplus::ImageFormatJPEG);
        imagePNG.Destroy();
        imagePNG.ReleaseGDIPlus();

        imageJPG.Destroy();
        imageJPG.ReleaseGDIPlus();


        LPCTSTR error = _com_error(hr).ErrorMessage();
        strncpy_s(errorBuff, sizeOfErrorBuff, _com_error(hr).ErrorMessage(), _TRUNCATE);

} 

The lovely C# people have this answer:

Convert Transparent PNG to JPG with Non-Black Background Color

But I need the c++ MFC solution to use as an exported function in a DLL.

By exported Function I mean the same architecture as you would find in kernel32.dll - sorry I do not know the terminology to differentiate that kind of DLL from one stuffed with COM objects.

Can anyone suggest a faster way to initialize the imageJPEG structure to solid white than the nested x/y for loops I have here?

Cheers

4GLGuy

4GLGuy
  • 21
  • 3

2 Answers2

1

The initialization can be done with Rectangle or FillRect (for this, FillRect is probably preferred--Rectangle draws an outline of the rectangle in the current pen color, which we probably don't want).

So, the sequence looks something like this:

CImage png;
png.Load(pDoc->filename);

CRect rect{ 0, 0, png.GetWidth(), png.GetHeight() };

CImage jpeg;
jpeg.Create(rect.Width(), rect.Height(), png.GetBPP());

auto dc = jpeg.GetDC();
HBRUSH white = CreateSolidBrush(RGB(255, 255, 255));
FillRect(dc, &rect, white);

png.Draw(dc, 0, 0);
jpeg.ReleaseDC();

jpeg.Save(L"Insert File name here", Gdiplus::ImageFormatJPEG);

jpeg.Destroy();
jpeg.ReleaseGDIPlus();
png.ReleaseGDIPlus();

GDI+ is "smart" enough that when you do a .Draw with an image that has an alpha channel, it takes that channel into account, without your having to use TransparentBlt (or anything similar).

SetTransparentColor will not work for what you're trying to do here. SetTransparentColor is for an image that does not have an alpha channel ("transparency layer"). You then use it to choose a color that will be treated as if it were transparent--which can certainly be useful, but isn't what you want here.

You can use memset instead, but only for colors where the red, green, and blue channels all have the same values (i.e., black, white, or some shade of grey). Otherwise, you can do the fill on your own with a nested loop, but in most cases you probably want to use FillRect instead (it may be able to use graphics hardware for acceleration, where the loop will pretty dependably just run on the CPU--worst case, they're both about the same speed, but in some cases FillRect will be faster).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Thanks for the support - when I add the CBrush line it was not recognized so I looked for the class definition (https://learn.microsoft.com/en-gb/cpp/mfc/reference/cbrush-class?view=vs-2017) and included afxwin.h. This caused the linker to do what I found in theis thread - https://stackoverflow.com/questions/25031009/error-please-define-afxdll-or-do-not-use-mdd-occurs-even-after-making-chang , it is currently complaining about windows.h being included in afxv_w32.h - working on it. – 4GLGuy Sep 06 '18 at 09:50
  • Is that because I've started with a console application? – 4GLGuy Sep 06 '18 at 10:07
  • I adapted this https://stackoverflow.com/questions/22479272/how-to-fill-a-rectangle-region-with-semi-transparent-brush-in-mfc but it came out black again - shame it was real quick. Working on it... – 4GLGuy Sep 06 '18 at 11:20
  • @4GLGuy: I've amended the code to show how to do the job without the MFC CBrush (really a trivial change). – Jerry Coffin Sep 06 '18 at 14:38
  • (Trivial - that is a word I could use to describe my level of knowledge of MFC) - Thanks for takeing the time to do that for me. I had been looking at other types of brush but did not have the confidence to assert that one brush is much the same as the other. I'd show you the fun and games I've been having with char, wchar_t and CString but it would probably just depress you. First time my C++ for dummies book (1994) has been opened this century. – 4GLGuy Sep 07 '18 at 07:21
0
imagePNG.Draw(imageJPG.GetDC(), 0, 0);

Each call to GetDC must have a subsequent call to ReleaseDC. See also CImage::GetDC documentation.

CImage::GetDC provides a handle to memory device context. This handle can be used to draw using standard GDI functions. The handle should later be cleaned up with CImage::ReleaseDC.

CImage::Draw may not know what the transparent color is. You have to use TransparentBlt to tell it what the transparent color is. For example, to replace red color with white color:

HDC hdc = imageJPG.GetDC();
CDC dc;
dc.Attach(hdc);
CRect rc(0, 0, xPNG, yPNG);
dc.FillSolidRect(&rc, RGB(255, 255, 255));
dc.Detach();

imagePNG.TransparentBlt(hdc, rc, RGB(255, 0, 0));//replace red with white
imageJPG.ReleaseDC();

...
imageJPG.Save(...);

Or just use CImage::SetTransparentColor:

imagePNG.SetTransparentColor(RGB(255, 0, 0));
imagePNG.Draw(hdc, rc);
for (int x = 0; x <= xPNG; x++){...}

To do this using a loop, change the condition in the loop to x < xPNG and x < yPNG.

zett42
  • 25,437
  • 3
  • 35
  • 72
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77