memononen / nanovg

Antialiased 2D vector drawing library on top of OpenGL for UI and visualizations.
zlib License
5.15k stars 771 forks source link

Cannot dynamically load a bitmap from memory if sizes are not dividable by 4 #613

Closed iUltimateLP closed 3 years ago

iUltimateLP commented 3 years ago

Hey there! I'm using a port of nanovg on the Nintendo Switch, and I encountered a weird problem today. I'm dynamically generating a bitmap (right now only a red box), and I want to feed that into nvgCreateImageMem.

Below you can find my code to generate the bitmap file:

    unsigned char* createBitmapFileHeader(int height, int stride)
    {
        int fileSize = FILE_HEADER_SIZE + INFO_HEADER_SIZE + (stride * height);

        brls::Logger::info("filesize for header {}", fileSize);

        static unsigned char fileHeader[] = {
            0,0,     /// signature
            0,0,0,0, /// image file size in bytes
            0,0,0,0, /// reserved
            0,0,0,0, /// start of pixel array
        };

        fileHeader[ 0] = (unsigned char)('B');
        fileHeader[ 1] = (unsigned char)('M');
        fileHeader[ 2] = (unsigned char)(fileSize      );
        fileHeader[ 3] = (unsigned char)(fileSize >>  8);
        fileHeader[ 4] = (unsigned char)(fileSize >> 16);
        fileHeader[ 5] = (unsigned char)(fileSize >> 24);
        fileHeader[10] = (unsigned char)(FILE_HEADER_SIZE + INFO_HEADER_SIZE);

        return fileHeader;
    }

    unsigned char* createBitmapInfoHeader(int height, int width)
    {
        static unsigned char infoHeader[] = {
            0,0,0,0, /// header size
            0,0,0,0, /// image width
            0,0,0,0, /// image height
            0,0,     /// number of color planes
            0,0,     /// bits per pixel
            0,0,0,0, /// compression
            0,0,0,0, /// image size
            0,0,0,0, /// horizontal resolution
            0,0,0,0, /// vertical resolution
            0,0,0,0, /// colors in color table
            0,0,0,0, /// important color count
        };

        infoHeader[ 0] = (unsigned char)(INFO_HEADER_SIZE);
        infoHeader[ 4] = (unsigned char)(width      );
        infoHeader[ 5] = (unsigned char)(width >>  8);
        infoHeader[ 6] = (unsigned char)(width >> 16);
        infoHeader[ 7] = (unsigned char)(width >> 24);
        infoHeader[ 8] = (unsigned char)(height      );
        infoHeader[ 9] = (unsigned char)(height >>  8);
        infoHeader[10] = (unsigned char)(height >> 16);
        infoHeader[11] = (unsigned char)(height >> 24);
        infoHeader[12] = (unsigned char)(1);
        infoHeader[14] = (unsigned char)(BYTES_PER_PIXEL*8);

        return infoHeader;
    }

    unsigned char* generateBitmapImage(unsigned char* image, int height, int width, int* outSize)
    {
        int widthInBytes = width * BYTES_PER_PIXEL;

        unsigned char padding[4] = {0, 0, 0, 0};
        int paddingSize = (4 - (widthInBytes) % 4) % 4;

        int stride = (widthInBytes) + paddingSize;

        unsigned char* fileHeader = createBitmapFileHeader(height, stride);
        unsigned char* infoHeader = createBitmapInfoHeader(height, width);

        int imageSize = (BYTES_PER_PIXEL * height * width);
        int bufferSize = INFO_HEADER_SIZE + FILE_HEADER_SIZE + imageSize;

        unsigned char* buffer = (unsigned char*)calloc(1, bufferSize);
        unsigned char* b = buffer;

        memcpy(buffer, fileHeader, FILE_HEADER_SIZE);
        buffer += FILE_HEADER_SIZE;
        memcpy(buffer, infoHeader, INFO_HEADER_SIZE);
        buffer += INFO_HEADER_SIZE;

        for (int i = 0; i < height; i++)
        {
            memcpy(buffer, image + (i * widthInBytes), BYTES_PER_PIXEL * width);
            buffer += (BYTES_PER_PIXEL * width);

            memcpy(buffer, padding, paddingSize * 1);
            buffer += (paddingSize * 1);
        }

        int diff = buffer - b;       
        *outSize = diff;

        FILE* f = fopen("sdmc://test.bmp", "wb");
        fwrite(b, diff, 1, f);
        fclose(f);

        return b;
    }

Actually generating the image (right now only red pixels):

    int height = this->image->getHeight();
    int width = this->image->getWidth();
    unsigned char* bmpImage = (unsigned char*)calloc(1, height * width * bitmap::BYTES_PER_PIXEL);

    for (int by = 0; by < height; by++)
    {
        for (int bx = 0; bx < width; bx++)
        {
            // https://stackoverflow.com/questions/3902648/c-representing-a-3d-array-in-a-1d-array
            memset(&bmpImage[(by * width + bx) * 3 + 2], (unsigned char)255, sizeof(unsigned char));
            memset(&bmpImage[(by * width + bx) * 3 + 1], (unsigned char)0, sizeof(unsigned char));
            memset(&bmpImage[(by * width + bx) * 3 + 0], (unsigned char)0, sizeof(unsigned char));
        }
    }

    unsigned char* data = bitmap::generateBitmapImage(bmpImage, height, width, &size);

Then, the data is loaded using nvgCreateImageMem.

Now, this works pretty good (both with bitmaps with clean resolutions like 256x256 and odd resolutions like 170x170). Also, you can see I got a test file written to see whether the bitmap is valid, and it is. Windows can view it without problems. However, trying to display it on the Switch using nanovg prints out

Failed to load outofmem

to the console - which is weird, because it succeeds with loading the exact same bitmap from file and not from memory. Any idea whats happening?

memononen commented 3 years ago

Too much code, did not ready, sorry.

You probably want to use nvgCreateImageRGBA() instead. It allows you to pass RGBA image data directly.

iUltimateLP commented 3 years ago

Thanks that did the trick!