Last updated on July 30th, 2026 at 11:36 pm
The main lesson: a PHP image generator can run out of memory even when the final image looks reasonable. The real problem is often peak memory: several full-size image canvases are alive at the same time.
What I learned
GD works with decoded images in memory. A compressed PNG, JPEG, or WebP file may be small on disk, but its decoded pixel data can use much more memory.
If a script repeatedly combines images without releasing old canvases, memory usage grows during processing. The error may appear during final encoding, even though the pressure started earlier.
Why the reported line can be misleading
The line that fails is not always the line that caused the problem. Encoding the final image needs extra temporary memory. If earlier canvases are still retained, that final allocation can push the process over its limit.
This changes the debugging question from “Why did encoding fail?” to “Which images were still alive when encoding started?”
A practical cleanup pattern
- Create the new canvas.
- Copy or draw the required pixels onto it.
- Release source canvases that will not be used again.
- Replace the working image with the new canvas.
- Release the final canvas after encoding completes.
$combined = imagecreatetruecolor($width, $height);
imagecopy($combined, $first, 0, 0, 0, 0, $firstWidth, $firstHeight);
imagecopy($combined, $second, 0, $firstHeight, 0, 0, $secondWidth, $secondHeight);
imagedestroy($first);
imagedestroy($second);
$workingImage = $combined;
The important part is timing: destroy an image only after its last use.
Always clean up after encoding
Output buffering and the final canvas should be cleaned up even when encoding fails. A try/finally block makes that intent explicit.
$bufferLevel = ob_get_level();
ob_start();
try {
imagewebp($workingImage, null, 80);
return (string) ob_get_clean();
} finally {
if (ob_get_level() > $bufferLevel) {
ob_end_clean();
}
imagedestroy($workingImage);
}
Common mistakes
- Cause: Keeping original images after resizing. Fix: release the original after the resized copy is complete.
- Cause: Retaining both old and new composition canvases. Fix: release the old canvas immediately after the copy succeeds.
- Cause: Raising the PHP memory limit first. Fix: measure and remove unnecessary live images before adding memory.
- Cause: Cleaning every output buffer. Fix: record the starting buffer level and close only the buffer created by the method.
How I would verify the fix
- Run the real image-generation path under the same memory limit as production.
- Use a representative large image, not only a tiny fixture.
- Confirm the output opens and has the expected dimensions.
- Run related tests and a PHP syntax check.
Conclusion
Memory errors in GD are usually easier to solve when I track object lifetimes instead of focusing only on the crash line. The concrete next action is to review every image-creation step and release each canvas immediately after its last copy or encoding operation.
