Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Gfx Php Laravel Package

mike42/gfx-php

Pure PHP graphics library for reading, writing and processing raster images (PNG, GIF, BMP, Netpbm) without Gd or Imagick. Supports scaling, cropping, format conversion and colorspace transforms, making image handling more portable. Requires PHP 7+ and zlib for PNG.

View on GitHub
Deep Wiki
Context7

Getting Started

gfx-php is a pure-PHP raster library — no GD, no Imagick. You reach for it in a Laravel app when the host environment can't be trusted to have image extensions compiled in (shared hosting, distroless containers, some managed PHP runtimes) or when you specifically need a format the extensions handle poorly, most notably BMP.

Install it properly. The README's install line is wrong (composer install mike42/gfx-php will not work); use:

composer require mike42/gfx-php

Requirements are PHP 7.0+ and ext-zlib (needed for PNG decode). There is no service provider, no facade, no config file, no auto-discovery — nothing to publish. It's a plain library you call directly.

Where to look first:

  1. vendor/mike42/gfx-php/src/Image.php — the abstract entry point; Image::fromFile() and Image::write() are the whole public surface you'll use 90% of the time.
  2. vendor/mike42/gfx-php/examples/ — the most reliable documentation in the repo; the readthedocs site (gfx-php.readthedocs.io) covers File formats and Image operations.
  3. The concrete raster classes (RgbRasterImage, IndexedRasterImage, GrayscaleRasterImage, BlackAndWhiteRasterImage) — this is where your loaded image actually lands, and it determines what conversions are cheap vs. lossy.

First use case — legacy BMP to web-friendly PNG in a queue job:

use Mike42\GfxPhp\Image;

$img = Image::fromFile(storage_path('app/legacy/report-4412.bmp'));
$img->write(storage_path('app/public/report-4412.png'));

That's genuinely the entire happy path. Format is inferred from the file extension on both ends. Don't expect a fluent wrapper — you get an Image object, a handful of operations, and write().

Implementation Patterns

Wrap it behind a contract before your first commit

With ~28 stars, zero dependents, and a single maintainer, treat this as a dependency you may need to swap. Bind it behind an interface so migrating to Intervention Image or Imagick later is a one-file change:

// app/Services/Images/ImageConverter.php
interface ImageConverter
{
    public function convert(string $sourcePath, string $targetPath): void;
    public function thumbnail(string $sourcePath, string $targetPath, int $w, int $h): void;
}
// app/Services/Images/GfxPhpConverter.php
namespace App\Services\Images;

use Mike42\GfxPhp\Image;

class GfxPhpConverter implements ImageConverter
{
    public function convert(string $sourcePath, string $targetPath): void
    {
        Image::fromFile($sourcePath)->write($targetPath);
    }

    public function thumbnail(string $sourcePath, string $targetPath, int $w, int $h): void
    {
        Image::fromFile($sourcePath)->scale($w, $h)->write($targetPath);
    }
}
// app/Providers/AppServiceProvider.php (register())
$this->app->bind(ImageConverter::class, GfxPhpConverter::class);

Bridge Laravel's Storage to filesystem paths

The library only speaks filenames, not streams or PSR-7. For local/public disks just hand it a real path:

$path = Storage::disk('local')->path('imports/chart.bmp');
Image::fromFile($path)->write(Storage::disk('public')->path('charts/chart.png'));

For an S3 disk there is no path. Download to a temp file, convert, upload back, clean up — and do it in a try/finally:

$tmpIn  = tempnam(sys_get_temp_dir(), 'gfx') . '.bmp';
$tmpOut = tempnam(sys_get_temp_dir(), 'gfx') . '.png';

try {
    file_put_contents($tmpIn, Storage::disk('s3')->get($key));
    Image::fromFile($tmpIn)->write($tmpOut);
    Storage::disk('s3')->put($targetKey, fopen($tmpOut, 'r'));
} finally {
    @unlink($tmpIn);
    @unlink($tmpOut);
}

Note the deliberate .bmp suffix on the tempnam() result — the extension is the format selector, so a bare tempnam file with no extension will fail to load.

Push conversions to a queue

Pure-PHP pixel work is CPU-bound and memory-hungry. A job with explicit limits is the safe default:

class ConvertImageJob implements ShouldQueue
{
    public int $timeout = 120;
    public int $tries = 2;

    public function __construct(public string $disk, public string $source, public string $target) {}

    public function handle(ImageConverter $converter): void
    {
        $in  = Storage::disk($this->disk)->path($this->source);
        $out = Storage::disk($this->disk)->path($this->target);
        $converter->convert($in, $out);
    }
}

Set memory_limit on the worker or in the job's handle() for known-large inputs; a 4000×4000 BMP expands to roughly 48 MB as an RGB raster before PHP array/object overhead.

Colorspace reduction is the real differentiator

Beyond format conversion, gfx-php exposes raster-model conversions that the extensions make awkward. The documented operations include toRgb(), toGrayscale(), toBlackAndWhite(), and toIndexed($colors). Typical Laravel use — generating a 1-bit asset for a thermal receipt printer or a monochrome e-ink pipeline:

$img = Image::fromFile($src);
$img->toBlackAndWhite()->write($dest . '.pbm');   // Netpbm PBM

For an indexed/palette target (GIF, small PNG):

Image::fromFile($src)->toIndexed(256)->write($dest . '.gif');

Chain these into a named pipeline rather than scattering them through controllers.

Validate before you parse

Untrusted uploads should never hit the decoder blind. Add a form request rule on the extension, plus a cheap header check:

'upload' => ['required', 'file', 'max:10240', 'mimes:bmp,png,gif,ppm,pgm,pbm,wbmp'],

Then guard dimensions in your service. The library throws on malformed input (since v0.4.1) rather than warning, which is what you want, but a truncated or hostile header can still cost you memory before it decides to throw.

Testing

Keep tiny fixture files (a 2×2 BMP, a 1-bit PBM) in tests/Fixtures/. Round-trip assertions are simplest: BMP → PNG → read back → compare getWidth()/getHeight(). This is also the one genuinely nice property of the package — your CI image no longer needs ext-gd just to assert something about a file.

Gotchas and Tips

There is no JPEG support. Not input, not output. No WebP, no TIFF, no SVG/PDF (explicitly out of scope per the README). If your upload form accepts JPEGs — and in a Laravel app it almost certainly does — gfx-php is at best a complement to GD/Imagick, not a replacement. This single fact kills most "drop-in replacement" plans.

Format is chosen by file extension, case-sensitively. .jpg will not fall back to anything; you'll get an exception. Sanitise extensions and normalise case before calling fromFile(). A filename from UploadedFile::getClientOriginalName() is attacker-controlled — never let it decide the decoder.

Transparency is flattened to white on PNG read. Per the v0.3 release notes, alpha is mixed against a white background. If you're compositing logos onto dark backgrounds, this library silently destroys your asset. Test with a transparent PNG before promising anything.

Animated GIFs: only the first frame is read (v0.4). Writing a GIF produces a single frame. Don't build anything resembling an animated-GIF feature on this.

Indexed formats lose color silently. toIndexed(256) quantises; toBlackAndWhite() dithers or thresholds depending on implementation. Both are destructive and both return a new image — the docs' operations page is worth reading once end-to-end so you know which calls are in-place and which return a value.

Rotation and EXIF orientation are not in the documented feature set. Scaling, cropping, format conversion, and colorspace transforms are. If you need to honour EXIF Orientation on phone photos, you're reading EXIF via ext-exif and you probably need GD/Imagick anyway.

Performance will surprise you. Per-pixel PHP loops over a multi-megapixel image are orders of magnitude slower than libgd. Benchmark on realistic file sizes before you wire it into a synchronous request path. For batch archives, chunk into jobs of 20–50 files and cache outputs — conversions are deterministic given inputs, so a hash-keyed cache is trivially correct.

Catch \Throwable, not just \Exception. Older releases raised warnings during PNG parsing before failing; newer ones throw. Wrap conversions and log the source filename, since the exception messages are low-level and don't say which upload broke.

Licensing needs an actual read. composer.json declares LGPL-2.1, while the README asks contributors to license under LGPLv3 or later. Those are different. For a typical Laravel SaaS you're dynamically linking through Composer (fine under LGPL), but if legal is strict about the discrepancy or you distribute the application, get it reviewed rather than assuming.

The README's install command is a bug. composer install mike42/gfx-php is not valid Composer syntax. It's composer require. Copy-pasting from the README is the fastest way to a confusing error.

Extension points, if you need them: the format readers/writers and the raster model classes are the seams. Adding a format means implementing the reader/writer against the raster classes; adding a transform means operating on the concrete raster type. Both are doable but undocumented — read the existing PNG or Netpbm implementation as your template.

Debugging tip: get_class($img) after fromFile() tells you which raster model you got (RGB / indexed / grayscale / 1-bit). Half of "the conversion looks wrong" tickets are an indexed image quietly being upconverted or a truecolor image being downgraded. When building fixtures, prefer PPM/PGM — uncompressed, trivially inspectable, and supported both directions.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route