Как создать растровое изображение / изображение из Scan0? - PullRequest
2 голосов
/ 21 марта 2019

У меня проблемы с переносом следующего кода 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 
}

Что он делает:Создайте изображение из WebSite / SVG в том виде, в котором оно было отображено последней встроенной версией Chromium, и сохраните его как файл.

Итак, это соответствующий обработчик рендеринга в 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);

Так что я подумал, что могу просто вызвать GdipCreateBitmapFromScan0 в gdibitmapflat и быть почти готовым

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

, что означает, что я должен создать заголовок растрового изображения myselfs.Или я мог бы использовать некоторый код из ImageIO .

Я не могу поверить, что создание простого растрового изображения даже на одной операционной системе - это так сложно ...

Неужели нет лучшего (и портативного) способа создания простого растрового изображения из тривиального массива цветов пикселей?И почему компилятор не находит GdipCreateBitmapFromScan0?Если бы я использовал LoadLibrary и GetProcAddress, чтобы вызвать его, вместо того, чтобы искать файлы заголовков Windows, я бы уже закончил ...И почему #include <gdiplus.h> не включает свои собственные зависимости?

Ответы [ 2 ]

1 голос
/ 21 марта 2019

Ваш взгляд на внутреннюю часть .NET привел вас к использованию функции, которая не является частью документированного открытого интерфейса GDI +. Мне кажется, что это реальная причина большинства ваших проблем.

Я думаю, вы, вероятно, захотите начать с создания GdiPlus::Bitmap объекта из ваших пикселей. У него есть конструктор, который выглядит так, как будто он напрямую примет ваши данные .

Как только вы создали объект Bitmap, вы вызываете его Save функцию-член. Bitmap публично получен из Image, так что вы в основном имеете дело с нормальным Image::Save для генерации PNG.

Если вы хотите устранить зависимость от кода Windows, вы можете вместо этого использовать (для одной очевидной возможности) libpng . Это дает вам гораздо больший контроль над процессом, за счет того, что вы потратите немного больше времени на работу (в зависимости от того, что вы хотите сделать, вероятно, порядка от полдюжины до десятка строк кода, а не от одной). или два).

0 голосов
/ 22 марта 2019

Итак, сделав это как в GDI +, так и в сыром C, я могу с уверенностью сказать, что это на самом деле быстрее, не говоря уже о значительно менее проблемных и менее интенсивных Google, просто выполняющих обработку изображений без GDI / GDI +.Тот, кто внедрил 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
...