Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@ run-tests.log
# Added by horde-components QC --fix-qc-issues
# PHPUnit Cache (other)
/.phpunit.cache

# Added by horde-components QC --fix-qc-issues
# Horde installer plugin runtime data
/var/
# Horde installer plugin web-accessible directory
/web/
15 changes: 14 additions & 1 deletion .horde.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ license:
uri: http://www.horde.org/licenses/lgpl21
dependencies:
required:
php: ^7.4 || ^8
php: ^8.1
composer:
horde/exception: ^3
horde/stream: ^2
Expand All @@ -47,3 +47,16 @@ dependencies:
zlib: '*'
imagick: '*'
vendor: horde
autoload:
psr-0:
Horde_Image: lib/
psr-4:
Horde\Image\: src/
autoload-dev:
psr-4:
Horde\Image\Test\: test/
keywords:
- gd
- imagick
- exif
- metadata
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Horde\Image

Image manipulation library for PHP 8.1+ with multiple backend support.

## Features

- **Multiple backends** - ImagickDriver, GdDriver, ImDriver (CLI), SvgDriver, PngDriver (pure PHP), NullDriver
- **Interface Segregation** - ImageDriver for single images, SequenceDriver for animation/multi-frame
- **Immutable resources** - all operations return new ImageResource instances
- **Drawing API** - path-based DrawingContext with state stack, SVG output and brush shapes
- **Filters** - Brightness, Contrast, Gamma, Grayscale, Colorize, Modulate, Sepia, Negate, Sharpen, Pixelate, Blur
- **Effects** - TextWatermark, PolaroidImage, PhotoStack, Border, DropShadow, RoundCorners, SmartCrop, CenterCrop, LiquidResize, Composite
- **Metadata** - EXIF/IPTC/XMP reading with GPS parsing and MakerNote support
- **Sequences** - multi-frame image support (animated GIF, APNG) via ImagickDriver
- **Color utilities** - named colors (140+ CSS names), lighten/darken/intensify and brightness calculation
- **Named font sizes** - FontSize enum with bidirectional point-value lookup
- **DI-friendly** - ImageFactory wrapper, all drivers are constructor-injectable

## Requirements

- PHP 8.1+
- At least one of: ext-imagick, ext-gd or ImageMagick CLI tools. A Pure PHP driver exists for a subset of features.

## Installation

```bash
composer require horde/image
```

## Quick Start

```php
use Horde\Image\Driver\ImagickDriver;
use Horde\Image\Color\Color;
use Horde\Image\Format\ImageFormat;
use Horde\Image\Geometry\Size;

$driver = new ImagickDriver();
$image = $driver->create(new Size(800.0, 600.0), Color::named('white'));

$resized = $image->resize(new Size(400.0, 300.0));
$data = $driver->encode($resized, ImageFormat::JPEG);
file_put_contents('output.jpg', $data);
```

See [doc/USAGE.md](doc/USAGE.md) for full documentation.
See [doc/UPGRADING.md](doc/UPRGADING.md) for upgrading calling code from legacy lib/ format or older versions.
See [doc/COPYING-EXIF.md](doc/COPYING-EXIF.md) for copyright and licensing clarification on exifer code included.

## License

LGPL-2.1-only. See [LICENSE](LICENSE) for details.
8 changes: 6 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"time": "2026-05-19",
"repositories": [],
"require": {
"php": "^7.4 || ^8",
"php": "^8.2",
"horde/exception": "^3 || dev-FRAMEWORK_6_0",
"horde/stream": "^2 || dev-FRAMEWORK_6_0",
"horde/support": "^3 || dev-FRAMEWORK_6_0",
Expand All @@ -37,11 +37,15 @@
"autoload": {
"psr-0": {
"Horde_Image": "lib/"
},
"psr-4": {
"Horde\\Image\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Horde\\Image\\Test\\": "test/"
"Horde\\Image\\Test\\": "test/",
"Horde\\Image\\Test\\Unit\\": "test/unit/"
}
},
"config": {
Expand Down
12 changes: 7 additions & 5 deletions doc/Horde/Image/COPYING-EXIF → doc/COPYING-EXIF.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
This library contains Exifer code which is originally licensed under GPL-2.0
The original author Jake Olefsky of Exif/Parser/* gave explicit approval that
the code can be included into the Horde code base.
Class for dealing with Exif data using a bundled PHP library based on the
Exifer code written by and Copyright 2003 Jake Olefsky
# EXIF Parser License

This library contains Exifer code which is originally licensed under GPL-2.0.

The original author Jake Olefsky of Exif/Parser/* gave explicit approval that the code can be included into the Horde code base.

Class for dealing with Exif data using a bundled PHP library based on the Exifer code written by and Copyright 2003 Jake Olefsky.

See: http://www.offsky.com/software/exif/index.php
28 changes: 0 additions & 28 deletions doc/Horde/Image/UPGRADING.rst

This file was deleted.

150 changes: 150 additions & 0 deletions doc/UPGRADING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Upgrading from Horde_Image (lib/) to Horde\Image (src/)

## Overview

The new `Horde\Image` API under `src/` coexists with the legacy `Horde_Image` classes in `lib/`. Both autoloaders work simultaneously and so migration can be incremental.

## Namespace Change

| Legacy | Modern |
|--------|--------|
| `Horde_Image_Base` | `Horde\Image\Driver\ImageDriver` (interface) |
| `Horde_Image_Imagick` | `Horde\Image\Driver\ImagickDriver` |
| `Horde_Image_Gd` | `Horde\Image\Driver\GdDriver` |
| `Horde_Image_Im` | `Horde\Image\Driver\ImDriver` |
| `Horde_Image_Svg` | `Horde\Image\Driver\SvgDriver` |
| `Horde_Image_Png` | `Horde\Image\Driver\PngDriver` |
| `Horde_Image_Null` | `Horde\Image\Driver\NullDriver` |
| `Horde_Image_Color` | `Horde\Image\Color\Color` |
| `Horde_Image_Exif` | `Horde\Image\Metadata\Reader\*` |
| `Horde_Image_Effect_*` | `Horde\Image\Effect\*` |

## Architecture Changes

### Driver/Resource Split

Legacy had a single object per image that was both driver and resource:

```php
// Legacy
$image = new Horde_Image_Imagick(['width' => 100, 'height' => 80]);
$image->resize(50, 40);
$data = $image->raw();
```

Modern separates the driver (stateless factory) from the resource (image data):

```php
// Modern
$driver = new ImagickDriver();
$image = $driver->create(new Size(100.0, 80.0), Color::named('white'));
$resized = $image->resize(new Size(50.0, 40.0));
$data = $driver->encode($resized, ImageFormat::PNG);
```

### Immutability

Legacy mutated images in place. Modern returns new instances:

```php
// Legacy (mutates $image)
$image->resize(50, 40);
$image->flip();

// Modern (returns new resources)
$resized = $image->resize(new Size(50.0, 40.0));
$flipped = $resized->flip(horizontal: true);
// $image is unchanged
```

### Interface Segregation

Legacy had one large abstract class. Modern splits capabilities:

- `ImageDriver` - single-image operations (all backends)
- `SequenceDriver extends ImageDriver` - animation/multi-frame (ImagickDriver only)
- `ImageResource` - per-image operations (resize, crop, rotate, flip, filter, effect)
- `DrawingContext` - path-based 2D drawing

### Color

Legacy used hex strings and arrays. Modern uses a typed value object:

```php
// Legacy
$image->rectangle(0, 0, 100, 100, 'red', '#ff0000');

// Modern
$ctx = $image->drawingContext();
$ctx->setFillColor(Color::named('red'));
$ctx->rect(0.0, 0.0, 100.0, 100.0);
$ctx->fill();
```

### Font Sizes

Legacy used string names passed to methods. Modern uses a typed enum:

```php
// Legacy
$image->text('Hello', 10, 20, 'arial', 'large');

// Modern
use Horde\Image\Drawing\FontSize;
use Horde\Image\Drawing\TextStyle;

$ctx->text('Hello', 10.0, 20.0, new TextStyle(size: FontSize::Large));
```

### Effects

Legacy used string-keyed parameter arrays. Modern uses typed objects:

```php
// Legacy
$image->addEffect('border', ['bordercolor' => '#000', 'borderwidth' => 2]);

// Modern
$bordered = $image->effect(new Border(width: 2.0, color: Color::named('black')));
```

### Metadata

Legacy had a single `Horde_Image_Exif` class. Modern has pluggable readers:

```php
// Legacy
$exif = new Horde_Image_Exif(new Horde_Image_Exif_Bundled());
$data = $exif->getData('photo.jpg');

// Modern
$reader = new BundledReader();
$meta = $reader->read('photo.jpg');
$gps = $meta->gps();
```

## Removed Features

| Feature | Reason | Alternative |
|---------|--------|-------------|
| SWF/Flash backend | Flash EOL 2020 | None needed |
| HTTP response helpers | Application layer concern | Use PSR-7 response objects |
| Global `Horde_Image::factory()` | Static pattern from Horde 3/4 era, no longer desirable | Use `ImageFactory` with DI |

## Migration Strategy

1. Add `"Horde\\Image\\": "src/"` to your PSR-4 autoload (already in composer.json)
2. New code uses `Horde\Image\*` classes
3. Existing code continues using `Horde_Image_*` from `lib/`
4. Migrate incrementally per-feature, per-module
5. Once all consumers are migrated, remove `lib/` dependency

---

## Older Upgrades (Horde_Image 1.x/2.x)

### Upgrading to 2.3.0

- **Horde_Image_Effect_Gd_Border** - Replaces and fixes the generic border effect implementation.
- **Horde_Image_Effect_Imagick_LiquidResize** - A `ratio` parameter has been added.
- **Horde_Image_Rgb** - This class holds a map from HTML color names to RGB values and replaces the global `$horde_image_rgb_colors` variable.
Loading
Loading