Again I ask. How do you convert an Image to HBITMAP?

I looked on the forum and saw a post from 2005, but it is not correct with the newer juce code.
So I am asking again. How can I take a juce image and covert it and get a handle to a HBITMAP and the same idea on the MAc?

So this is what I am trying but it does not work. It actually crashes on StretchDIBits.
What am I doing wrong?

void DrawImage( Image *pImage, int x, int y, it w, int h, int desWidth, int desHeight )
{
::SetMapMode (m_hDC, MM_TEXT);

NativeImageType imageType;
imageType.convert(*pImage);

WindowsBitmapImage p = (WindowsBitmapImage) &imageType;

int error = ::StretchDIBits (m_hDC, x, y, desWidth, desHeight,
0, 0, w, h, p->bitmapData, (const BITMAPINFO*) &p->bitmapInfo, DIB_RGB_COLORS, SRCCOPY);

Have you checked out the JUCE source code…? I suggest first searching for HBITMAP which should lead you to the WindowsBitmapImage class which has a public HBITMAP variable named hBitmap… and if you follow the code further you’ll see that ImagePixelData has a method named clone() which returns a Ptr… and looking at the Native Win32 implementation of clone() returns a pointer to a WindowsBitmapImage object which you should be able to use to access a public variable for the class… (using dynamic_cast).

On Mac… you’d need to load the ImagePixelData (into an NSImage) which will probably be your common denominator.

Rail

I assume you are saying to

WindowsBitmapImage *p = (WindowsBitmapImage*) pImage->getPixelData()->clone();

(const BITMAPINFO*) &p->bitmapInfo is not a correct BITMAPINFO pointer

This does not work?

On Windows… try this (I haven’t tested it… let us know)…

 WindowsBitmapImage* p = dynamic_cast<WindowsBitmapImage*>(pImage->getPixelData()->clone());

  if (p != nullptr)
      {
      // You have a good WindowsBitmapImage... so...
      // p->hBitmap should be available
      }

Rail

P returns null;

Try putting a breakpoint in clone() to see what’s happening… I’m currently on my Mac so based this on looking at the JUCE source code… Perhaps I missed something…

Rail

SoftwarePixelData::clone() is being called.
There are no NativeImageType methods being called.

I also tried:

NativeImageType type;
Image i = type.convert( *pImage );
p = (WindowsBitmapImage*) i.getPixelData()->clone();

and it does not work either.

You probably need to create a WindowsBitmapImage object with the right size then copy your Image PixelData into it.

Rail

I don’t think so.
The pixel data has to be converted to the correct format before this would work.

class WindowsBitmapImage  : public ImagePixelData
{
 public:
        WindowsBitmapImage (const Image::PixelFormat format,
                            const int w, const int h, const bool clearImage)
            : ImagePixelData (format, w, h)

and

 PixelFormat  Image::getFormat() const noexcept;

Can’t you use that???

You have an Image, get it’s PixelFormat… then create a WindowsBitmapImage using that format and the size of your Image… etc…

It would have to be RGB or ARGB though…

 jassert (format == Image::RGB || format == Image::ARGB);

You can always create a new Image of one of those types if needed using the Graphics class and a new Image.

Rail

I think this is a situation where we’d need to add an accessor method that’d return the underlying native handle for the image, like we do for windows in ComponentPeer::getNativeHandle()

However, like that, it’d have to come with the huge caveat that the handle which gets returned would be completely platform-dependent, may not exist at all, and would be liable to change to a different type without warning, so it’s really not a recommended way to do things.

Rather than asking for this very specific request, why not explain what it is that you’re actually trying to achieve? There may be much better ways to do what you want than messing about ancient win32 handle types.

3 Likes

I need to use the ancient win32 handle types as you state.
Juce is still using them for cursors, so they are not exactly ancient.

I am trying to print an image.
I have my own print routines, not the lowLevel juce calls.
So I need the image in a windows bitmap format to use StretchDIBits to send the image to a printDC.

Of course I will need to do the same on the same on a Mac.

So the best way to ask for this is how to get the Image into a WindowsBitmapImage and then I could use its routine to send the bits to a dc.

Thanks for listening.

TBH there’s no way I’d attempt to implement that by relying on the JUCE Image to supply me the handle. You’ll just have a world of pain in the future when we change the internals of the class.

If I was writing it, I’d create my own Image → HBITMAP conversion function, so that an Image in ANY format could be passed into it, and make it create whatever specific HBITMAP format you need from the image’s pixel colours. If you’re printing then the overhead of this extra copy step won’t matter. It’s probably 20 lines of code to write, and will work forever regardless of what we update internally in JUCE.

That is what I am looking into.

So what would I do on the mac?

I don’t really know anything about printing on OSX, but I guess something similar.

BTW maybe you have a good reason for using bitmaps, but for printing that sounds like a really bad idea. Can’t you use something like the LowLevelGraphicsPostScriptRenderer to render what you need as vector graphics?

No because I have implemented most of juce Graphic calls directly to a printeDC on Windows and a CGContextRef on Mac. I need good text printing, and of course real text in PDFs. And what if the printer doesn’t support PostScript. I have never looked into sending LowLevelGraphicsPostScriptRenderer data to a printer, but I could do it with a little work

Someday I may clean my code up and see if any juce users want it.

OK I am close but still need help.
This code below prints a juce image on windows but the transparent pixels are printing black.
Image does have an alpha channel.
Any ideas?

	Image::BitmapData bd(juceImage, 0, 0, imageWidth, imageHeight);

	HBITMAP hbmp = CreateBitmap(imageWidth, imageHeight, 1, 32, bd.data);
	if ( hbmp != nullptr )
	{
        HDC memDC = CreateCompatibleDC(m_hPrintDC);
        HGDIOBJ oldBitmap = SelectObject(memDC, hbmp);
  
		BITMAP  bitmap;
		GetObject(hbmp, sizeof(bitmap), &bitmap);

	    StretchBlt(m_hPrintDC, itemHPos, itemVPos, desWidth, desHeight, memDC, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
	    SelectObject(m_hDC, oldBitmap);
	}
1 Like

I think those ancient win32 StretchBlt functions pre-date alpha-blending!

But more importantly, you’re still making false assumptions about the layout of the memory that the Image::BitmapData provides - you can’t just feed it into a win32 function and expect it to be correct. The way to do this is to read each pixel and make sure you directly set the bytes/pixels in the HBITMAP yourself.

…and maybe a helpful reference would be to look at things like PNGImageFormat::writeImageToStream or the JPEG one, where it reads any Image and gets the pixels to write them into another specific format.

I found out the hard way that transparency was difficult with the old calls.
I tried writing the pixels one by one and even tried AlphaBlend, but the image printed was not of great quality.

So I took another approach close to what you stated.
I saved the image to a temporary file, and then used GdiPlus to load and print the image.

Something I noticed when using this approach was that after I wrote the file, Windows could not read it until I guess a destructor was called, so I have to enclose the file write within braces.

Here is the complete code so maybe you can tell me why the write had to be inside braces.

	//Write image out as a temporary png file
	String filename;
	{
		File file = File::createTempFile( ".png" );
		FileOutputStream	stream(file);

		PNGImageFormat png;
		png.writeImageToStream(*pOveImage, stream);
		stream.flush();

		filename = file.getFullPathName();
	}

	//Load the temporary file and print it
	Gdiplus::Image* pImage = Gdiplus::Image::FromFile(filename.toWideCharPointer()); 
	if (pImage != nullptr)
	{
		pGraphics->DrawImage(pImage, x, y, w, h);
		delete pImage;
	}
		
	//Delete temporary file
	if (!filename.isEmpty())
	{
		File file(filename);
		file.deleteFile();
	}

Now I am on to the mac and will probably use the same approach.
Thanks for your help.