PHP library / CLI

PicoWebP

Convert PNG, BMP and baseline JPEG images to WebP on PHP hosts without GD, Imagick or cwebp. No external conversion service required.

DownloadVersion 1.1.0
PicoWebP
PicoWebP

About PicoWebP

PicoWebP adds WebP conversion to PHP applications when the server’s image tools are unavailable or outside your control. Install it through Composer and convert images inside your application, without adding an image extension, running a command-line encoder or sending files to a conversion service.

Where PicoWebP fits

  • Shared and restricted hosting. Your application needs WebP output, but you cannot enable GD or Imagick, install cwebp, or run shell commands.
  • Existing PHP applications and APIs. Add image conversion to an established service without introducing native image-processing software into its deployment.
  • PHP plugins and distributed software. Provide a conversion fallback when customers’ hosting environments have different image capabilities. Your application can use native tools where available and PicoWebP where they are missing.

PHP on AWS Lambda and Bref

PicoWebP is also an option to consider when packaging image-processing dependencies makes a serverless deployment more complicated.

Bref supports GD and Imagick through additional Lambda layers and Docker images. PicoWebP provides another approach: keep WebP encoding in the PHP application itself. Whether that is a useful trade-off depends on your image sizes, conversion volume and execution limits.

Supported images

The bundled readers handle PNG, BMP and baseline JPEG files without GD. You can also pass raw RGB pixels directly to the encoder.

PicoWebP preserves transparency and embedded ICC colour profiles. Colour compression is lossy; transparency is compressed losslessly. Existing WebP files are recognised from their contents and skipped by default, even when the filename has the wrong extension.

Progressive JPEG input and re-encoding an existing WebP require GD.

Performance and background processing

Pure-PHP encoding takes substantially more CPU time than native tools. In the recorded benchmark, encoding a roughly one-megapixel image took about 25 seconds with the default settings. Larger images can take minutes.

Use PicoWebP for background conversion with sensible image-size limits. Where a native encoder is available, it is generally the better choice for processing large volumes of images.

For longer jobs, resumable encoding lets your application save progress and continue in another invocation. Your application supplies the scheduling and state storage; splitting the work does not make the conversion faster.

Installation and requirements

Install the package with composer require nikocodes/picowebp.

PicoWebP requires PHP 8.1 or newer and zlib. It includes a PHP API and a command-line interface.

See the installation instructions, examples and benchmarks.

For Node.js and browser applications, see PicoWebPJS.

Notes & updates

  • Writing WebP files in PHP, without GD or libwebp

    Published

    I’ve published PicoWebP, a WebP encoder written in PHP that works without GD, Imagick, libwebp or cwebp. It preserves transparency and colour profiles, supports resumable conversion, and trades speed for the ability to run on hosts without native image tools.

Release history

Version 1.1.0

Added

  • BmpReader — reads BMP files without GD. Supports the OS/2 core header, BITMAPINFOHEADER and V2–V5 headers at 1, 4, 8, 16, 24 and 32 bits per pixel, including palettes, bottom-up and top-down rows, BI_RLE8 and BI_RLE4, channel masks and embedded V5 ICC profiles.
  • BMP transparency through a declared alpha mask, including fully transparent images. The unused fourth byte of a 32-bit BI_RGB file is ignored.
  • BMP metadata in ImageMeta: dimensions, bit depth, storage method and alpha capability. ImageInput uses the bundled BMP reader on every host, including those with GD installed.
  • BMP input in the CLI and file-encoding example. Truncated pixel data and incomplete RLE streams are rejected before an output file is replaced.
  • BMP fixtures in the test suite, with comparisons against GD and, when magick is available, ImageMagick. Includes checks for transparent and opaque alpha channels, unused masks, profile offsets and malformed RLE data.

Changed

  • Refresh the README with Packagist installation, BMP examples, supported layouts, and reader timing and memory measurements.

Notes

  • The reader scales 5- and 6-bit colour channels to eight bits using rounding. Another decoder may differ by one step for some colours.

External download

Download 1.1.0
Version 1.0.1

Fixed

  • Cap VP8 chroma DC quantisation at 132 so low-quality output matches the encoder's reconstruction and the decoder's prediction state.
  • Preserve adapted coefficient probabilities across exportState() / importState(), keeping resumed output byte-identical to single-shot encoding.
  • Decode separate and reordered baseline JPEG scans into the correct component planes, with one block per MCU for single-component scans.
  • Reset PNG transparency keys and JPEG ICC, Adobe colour-transform, and restart settings when a reader instance decodes another image.
  • Make the CLI default to its advertised quality 80, honour --alpha=fail for raw alpha planes, and return exit code 1 for invalid reader or encoder input.
  • Report explicitly selected alpha filters correctly and reject invalid filter values, empty alpha dimensions, invalid VP8 dimensions, and RGB buffer lengths.
  • Remove a PHP 8.1+ float-to-integer deprecation from the test fixture and correct stale encoder comments. Add regression coverage for the fixes, including libwebp reconstruction checks and optional cjpeg scan fixtures.

External download

Download 1.0.1
Version 1.0.0

First release. picowebp writes VP8 lossy WebP key frames in pure PHP — no GD, no Imagick, no cwebp, no libwebp, no shelling out — for the host that has no WebP support at all.

Added

Encoder

  • PicoWebP\Vp8\Vp8LossyEncoder — takes a packed RGB string and returns a complete WebP file. fromQuality() / qIndexForQuality() map a 0–100 quality onto the VP8 quantiser.
  • 16×16 and 4×4 intra prediction (all ten B_PRED modes) under a rate-distortion mode search, coefficient-probability adaptation, macroblock skip flags, and an optional per-coefficient trellis pass — off by default, because it buys ~14% fewer bytes at ~2.8 dB of PSNR on a real photograph.
  • CPU/bytes knobs at both the API and the CLI: enableRd, rdLambda, rdPruneCandidates, adaptProbs, enableBPred, enableSkipFlags, enableTrellis, and the --no-rd, --no-adapt-probs, --prune, --trellis, --no-skip flags that go with them.

Container features

  • Alpha: a translucent source is written as VP8X + ALPH + VP8 , with the alpha plane coded losslessly and byte-exactly; fully opaque images keep the simple VP8 container. --alpha=keep|flatten|fail, --alpha-filter=0..3.
  • Colour profiles: PNG iCCP and JPEG APP2 profiles are carried through to an ICCP chunk. --icc=FILE, --no-icc.

Bundled readers — pure PHP, no image extension required

  • PngReader — every colour type and bit depth, interlaced or not, palettes and tRNS, iCCP. Palette images are expanded to true colour before anything reads a pixel, and all eight bits of alpha are kept where libgd rounds to seven.
  • JpegReader — baseline sequential 8-bit JPEG, greyscale or YCbCr, every sampling factor, restart markers, APP2 ICC.
  • ImageInput — one entry point over both, plus GD where GD happens to be installed: pixels(), load(), isWebp(), mayHaveAlpha(), iccProfile(), and the $preferBundled / $gdAvailable switches.

An input that is already a WebP

  • ImageMeta sniffs RIFF....WEBP on the file's bytes, not its extension, and reports real geometry for the VP8X, VP8 and VP8L containers.
  • ImageInput::pixels() throws SkippedInput — a RuntimeException, so a caller that has never heard of it keeps working — carrying reason(), slug() and meta. --reencode-webp overrides it, and that path needs GD.

Command line

  • bin/picowebp in.jpg out.webp, with --rgb=WxH and --alpha-plane=FILE for headerless input, --dump-yuv=FILE, --json, --bundled, --no-gd, --reencode-webp.
  • Exit codes a queue can act on: 0 encoded, 1 error, 3 --alpha=fail refused, 4 skipped. A destination that cannot be written — a missing directory, a directory, an unwritable --dump-yuv path — is a failure with a message rather than a save that never happened.

Resumable path

  • PlaneStore decodes once and stages the padded planes to disk; the encoder then works in bands of macroblock rows — beginBand(), encodeRowRange(), compactReconWindow(), finishFrame() — with exportState() / importState() carrying a part-done frame across invocations. Output is byte-identical to the single-shot encoder. It costs about twice the CPU and buys memory headroom and resumability, not speed.

Everything else

  • PicoWebP\Spike\Vp8lEncoder, the internal lossless helper that supplies the headerless stream an ALPH chunk needs. Not a general-purpose lossless encoder, and not a PNG replacement.
  • examples/encode.php and examples/chunked-queue.php.
  • php tests/run.php (also composer test): bitstream structure, the alpha and ICC containers, PSNR against the source when dwebp is on PATH, both the bundled readers and the GD path, the WebP-skip route end to end, and the CLI's failure exit codes.

Notes

  • Requires PHP 8.1+ and ext-zlib. ext-gd is optional: it is faster where it exists, and it is the only route to a progressive JPEG or to re-encoding a WebP source.
  • Progressive JPEG is not decoded without GD. The bundled reader refuses it by name, saying which variant it found, rather than guessing.
  • BMP, GIF, TIFF/TIF and raw Y′CbCr inputs are not implemented.
  • This is a fallback encoder, not a libwebp replacement. Expect roughly 200–300× libwebp's CPU, a 377 s / 84 MB queue job for one 12 MP frame, and up to 21% larger files on flat logos and UI artwork. The measured tables, and what would move them, are in the README.

External download

Download 1.0.0