Before i go in ive seen that youre considering removing TGA support here, but i love tga for its export speed. Maybe keep it simple, without any rle (if i want compression id pick png...) to make it stable.
While figuring out complications with huge surfaces i found that tga export is kind of buggy. TGA uses 2 bytes to store width and height, so its limited to 65535x65536 and there currently is no check for that, simple fix tho
Show fix idea
// src_c/image.c line 1680
static int
SaveTGA_RW(SDL_Surface *surface, SDL_RWops *out, int rle)
{
SDL_Surface *linebuf = NULL;
int alpha = 0;
struct TGAheader h;
int srcbpp;
SDL_BlendMode surf_blendmode;
int have_surf_colorkey = 0;
Uint32 surf_colorkey;
SDL_Rect r;
int bpp;
Uint8 *rlebuf = NULL;
h.infolen = 0;
SETLE16(h.cmap_start, 0);
srcbpp = PG_SURF_BitsPerPixel(surface);
if (srcbpp < 8) {
SDL_SetError("cannot save <8bpp images as TGA");
return -1;
}
// no changes up until here
if (surface->w >= 1<<16){
SDL_SetError("width too large, TGA is limited to 65535x65535");
return -1;
}
if (surface->h >= 1<<16){
SDL_SetError("height too large, TGA is limited to 65535x65535");
return -1;
}
[...]
Further i can crash sdl even if its within bounds (might be a sld2 issue, not sure how to test sld3)
tiny python repl + huge valgrind log
(.venv) p1geon@spyro:~/documents/code/python/pygame-ce$ valgrind -s python
==5981== Memcheck, a memory error detector
==5981== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==5981== Using Valgrind-3.24.0 and LibVEX; rerun with -h for copyright info
==5981== Command: python
==5981==
Python 3.13.5 (main, Jul 15 2026, 20:25:40) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pygame
>>> s = pygame.Surface((2**16-1,2**16-1))
==5981== Warning: set address range perms: large range [0x59ca0040, 0x459c20088) (undefined)
>>> s.fill("green")
Rect(0, 0, 65535, 65535)
>>> pygame.image.save(s,"image2.tga")
==5981== Invalid read of size 2
==5981== at 0x7048866: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7030CF9: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7064E1E: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x851CC4F: SaveTGA_RW (image.c:1824)
==5981== by 0x851CDA5: SaveTGA (image.c:1866)
==5981== by 0x8517B54: image_save (image.c:232)
==5981== by 0x585F7B: ??? (in /usr/bin/python3.13)
==5981== by 0x544EBA: _PyObject_MakeTpCall (in /usr/bin/python3.13)
==5981== by 0x56139A: _PyEval_EvalFrameDefault (in /usr/bin/python3.13)
==5981== by 0x55ABDB: PyEval_EvalCode (in /usr/bin/python3.13)
==5981== by 0x5D7351: ??? (in /usr/bin/python3.13)
==5981== by 0x56104D: _PyEval_EvalFrameDefault (in /usr/bin/python3.13)
==5981== Address 0xffffffffd9cd805c is not stack'd, malloc'd or (recently) free'd
==5981==
==5981== Invalid read of size 8
==5981== at 0x531ADD: ??? (in /usr/bin/python3.13)
==5981== by 0x5E39EB: PyImport_ImportModule (in /usr/bin/python3.13)
==5981== by 0x6A0C567: pg_mod_autoquit (base.c:175)
==5981== by 0x6A0CA7F: _pg_quit (base.c:357)
==5981== by 0x6A109C4: pygame_parachute (base.c:2013)
==5981== by 0x49F7DEF: ??? (in /usr/lib/x86_64-linux-gnu/libc.so.6)
==5981== by 0x7048865: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7030CF9: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7064E1E: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x851CC4F: SaveTGA_RW (image.c:1824)
==5981== by 0x851CDA5: SaveTGA (image.c:1866)
==5981== by 0x8517B54: image_save (image.c:232)
==5981== Address 0x10 is not stack'd, malloc'd or (recently) free'd
==5981==
==5981==
==5981== Process terminating with default action of signal 11 (SIGSEGV)
==5981== Access not within mapped region at address 0x10
==5981== at 0x531ADD: ??? (in /usr/bin/python3.13)
==5981== by 0x5E39EB: PyImport_ImportModule (in /usr/bin/python3.13)
==5981== by 0x6A0C567: pg_mod_autoquit (base.c:175)
==5981== by 0x6A0CA7F: _pg_quit (base.c:357)
==5981== by 0x6A109C4: pygame_parachute (base.c:2013)
==5981== by 0x49F7DEF: ??? (in /usr/lib/x86_64-linux-gnu/libc.so.6)
==5981== by 0x7048865: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7030CF9: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7064E1E: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x851CC4F: SaveTGA_RW (image.c:1824)
==5981== by 0x851CDA5: SaveTGA (image.c:1866)
==5981== by 0x8517B54: image_save (image.c:232)
==5981== If you believe this happened as a result of a stack
==5981== overflow in your program's main thread (unlikely but
==5981== possible), you can try to increase the size of the
==5981== main thread stack using the --main-stacksize= flag.
==5981== The main thread stack size used in this run was 8388608.
==5981==
==5981== HEAP SUMMARY:
==5981== in use at exit: 17,183,953,858 bytes in 4,637 blocks
==5981== total heap usage: 18,474 allocs, 13,837 frees, 17,225,019,104 bytes allocated
==5981==
==5981== LEAK SUMMARY:
==5981== definitely lost: 0 bytes in 0 blocks
==5981== indirectly lost: 0 bytes in 0 blocks
==5981== possibly lost: 17,179,561,016 bytes in 23 blocks
==5981== still reachable: 4,392,842 bytes in 4,614 blocks
==5981== suppressed: 0 bytes in 0 blocks
==5981== Rerun with --leak-check=full to see details of leaked memory
==5981==
==5981== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==5981==
==5981== 1 errors in context 1 of 2:
==5981== Invalid read of size 8
==5981== at 0x531ADD: ??? (in /usr/bin/python3.13)
==5981== by 0x5E39EB: PyImport_ImportModule (in /usr/bin/python3.13)
==5981== by 0x6A0C567: pg_mod_autoquit (base.c:175)
==5981== by 0x6A0CA7F: _pg_quit (base.c:357)
==5981== by 0x6A109C4: pygame_parachute (base.c:2013)
==5981== by 0x49F7DEF: ??? (in /usr/lib/x86_64-linux-gnu/libc.so.6)
==5981== by 0x7048865: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7030CF9: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7064E1E: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x851CC4F: SaveTGA_RW (image.c:1824)
==5981== by 0x851CDA5: SaveTGA (image.c:1866)
==5981== by 0x8517B54: image_save (image.c:232)
==5981== Address 0x10 is not stack'd, malloc'd or (recently) free'd
==5981==
==5981==
==5981== 1 errors in context 2 of 2:
==5981== Invalid read of size 2
==5981== at 0x7048866: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7030CF9: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x7064E1E: ??? (in /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.3200.4)
==5981== by 0x851CC4F: SaveTGA_RW (image.c:1824)
==5981== by 0x851CDA5: SaveTGA (image.c:1866)
==5981== by 0x8517B54: image_save (image.c:232)
==5981== by 0x585F7B: ??? (in /usr/bin/python3.13)
==5981== by 0x544EBA: _PyObject_MakeTpCall (in /usr/bin/python3.13)
==5981== by 0x56139A: _PyEval_EvalFrameDefault (in /usr/bin/python3.13)
==5981== by 0x55ABDB: PyEval_EvalCode (in /usr/bin/python3.13)
==5981== by 0x5D7351: ??? (in /usr/bin/python3.13)
==5981== by 0x56104D: _PyEval_EvalFrameDefault (in /usr/bin/python3.13)
==5981== Address 0xffffffffd9cd805c is not stack'd, malloc'd or (recently) free'd
==5981==
==5981== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Segmentation fault
fixing the bounds check should be as simple as that, but about the sdl segfault im not sure, best hope they fix it in sdl3?
Before i go in ive seen that youre considering removing TGA support here, but i love tga for its export speed. Maybe keep it simple, without any rle (if i want compression id pick png...) to make it stable.
While figuring out complications with huge surfaces i found that tga export is kind of buggy. TGA uses 2 bytes to store width and height, so its limited to 65535x65536 and there currently is no check for that, simple fix tho
Show fix idea
Further i can crash sdl even if its within bounds (might be a sld2 issue, not sure how to test sld3)
tiny python repl + huge valgrind log
fixing the bounds check should be as simple as that, but about the sdl segfault im not sure, best hope they fix it in sdl3?