0

I'm trying to create a very big image from pixel data, using GDI+. The code looks something like this:

Gdiplus::Status CreatePNG(int tileSize, int height = 16 * 1024)
{
    Gdiplus::Status status = Gdiplus::Ok;
    int width = 1024 * 16;
    Gdiplus::Bitmap bitmap(width, height, PixelFormat32bppRGB);
    Gdiplus::BitmapData bitmapData;
    INT *pixels = new INT[width * tileSize];
    ZeroMemory(pixels, width * tileSize * sizeof(INT));
    for (int y = 0; y != tileSize; ++y) {
        for (int x = 0; x != width; ++x) {
            INT color = 0xffff00;
            pixels[y * width + x] = color;
        }
    }
    ZeroMemory(&bitmapData, sizeof(bitmapData));
    bitmapData.Scan0 = pixels;
    bitmapData.Stride = width * sizeof(INT);
    bitmapData.Width = width;
    bitmapData.Height = tileSize;
    bitmapData.PixelFormat = bitmap.GetPixelFormat();
    Gdiplus::Rect tile(0, 0, width, tileSize);
    status = bitmap.LockBits(&tile, Gdiplus::ImageLockModeWrite | Gdiplus::ImageLockModeUserInputBuf,
        bitmap.GetPixelFormat(), &bitmapData);
    if (status == Gdiplus::Ok)
        status = bitmap.UnlockBits(&bitmapData);

    if (status == Gdiplus::Ok) {
        CLSID encoderClsid;
        if (GetEncoderClsid(L"image/png", &encoderClsid) >= 0) {
            status = bitmap.Save(TEXT("big.png"), &encoderClsid);
        }
    }
    return status;
}

In other words, I create a bitmap that's 16K pixels wide and tall, and fill the top with yellow pixels from a user input buffer (Gdiplus::ImageLockModeUserInputBuf).

This works only as long as height is small enough: 31K lines is okay, more than 32K results in bitmap.LockBits returning Gdiplus::InvalidParameter. The wider the image, the taller it can be, so I assume there is some logical barrier at 256 megapixels.

Where does that limit come from? I have not found any mention of it in the documentation.

How can I manipulate an image that's bigger?

I'm building a 32 bit application, but the same problem seems to exist when I target 64 bits.

Enno
  • 1,736
  • 17
  • 32
  • 2
    Is your application (not your OS) 32-bit or 64-bit? – PaulMcKenzie Aug 07 '23 at 17:46
  • 2
    Perhaps related: https://stackoverflow.com/questions/29175585/what-is-the-maximum-resolution-of-c-sharp-net-bitmap ? – Dan Mašek Aug 07 '23 at 17:51
  • I'm sure you can find the answer to this question in the documentation. Did you try reading it? If not, why not? – Jesper Juhl Aug 07 '23 at 18:37
  • 1
    Q: Please clarify what exactly "goes wrong" when you increase from 31K to 32K lines. Look here: https://stackoverflow.com/questions/2651010/gdi-on-64bit-systems. And please consider trying a newer API (e.g. Direct2D) instead. – paulsm4 Aug 07 '23 at 18:42
  • Sorry, seem to have forgotten to explain the problem in enough detail. I'll edit my question. – Enno Aug 07 '23 at 18:52
  • @paulsm4 I know now that GDI+ is considered legacy, but I was hoping it had more backward compatibility, and wouldn't get killed any time soon given how long it's been around. Also, it's the first native Windows API for which I found an example that wrote PNG files. My end goal isn't to make bitmaps, but PNGs, since the code I am modifying used to have a dependency on libpng that I want to eliminate. I can easily produce the image one line at a time, there is no need to hold it in memory all at once. The Bitmap is there because that's what was done in the example I found. – Enno Aug 07 '23 at 19:10
  • Some background information in [this](https://stackoverflow.com/questions/29175585/what-is-the-maximum-resolution-of-c-sharp-net-bitmap) question and answers. – 500 - Internal Server Error Aug 07 '23 at 19:14
  • @Enno - Please give some details about exactly "what's failing" ... and how. Please look at selbie's reply and a) add a comment as to whether or not helps, and b) consider "accepting" (if it resolves the problem), and at least "upvoting" (regardless). And of course - all bets off for 32 bit builds, and/or inadvertently picking up the 32 bit .dll at runtime. – paulsm4 Aug 07 '23 at 19:41
  • 1
    You want to write PNG's. But you also want to elliminate a dependency on libpng? Why? That makes no sense to me. Use what's available - for writing and reading PNGs, that's libpng! – Jesper Juhl Aug 07 '23 at 20:07
  • @JesperJuhl: There's far more libraries able to manipulate PNG files than just `libpng`, and on Windows, support is built into the OS. – Ben Voigt Aug 07 '23 at 20:29
  • @BenVoigt I know that. But Just discarding a well known option is not rational. – Jesper Juhl Aug 07 '23 at 20:31
  • More salient is that this question has nothing to do with PNG. The code only uses PNG in the last couple lines, which aren't reached when the error happens. – Ben Voigt Aug 07 '23 at 20:33
  • "The wider the image, the taller it can be" Did you mean narrower rather than wider? – Ben Voigt Aug 07 '23 at 20:48
  • @JesperJuhl "Well known" is not necessarily the same as "high quality". Discarding `libpng` in favor of the infrastructure that comes with the system is a well-justified option. If nothing else, your application gets servicing for free. – IInspectable Aug 08 '23 at 13:23

1 Answers1

2

This looks suspicious:

INT *pixels = new INT[width * tileSize];

You start to approach integer overflow limits when width*tileSize both get above 46K. Or more precisely, the largest integer multiplication can't exceed 2147483647 (2GB) before encountering overflow and the result being negative.

But on a 32-bit process, you're out of memory when your total process allocations reach 2GB. So trying to allocate a bitmap over that size isn't likely to succeed anyway.

How about this instead:

uint64_t uWidth = (unsigned)width;
uint64_t uTileSize = (unsigned)tileSize;
uint64_t totalPixels = uWidth * uTileSize;
INT *pixels = new INT[totalPixels];

The above fix assumes a 64-bit OS and that you compiled as a 64-bit process. As I mentioned above, you can't reliably allocate a 32Kx32K ARGB bitmap on a 32-bit process, because you'll be out of memory by then.

Make sure your Visual Studio project is getting built as 64-bit:

enter image description here

selbie
  • 100,020
  • 15
  • 103
  • 173