Skip to content

Lazy Evaluation ​

phpmlkit/opal inherits libvips's lazy evaluation model. Operations are not executed when you call them — they are queued into a pipeline that runs only when you consume the result.

The pipeline model ​

Each transform appends a node to an internal VIPS pipeline graph. The graph is a directed acyclic graph of operations. No pixels are read, processed, or written until a terminal operation triggers evaluation.

php
use PhpMlKit\Opal\Image;

$image = Image::fromFile('large.tiff');  // header only — no pixel data decoded

// These methods build the pipeline but do NOT execute:
$pipeline = $image
    ->resize(1920, 1080)
    ->sharpen(sigma: 2.0)
    ->toGrayscale();

At this point, no image processing has happened. The original $image and the derived $pipeline are both lightweight objects holding VIPS operation references.

Terminal operations ​

The pipeline is evaluated when you call any of these methods:

php
// Save to disk — triggers full pipeline execution
$pipeline->toFile('output.jpg');

// Encode to a binary string
$bytes = $pipeline->toBuffer(ImageFormat::JPEG);

// Export as NDArray (also triggers evaluation)
$array = $pipeline->toArray(ChannelFormat::HWC);

// Export raw pixel buffer
$memory = $pipeline->toMemory();

TIP

You can chain any number of operations before calling a terminal method. VIPS will merge, reorder, and optimise the pipeline automatically.

Memory benefits ​

Lazy evaluation dramatically reduces memory usage. Consider this common pattern:

php
// No image data is held in PHP memory
$result = Image::fromFile('huge.tiff')
    ->resize(1920, 1080)
    ->sharpen(sigma: 2.0)
    ->toFile('output.jpg');

libvips streams pixel data through the pipeline in small tiles. It never decodes the full source image into memory. For a 100 MB TIFF resized to a 2 MB JPEG, peak memory usage stays near 2 MB, not 100 MB.

Contrast this with libraries that eagerly decode into a pixel buffer:

php
// Imagick loads the entire image into memory
$img = new Imagick('huge.tiff');       // 100 MB allocated
$img->resizeImage(1920, 1080, ...);    // another 50 MB allocated
$img->writeImage('output.jpg');        // then freed

When evaluation happens multiple times ​

Each terminal call traverses the pipeline independently. If you call save() and then toArray() on the same pipeline, the operations execute twice:

php
$pipeline = $image->resize(800, 600);

$pipeline->toFile('out.jpg');   // evaluated once
$array = $pipeline->toArray(); // evaluated again

If you need both outputs from the same evaluation, work with a single terminal operation:

php
$pipeline->toFile('out.jpg');   // evaluate once

// Reload from disk if you need both save and array
$saved = Image::fromFile('out.jpg');
$array = $saved->toArray();

Or restructure your pipeline to export first, then save the array result if appropriate.

Forcing evaluation explicitly ​

You typically do not need to force evaluation. If you want to check that a pipeline is valid without producing output, you can trigger evaluation by exporting raw pixel data:

php
try {
    $pipeline->toMemory();  // evaluate to validate
} catch (\Exception $e) {
    echo "Pipeline failed: " . $e->getMessage();
}

WARNING

Calling toMemory() on a heavy pipeline purely for validation will still process the full image. Prefer validating on a downscaled version in development.

VIPS optimisation notes ​

The VIPS library applies several optimisations automatically:

  • Operation fusion — adjacent point operations (brightness, contrast, gamma) are merged into a single linear transform.
  • Tile-based execution — images are processed in small tiles (typically 128x128) so the working set fits in CPU cache.
  • Region-of-interest tracking — only the pixels needed for the final output are computed.
  • Format-optimised decode — JPEG, WebP, and TIFF decoders support shrink-on-load, so you can decode at reduced resolution directly.

These optimisations are transparent. The same pipeline code benefits from them without changes.

Summary ​

AspectBehaviour
Operation executionDeferred until terminal method
Terminal methodstoFile(), toBuffer(), toArray(), toMemory()
Multiple terminalsEach call re-evaluates the pipeline
MemoryTile-based streaming; no full-resolution decode
Automatic fusionAdjacent point operations are merged by VIPS

Released under the MIT License.