mojahed/image

PHP wrapper for the MdsImage binary — resize, crop, effects, watermark, batch processing, and image info. Requires no GD, Imagick, or any PHP image extension.

Maintainers

Package info

github.com/md-mojahed/MdsImage-php

pkg:composer/mojahed/image

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-06-02 10:50 UTC

This package is auto-updated.

Last update: 2026-06-02 10:52:14 UTC


README

PHP wrapper for the MdsImage.

Process images from PHP with zero extensions — no GD, no Imagick, no libvips.
All operations are handled by the MdsImage standalone binary. Drop the binary on any server and start processing.

Requirements

  • PHP >= 7.4
  • MdsImage binary

Install

composer require mojahed/image

Setup

Download the MdsImage binary for your platform and place it somewhere accessible:

# Linux
chmod +x MdsImage-linux-amd64
sudo mv MdsImage-linux-amd64 /usr/local/bin/MdsImage

# macOS
chmod +x MdsImage-mac-arm64
sudo mv MdsImage-mac-arm64 /usr/local/bin/MdsImage

Usage

Basic pattern

use Mojahed\Image;

// Set the binary path once, reuse $image everywhere
$image = Image::setBin('/usr/local/bin/MdsImage');

// input() opens a file and returns a chainable pipeline
// output() executes and saves the result
$image->input('photo.jpg')->resize(800, 600)->output('out.jpg');

Resize

// Exact resize — ignores aspect ratio
$image->input('photo.jpg')->resize(800, 600)->output('out.jpg');

// Fit within bounds — preserves aspect ratio, no crop
$image->input('photo.jpg')->fit(1200, 800)->output('out.jpg');

// Fit but never enlarge a small image
$image->input('photo.jpg')->noEnlarge()->fit(1200, 800)->output('out.jpg');

// Fill exact dimensions — crops from center
$image->input('photo.jpg')->fill(400, 400)->output('out.jpg');

// Smart thumbnail (same as fill, clearer intent)
$image->input('photo.jpg')->thumb(300, 300)->output('thumb.jpg');

Crop & Canvas

// Crop a region: 500×400 starting at pixel (50, 30)
$image->input('photo.jpg')->crop(500, 400, 50, 30)->output('out.jpg');

// Pad canvas to exact size with a background colour
// (fit first to preserve aspect ratio, then pad to square)
$image->input('photo.jpg')
      ->fit(800, 800)
      ->pad(800, 800, '#F5F5F5')
      ->output('square.jpg');

// Add a border on all sides
$image->input('photo.jpg')->border(20, '#000000')->output('out.jpg');

// Trim uniform background from edges (default threshold = 10)
$image->input('photo.jpg')->trim()->output('trimmed.jpg');
$image->input('photo.jpg')->trim(25)->output('trimmed.jpg'); // more aggressive

Transforms

$image->input('photo.jpg')->flip('h')->output('out.jpg');   // horizontal mirror
$image->input('photo.jpg')->flip('v')->output('out.jpg');   // upside-down
$image->input('photo.jpg')->rotate(90)->output('out.jpg');  // 90 | 180 | 270
$image->input('photo.jpg')->autoOrient()->output('out.jpg'); // fix EXIF orientation

Color & Adjustments

$image->input('photo.jpg')->brightness(30)->output('out.jpg');    // -100 to 100
$image->input('photo.jpg')->contrast(20)->output('out.jpg');      // -100 to 100
$image->input('photo.jpg')->saturation(40)->output('out.jpg');    // -100 to 100
$image->input('photo.jpg')->gamma(1.5)->output('out.jpg');        // positive float
$image->input('photo.jpg')->hue(120)->output('out.jpg');          // -360 to 360
$image->input('photo.jpg')->grayscale()->output('out.jpg');
$image->input('photo.jpg')->sepia()->output('out.jpg');
$image->input('photo.jpg')->invert()->output('out.jpg');
$image->input('photo.jpg')->tint('#FF6600')->output('out.jpg');
$image->input('photo.jpg')->stripExif()->output('out.jpg');

Filters

$image->input('photo.jpg')->blur(2.0)->output('out.jpg');     // Gaussian blur radius
$image->input('photo.jpg')->sharpen(1.5)->output('out.jpg');  // unsharp mask amount

Watermark

// Positions: topleft | topright | bottomleft | bottomright | center
$image->input('photo.jpg')->watermark('logo.png')->output('out.jpg');
$image->input('photo.jpg')->watermark('logo.png', 'topleft', 60)->output('out.jpg');

Output Options

// JPEG quality (default 85)
$image->input('photo.jpg')->thumb(300, 300)->quality(95)->output('hq.jpg');

// Force output format
$image->input('photo.jpg')->resize(400, 400)->format('png')->output('out.png');

// Suppress binary progress output
$image->input('photo.jpg')->thumb(300, 300)->quiet()->output('out.jpg');

Output Modes

// Save to file
$image->input('photo.jpg')->thumb(300, 300)->output('thumb.jpg');

// Write to stdout (for streaming HTTP responses)
$image->input('photo.jpg')->thumb(300, 300)->output('-');

// Raw base64 string
$b64 = $image->input('photo.jpg')->thumb(300, 300)->toBase64();

// data: URI — ready for <img src="">, CSS, or JSON API payloads
$uri = $image->input('photo.jpg')->thumb(300, 300)->toDataUri();
echo '<img src="' . $uri . '">';

Image Info

// Get all metadata as an associative array
$info = $image->input('photo.jpg')->info();

echo $info['width'];                    // 1024
echo $info['height'];                   // 768
echo $info['format'];                   // JPEG
echo $info['mime_type'];                // image/jpeg
echo $info['file_size_human'];          // 1.2 MB
echo $info['megapixels'];               // 0.79
echo $info['aspect_ratio'];             // 4:3
echo $info['color_mode'];               // RGB
echo $info['has_alpha'];                // false
echo $info['average_color']['hex'];     // C0BCAB

foreach ($info['dominant_colors'] as $c) {
    echo "#{$c['hex']}  rgb({$c['r']},{$c['g']},{$c['b']})\n";
}

// JPEG-specific (present only for JPEG files)
if (!empty($info['jpeg'])) {
    echo $info['jpeg']['exif_make'];         // Apple
    echo $info['jpeg']['exif_model'];        // iPhone 15 Pro
    echo $info['jpeg']['exif_datetime'];     // 2024:03:15 14:22:00
    echo $info['jpeg']['exif_iso'];          // 100
    echo $info['jpeg']['exif_fnumber'];      // 1.8
    echo $info['jpeg']['exif_exposure_time']; // 1/1000 s
    echo $info['jpeg']['exif_focal_length_mm']; // 24
    if ($info['jpeg']['exif_gps_present']) {
        echo $info['jpeg']['exif_gps_lat'] . ', ' . $info['jpeg']['exif_gps_lon'];
    }
}

// PNG-specific (present only for PNG files)
if (!empty($info['png'])) {
    echo $info['png']['color_type'];         // RGBA
    echo $info['png']['interlaced'];         // false
    // text_chunks: author, description, copyright, etc.
}

// Get info as a JSON string
$json = $image->input('photo.jpg')->infoJson();

Chained Pipelines

Operations are applied in the exact order you call them:

// Full production pipeline
$image->input('raw-upload.jpg')
      ->autoOrient()
      ->noEnlarge()->fit(1920, 1080)
      ->brightness(5)
      ->contrast(10)
      ->saturation(15)
      ->sharpen(0.8)
      ->watermark('brand.png', 'bottomright', 75)
      ->stripExif()
      ->quality(92)
      ->output('final.jpg');

// Thumbnail with sepia effect
$image->input('photo.jpg')
      ->thumb(300, 300)
      ->sepia()
      ->output('thumb-sepia.jpg');

// E-commerce product image (exact square, no crop, no distortion)
$image->input('product.jpg')
      ->trim()
      ->noEnlarge()->fit(800, 800)
      ->pad(800, 800, '#FFFFFF')
      ->sharpen(0.5)
      ->quality(90)
      ->output('product-800.jpg');

Batch Processing

Process multiple files concurrently with worker parallelism:

// All JPEGs in a folder → thumbnails
$results = $image->batch()
    ->glob('photos/*.jpg')
    ->outDir('thumbs/')
    ->thumb(300, 300)
    ->quality(85)
    ->run();

// Process all images in a directory with a custom filename pattern
$results = $image->batch()
    ->dir('uploads/')
    ->outDir('processed/')
    ->pattern('{name}-800w{ext}')    // {name}, {ext}, {base} tokens
    ->noEnlarge()->fit(800, 600)
    ->sharpen(1.0)
    ->quality(88)
    ->workers(4)
    ->run();

// Convert folder to PNG
$results = $image->batch()
    ->glob('assets/*.jpg')
    ->outDir('assets/png/')
    ->format('png')
    ->run();

// Check results
foreach ($results as $r) {
    if ($r['error'] !== null) {
        echo "{$r['input']}: {$r['error']}\n";
    } else {
        echo "{$r['input']}{$r['output']}\n";
    }
}

Batch methods

Method Description
->glob('photos/*.jpg') Input files matching a glob pattern
->dir('uploads/') All images in a directory
->outDir('thumbs/') Output directory (required)
->pattern('{name}-sm{ext}') Output filename pattern
->format('png') Force output format
->quality(85) JPEG quality
->workers(4) Parallel workers (default: CPU count, max 8)
->quiet() Suppress progress output

All image operations (resize, thumb, sepia, watermark, etc.) are available on BatchBuilder too.

Error Handling

try {
    $image->input('missing.jpg')->resize(800, 600)->output('out.jpg');
} catch (\RuntimeException $e) {
    echo $e->getMessage();
    // "MdsImage failed (exit 1): input file not found: missing.jpg"
}

Exit code 1 = processing or IO error.
Exit code 2 = usage error (won't occur from the PHP wrapper under normal use).

Supported Formats

Direction Formats
Input JPEG, PNG, GIF, BMP, TIFF, WebP
Output JPEG, PNG

License

MIT © Md Mojahedul Islam