Search code examples
c++gdi+gdichromium-embedded

How to create a bitmap/image from Scan0?


I'm having trouble porting the following C# code to C++:

protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects
    , System.IntPtr buffer, int width, int height)
{
    if (isPainting == true)
        return;

    isPainting = true;

    // Save the provided buffer (a bitmap image) as a PNG.
    using (System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, width * 4, System.Drawing.Imaging.PixelFormat.Format32bppRgb, buffer))
    {
        bitmap.Save(@"LastOnPaint.png", System.Drawing.Imaging.ImageFormat.Png);
    } // End Using bitmap 
}

What it does:
Create an image from a WebSite/SVG as rendered by the latest version of Chromium embedded and save it as a file.

So this is the corresponding render-handler in C++:

void RenderHandler::OnPaint(
    CefRefPtr<CefBrowser> browser,
    CefRenderHandler::PaintElementType type,
    const CefRenderHandler::RectList& dirtyRects,
    const void* buffer, int width, int height
) {
    // size_t len = sizeof(buffer) / sizeof(void*);
    // printf("buffer length: %zu\n", len); // 1...
    // Array size is probably: width*height * 4;
}

So I was looking into what C# does in the bitmap-constructor, which is the following:

public Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0)
{
    IntPtr bitmap = IntPtr.Zero;
    int status = Gdip.GdipCreateBitmapFromScan0(width, height, stride, unchecked((int)format), new HandleRef(null, scan0), out bitmap);
    Gdip.CheckStatus(status);

    SetNativeImage(bitmap);
}


internal void SetNativeImage(IntPtr handle) {
        if (handle == IntPtr.Zero)
            throw new ArgumentException(SR.GetString(SR.NativeHandle0), "handle");

        nativeImage = handle;
    }

Which traces to

internal const string Gdiplus = "gdiplus.dll";

[DllImport(ExternDll.Gdiplus, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Unicode)] // 3 = Unicode
[ResourceExposure(ResourceScope.Machine)]
internal static extern int GdipCreateBitmapFromScan0(int width, int height, int stride, int format, HandleRef scan0, out IntPtr bitmap);

So I thought I could just call GdipCreateBitmapFromScan0 in gdibitmapflat and be almost finished

GpStatus WINGDIPAPI GdipCreateBitmapFromScan0(INT width
, INT height, INT stride, PixelFormat format
, BYTE* scan0, GpBitmap** bitmap)

So I gathered the necessary header-files for GDI, which was a horrible experience

#ifndef __BITMAPHELPER_H__
#define __BITMAPHELPER_H__

// #define WIN32_LEAN_AND_MEAN

#pragma warning(disable:4458)

#include <Windows.h>
#include <ObjIdl.h>
#include <minmax.h>
#include <gdiplus.h>
#include <wingdi.h>
#include <gdiplusbitmap.h>
#include <gdiplusflat.h>
using namespace Gdiplus;
#pragma comment (lib,"gdiplus.lib")

#pragma warning(default:4458)


#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <cstdbool>

#include <algorithm>
#include <memory>

And thought this would about do it

#include "BitmapHelper.h" 
static void Test()
{
    GpBitmap *bitmap = NULL;
    GdipCreateBitmapFromScan0(100, 100, 0, PixelFormat32bppARGB, NULL, &bitmap); // create a bitmap object with specified width/height/color
    // GpGraphics *graph;       


    // Image * syntaxTest = NULL;
    //syntaxTest->FromFile(TEXT("d:\\abc.jpg"), true);  // create an image object
    // Bitmap::FromBITMAPINFO
    // GpImage *image = NULL;
    // Gdiplus::Image()


    Bitmap *bmp = NULL;     

    // GdipLoadImageFromFile(TEXT("d:\\abc.jpg"), &image);  // create an image object


    // GdipGetImageGraphicsContext(bitmap, &graph); // create a graphic object via bitmap object
    // GdipDrawImageI(graph, image, 100, 100);          // draw image to this graphic object, it can be done

}

However, it turns out the compiler doesn't know GdipCreateBitmapFromScan0, although it's definitely inside #include <gdiplusflat.h>...

How to create a bitmap/image from Scan0 ?
Note:
While I am at it, I don't want to resort to C++.NET, and ideally not to the WinAPI either; because i'd like it to work on Linux too. And not to a monstrous dependency like SDL either.

So far, it looks like my possible alternatives are using this code:

https://codereview.stackexchange.com/questions/196084/read-and-write-bmp-file-in-c

which means I have to create the bitmap header myselfs.
Or I could use some code from ImageIO.

I can't quite belive that creating a simple bitmap on even a single operating-system is that hard...

Is there really no better (and portable) way to create a simple bitmap from a trivial array of pixel colors ?
And why does the compiler not find GdipCreateBitmapFromScan0 ?
If I had used LoadLibrary and GetProcAddress to invoke it instead of f*ing windows header files, I'd be about finished by now...
And why does #include <gdiplus.h> not include its own dependencies ?


Solution

  • Your looking at the internals of .NET have led you toward using a function that's not part of the documented, public interface of GDI+. It looks to me like that's the real cause of most of your problems.

    What I think you probably want to do is start by creating a GdiPlus::Bitmap object from your pixels. It has a constructor that looks like it'll directly accept your data.

    Once you've created the Bitmap object, you call its Save member function. Bitmap is publicly derived from Image, so you're basically dealing with the normal Image::Save to generate a PNG.

    If you want to eliminate the dependency on Windows code, you might consider using (for one obvious possibility) libpng instead. This gives you quite a lot more control over the process, at the expense of being quite a bit more work to use (depending on what you want to do, probably on the order of a half dozen to a dozen lines of code rather than one or two).