如何从Scan0创建位图/图像?

时间:2019-03-21 17:24:24

标签: c++ gdi+ gdi chromium-embedded

在将以下C#代码移植到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 
}

它的作用:
根据最新版的Chromium嵌入的图像从WebSite / SVG创建图像并将其保存为文件。

这是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;
}

所以我一直在研究C#在位图构造函数中的作用,如下所示:

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;
    }

可追溯到

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);

所以我想我可以在gdibitmapflat中调用GdipCreateBitmapFromScan0并快要完成了

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

所以我为GDI收集了必要的头文件,这真是可怕的经历

#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>

并认为这样做会

#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

}

但是,事实证明编译器不知道GdipCreateBitmapFromScan0,尽管它肯定位于#include <gdiplusflat.h> ...

如何从Scan0创建位图/图像?
注意:
在此过程中,我不想求助于C ++。NET,理想情况下也不要求助于WinAPI。因为我也希望它也能在Linux上工作。而且也不是像SDL这样的可怕依赖。

到目前为止,看来我可能的替代方法正在使用以下代码:

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

这意味着我必须自己创建bitmap header
或者我可以使用ImageIO中的一些代码。

我不敢相信,即使在单个操作系统上创建一个简单的位图也是如此困难……

真的没有更好的(可移植的)方法来从琐碎的像素颜色数组中创建简单的位图吗?
为什么编译器找不到GdipCreateBitmapFromScan0?
如果我使用LoadLibrary和GetProcAddress来调用它而不是查找Windows头文件,那么我现在就可以完成了...
为什么#include <gdiplus.h>不包括它自己的依赖关系?

2 个答案:

答案 0 :(得分:1)

您对.NET的内部了解使您开始使用一个功能,该功能不属于GDI +的书面公开接口。在我看来,这是造成您大多数问题的真正原因。

我想您可能想做的就是从像素创建一个GdiPlus::Bitmap对象开始。它有一个constructor that looks like it'll directly accept your data

一旦创建了Bitmap对象,就调用其Save成员函数。 Bitmap是从Image公开派生的,因此您基本上是在处理普通的Image::Save来生成PNG。

如果要消除对Windows代码的依赖性,则可以考虑使用libpng(一种明显的可能性)。这使您对过程有更多的控制权,但要付出更多的工作量(取决于您要执行的工作,可能大约是六到十二行代码,而不是一行)。或两个)。

答案 1 :(得分:0)

因此,在GDI +和原始C语言中都完成此操作之后,我可以肯定地说它实际上更快,更不用说不用GDI / GDI +进行图像处理了,问题也就更少了,Google的工作也更少了。实施GDI +的任何人都会严重大脑受损。

由于我尚未正确处理透明性,并且尚未合并lodepng,因此暂时添加了GDI +作为可选的附加选项。

// A program to read, write, and crop BMP image files.
#include "Bmp.h"



//   Make a copy of a string on the heap.
// - Postcondition: the caller is responsible to free
//   the memory for the string.
char *_string_duplicate(const char *string)
{
    char *copy = (char*)malloc(sizeof(*copy) * (strlen(string) + 1));
    if (copy == NULL)
    {
        // return "Not enough memory for error message";
        const char* error_message = "Not enough memory for error message";
        size_t len = strlen(error_message);
        char* error = (char*)malloc(len * sizeof(char) + 1);
        strcpy(error, error_message);
        return error;
    }

    strcpy(copy, string);
    return copy;
}


// Check condition and set error message.
bool _check(bool condition, char **error, const char *error_message)
{
    bool is_valid = true;
    if (!condition)
    {
        is_valid = false;
        if (*error == NULL)  // to avoid memory leaks
        {
            *error = _string_duplicate(error_message);
        }
    }
    return is_valid;
}


//   Write an image to an already open file.
// - Postcondition: it is the caller's responsibility to free the memory
//   for the error message.
// - Return: true if and only if the operation succeeded.
bool write_bmp(FILE *fp, BMPImage *image, char **error)
{
    // Write header
    rewind(fp);
    size_t num_read = fwrite(&image->header, sizeof(image->header), 1, fp);
    if (!_check(num_read == 1, error, "Cannot write image"))
    {
        return false;
    }
    // Write image data
    num_read = fwrite(image->data, image->header.image_size_bytes, 1, fp);
    if (!_check(num_read == 1, error, "Cannot write image"))
    {
        return false;
    }

    return true;
}

// Free all memory referred to by the given BMPImage.
void free_bmp(BMPImage *image)
{
    free(image->data);
    free(image);
}


// Open file. In case of error, print message and exit.
FILE *_open_file(const char *filename, const char *mode)
{
    FILE *fp = fopen(filename, mode);
    if (fp == NULL)
    {
        fprintf(stderr, "Could not open file %s\n", filename);

        exit(EXIT_FAILURE);
    }

    return fp;
}

// Close file and release memory.void _clean_up(FILE *fp, BMPImage *image, char **error)
void _clean_up(FILE *fp, BMPImage *image, char **error)
{
    if (fp != NULL)
    {
        fclose(fp);
    }
    free_bmp(image);
    free(*error);
}


// Print error message and clean up resources.
void _handle_error(char **error, FILE *fp, BMPImage *image)
{
    fprintf(stderr, "ERROR: %s\n", *error);
    _clean_up(fp, image, error);

    exit(EXIT_FAILURE);
}

void write_image(const char *filename, BMPImage *image, char **error)
{
    FILE *output_ptr = _open_file(filename, "wb");

    if (!write_bmp(output_ptr, image, error))
    {
        _handle_error(error, output_ptr, image);
    }

    fflush(output_ptr);
    fclose(output_ptr);
    _clean_up(output_ptr, image, error);
}



//   Return the size of an image row in bytes.
// - Precondition: the header must have the width of the image in pixels.
uint32_t computeImageSize(BMPHeader *bmp_header)
{
    uint32_t bytes_per_pixel = bmp_header->bits_per_pixel / BITS_PER_BYTE;
    uint32_t bytes_per_row_without_padding = bmp_header->width_px * bytes_per_pixel;
    uint32_t padding = (4 - (bmp_header->width_px * bytes_per_pixel) % 4) % 4;

    uint32_t row_size_bytes = bytes_per_row_without_padding + padding;

    return row_size_bytes * bmp_header->height_px;
}



#ifdef USE_GDI

    #pragma warning(disable:4189)
    int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
    {
        UINT  num = 0;          // number of image encoders
        UINT  size = 0;         // size of the image encoder array in bytes

        Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;

        Gdiplus::GetImageEncodersSize(&num, &size);
        if (size == 0)
            return -1;  // Failure

        pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
        if (pImageCodecInfo == NULL)
            return -1;  // Failure

        Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);

        for (UINT j = 0; j < num; ++j)
        {
            if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
            {
                *pClsid = pImageCodecInfo[j].Clsid;
                free(pImageCodecInfo);
                return j;  // Success
            } // if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) 

        } // Next j 

        free(pImageCodecInfo);
        return -1;  // Failure
    }


    // https://github.com/lvandeve/lodepng

    static bool notInitialized = true;


    void WriteBitmapToFile(const char *filename, int width, int height, const void* buffer)
    {
        // HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

        if (notInitialized)
        {
            // https://docs.microsoft.com/en-us/windows/desktop/api/gdiplusinit/nf-gdiplusinit-gdiplusstartup
            Gdiplus::GdiplusStartupInput gdiplusStartupInput;
            ULONG_PTR gdiplusToken;
            Gdiplus::Status isOk = Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

            if (isOk != Gdiplus::Status::Ok)
            {
                printf("Failed on GdiplusStartup\n");
            }

            notInitialized = false;
            // defer
            // GdiplusShutdown(gdiplusToken);
        } // End if (notInitialized) 


        // https://docs.microsoft.com/en-us/windows/desktop/gdiplus/-gdiplus-constant-image-pixel-format-constants
        Gdiplus::Bitmap* myBitmap = new Gdiplus::Bitmap(width, height, width*4, PixelFormat32bppARGB, (BYTE*)buffer);
        // myBitmap->RotateFlip(Gdiplus::Rotate180FlipY);




        CLSID pngClsid;

        // int result = GetEncoderClsid(L"image/tiff", &tiffClsid);
        int result = GetEncoderClsid(L"image/png", &pngClsid);
        printf("End GetEncoderClsid:\n");

        if (result == -1)
            printf("Error: GetEncoderClsid\n");
            // throw std::runtime_error("Bitmap::Save");

        // if (Ok != myBitmap->Save(L"D\foobartest.png", &pngClsid)) printf("Error: Bitmap::Save");

        // WTF ? I guess a standard C/C++-stream would have been too simple ? 
        IStream* oStream = nullptr;
        if (CreateStreamOnHGlobal(NULL, TRUE, (LPSTREAM*)&oStream) != S_OK)
            printf("Error on creating an empty IStream\n");

        Gdiplus::EncoderParameters encoderParameters;

        encoderParameters.Count = 1;
        encoderParameters.Parameter[0].Guid = Gdiplus::EncoderQuality;
        encoderParameters.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong;
        encoderParameters.Parameter[0].NumberOfValues = 1;

        ULONG quality = 100;
        encoderParameters.Parameter[0].Value = &quality;



        // https://docs.microsoft.com/en-us/windows/desktop/api/gdiplusheaders/nf-gdiplusheaders-image-save(inistream_inconstclsid_inconstencoderparameters)
        if (Gdiplus::Status::Ok != myBitmap->Save(oStream, &pngClsid, &encoderParameters))
            printf("Error: Bitmap::Save\n");
            // throw std::runtime_error("Bitmap::Save");


        ULARGE_INTEGER ulnSize;
        LARGE_INTEGER lnOffset;
        lnOffset.QuadPart = 0;
        oStream->Seek(lnOffset, STREAM_SEEK_END, &ulnSize);
        oStream->Seek(lnOffset, STREAM_SEEK_SET, NULL);

        uint8_t *pBuff = new uint8_t[(unsigned int)ulnSize.QuadPart];
        ULONG ulBytesRead;
        oStream->Read(pBuff, (ULONG)ulnSize.QuadPart, &ulBytesRead);


        FILE *output_ptr = _open_file(filename, "wb");
        fwrite((void*)pBuff, sizeof(uint8_t), (unsigned int)ulnSize.QuadPart, output_ptr);
        fflush(output_ptr);
        fclose(output_ptr);
        oStream->Release();


        delete pBuff;
        delete myBitmap;

        // https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp
        // std::string rotated_string = base64_encode((const unsigned char*)pBuff, ulnSize.QuadPart);   
    }


#pragma warning(default:4189)

#else

    // TODO: PNG-Encoder 
    // https://github.com/lvandeve/lodepng
    // https://lodev.org/lodepng/
    BMPImage * CreateBitmapFromScan0(int32_t w, int32_t h, uint8_t* scan0)
    {
        BMPImage *new_image = (BMPImage *)malloc(sizeof(*new_image));
        BMPHeader *header = (BMPHeader *)malloc(sizeof(*header));

        new_image->header = *header;
        new_image->header.type = MAGIC_VALUE;
        new_image->header.bits_per_pixel = BITS_PER_PIXEL;
        new_image->header.width_px = w;
        new_image->header.height_px = h;
        new_image->header.image_size_bytes = computeImageSize(&new_image->header);
        new_image->header.size = BMP_HEADER_SIZE + new_image->header.image_size_bytes;
        new_image->header.dib_header_size = DIB_HEADER_SIZE;
        new_image->header.offset = (uint32_t) sizeof(BMPHeader);
        new_image->header.num_planes = 1;
        new_image->header.compression = 0;
        new_image->header.reserved1 = 0;
        new_image->header.reserved2 = 0;
        new_image->header.num_colors = 0;
        new_image->header.important_colors = 0;

        new_image->header.x_resolution_ppm = 3780; // image->header.x_resolution_ppm;
        new_image->header.y_resolution_ppm = 3780; // image->header.y_resolution_ppm;

        new_image->data = (uint8_t*)malloc(sizeof(*new_image->data) * new_image->header.image_size_bytes);
        memcpy(new_image->data, scan0, new_image->header.image_size_bytes);

        return new_image;
    }


    void WriteBitmapToFile(const char *filename, int width, int height, const void* buffer)
    {
        BMPImage * image = CreateBitmapFromScan0((int32_t)width, (int32_t)height, (uint8_t*)buffer);
        char *error = NULL;
        write_image(filename, image, &error);
    }

#endif 

标题:

#ifndef BITMAPLION_BITMAPINFORMATION_H
#define BITMAPLION_BITMAPINFORMATION_H


#ifdef __cplusplus
// #include <iostream>
// #include <fstream>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#else
#include <stdio.h>
    #include <stdlib.h>  // for malloc
    #include <stdint.h>
    #include <stdbool.h>
    #include <string.h>  // for strlen, strcopy
#endif


#ifdef __linux__ 
    //linux  specific code goes here
#elif _WIN32
    // windows specific code goes here
    #pragma warning(disable:4458)


    #include <Windows.h>
    #include <ObjIdl.h>
    #include <minmax.h>
    #include <gdiplus.h>
    // #include <gdiplusheaders.h>
    // #include <wingdi.h>
    // #include <gdiplusbitmap.h>
    // #include <gdiplusflat.h>
    // #include <Gdipluspixelformats.h>

    #pragma comment (lib,"gdiplus.lib")

    // using namespace Gdiplus;

    #pragma warning(default:4458)

#else

#endif



#define BMP_HEADER_SIZE 54
#define DIB_HEADER_SIZE 40

// Correct values for the header
#define MAGIC_VALUE         0x4D42
#define NUM_PLANE           1
#define COMPRESSION         0
#define NUM_COLORS          0
#define IMPORTANT_COLORS    0
#define BITS_PER_BYTE 8
// #define BITS_PER_PIXEL 24
#define BITS_PER_PIXEL 32


#ifdef _MSC_VER
#pragma pack(push)  // save the original data alignment
    #pragma pack(1)     // Set data alignment to 1 byte boundary
#endif


typedef struct
#ifndef _MSC_VER
        __attribute__((packed))
#endif
{
    uint16_t type;              // Magic identifier: 0x4d42
    uint32_t size;              // File size in bytes
    uint16_t reserved1;         // Not used
    uint16_t reserved2;         // Not used
    uint32_t offset;            // Offset to image data in bytes from beginning of file
    uint32_t dib_header_size;   // DIB Header size in bytes
    int32_t  width_px;          // Width of the image
    int32_t  height_px;         // Height of image
    uint16_t num_planes;        // Number of color planes
    uint16_t bits_per_pixel;    // Bits per pixel
    uint32_t compression;       // Compression type
    uint32_t image_size_bytes;  // Image size in bytes
    int32_t  x_resolution_ppm;  // Pixels per meter
    int32_t  y_resolution_ppm;  // Pixels per meter
    uint32_t num_colors;        // Number of colors
    uint32_t important_colors;  // Important colors
} BMPHeader;


#ifdef _MSC_VER
#pragma pack(pop)  // restore the previous pack setting
#endif



typedef struct {
    BMPHeader header;
    // unsigned char* data;
    // It is more informative and will force a necessary compiler error
    // on a rare machine with 16-bit char.
    uint8_t* data;
} BMPImage;


// #define USE_GDI true 

#ifndef USE_GDI
    BMPImage * CreateBitmapFromScan0(int32_t w, int32_t h, uint8_t* scan0);
#endif 

void WriteBitmapToFile(const char *filename, int width, int height, const void* buffer);



#endif //BITMAPLION_BITMAPINFORMATION_H