some fundamentals are actually working

This commit is contained in:
Joby Elliott 2020-10-01 16:07:33 -06:00
parent 67b4bd1209
commit 5cac6c5f64
15 changed files with 316 additions and 81 deletions

2
.gitignore vendored
View file

@ -2,3 +2,5 @@ composer.phar
/vendor/
composer.lock
debug.log
/examples/out/*
!.gitkeep

View file

@ -3,13 +3,23 @@
[![Build Status](https://travis-ci.org/jobyone/image-transform.svg?branch=main)](https://travis-ci.org/jobyone/image-transform)
[![Coverage Status](https://coveralls.io/repos/github/jobyone/image-transform/badge.svg?branch=main)](https://coveralls.io/github/jobyone/image-transform?branch=main)
A tightly-focused library for performing a very limited set of simple image transformations. This library's purpose is to eschew the standard kitchen sink approach to PHP image libraries in favor of high performance, wide driver support, and a dead simple API.
## Roadmap
## Current state
This library is under active development, and until a 1.0 release is made you should expect it to potentially be broken, and unexpectedly and dramatically change its API and functionality. That said, I *do* have use of this thing for work, so I'm probably going to be working pretty dang hard on it, and hope to have a stable release by about November of 2020.
### Current progress
| Driver | Rotate | Mirror | Resize | Crop | Overlay | Grayscale | Colorize |
| :--------- | :----: | :----: | :----: | :--: | :-----: | :-------: | :------: |
| GD | X | X | X | X | | | |
| Imagick | | | | | | | |
| Gmagick | | | | | | | |
| ImagickCLI | X | X | X | X | | | |
## Roadmap
### Drivers
A 1.0 release will not be made until the following drivers are available and solidly tested:
@ -53,10 +63,3 @@ In the name of simplicity and ease of use, the effective order of operations wil
2. Resizing and cropping
3. Color effects
4. Content-changing effects
This is only the effective order of operations, because to improve performance the *actual* order of operations, if applicable, will be:
1. Resizing and cropping
2. Orientation
3. Color effects
4. Content-changing effects

28
examples/imagick-cli.php Normal file
View file

@ -0,0 +1,28 @@
<?php
use ByJoby\ImageTransform\Drivers\ImagickCLIDriver;
use ByJoby\ImageTransform\Sizers\Cover;
include __DIR__ . '/../vendor/autoload.php';
// first step is instantiate a Driver, in this case ImagickCLI
$driver = new ImagickCLIDriver();
// instantiate an Image object using a source file and Sizer
// in this example we're covering in a 200x500 box
$image = $driver->image(
'example-portrait.jpg',
new Cover(200, 500)
);
// currently the only operation supported are 90 degree rotation and flipping
$image->rotate(2); //rotates by two 90 degree chunks, so 180
$image->flipH(); //flips horizontally, use flipV to flip vertically
// use save() to build the image and save it to a file
$image->save('out/example-2.jpg');
// display the generated file in the browser
echo '<img src="out/example-2.jpg">';
var_dump(memory_get_peak_usage());

0
examples/out/.gitkeep Normal file
View file

View file

@ -1,30 +1,28 @@
<?php
use ByJoby\ImageTransform\Drivers\ImagickCLIDriver;
use ByJoby\ImageTransform\Sizers\Cover;
use ByJoby\ImageTransform\Sizers\Crop;
use ByJoby\ImageTransform\Drivers\GDDriver;
use ByJoby\ImageTransform\Sizers\Fit;
use ByJoby\ImageTransform\Sizers\Original;
include __DIR__ . '/../vendor/autoload.php';
$driver = new ImagickCLIDriver();
// $sizer = new Fit(1000, 500);
// $sizer = new Original();
$sizer = new Crop(500,400);
// first step is instantiate a Driver, in this case GD
$driver = new GDDriver();
// instantiate an Image object using a source file and Sizer
// in this example we're fitting in a 200x500 box
$image = $driver->image(
'example-portrait.jpg',
$sizer
new Fit(200, 500)
);
// $image->rotate();
var_dump(
'crop: width: '.$image->sizer()->cropToWidth(),
'crop: height: '.$image->sizer()->cropToHeight(),
'resize: width: '.$image->sizer()->resizeToWidth(),
'resize: height: '.$image->sizer()->resizeToHeight(),
'final: width: '.$image->width(),
'final: height: '.$image->height(),
$image
);
// currently the only operation supported are 90 degree rotation and flipping
$image->rotate(2); //rotates by two 90 degree chunks, so 180
$image->flipH(); //flips horizontally, use flipV to flip vertically
// use save() to build the image and save it to a file
$image->save('out/example-1.jpg');
// display the generated file in the browser
echo '<img src="out/example-1.jpg">';
var_dump(memory_get_peak_usage());

View file

@ -7,4 +7,5 @@ use ByJoby\ImageTransform\Sizers\AbstractSizer;
interface DriverInterface
{
public function image(string $src, AbstractSizer $sizer): Image;
public function save(Image $image, string $filename);
}

View file

@ -4,18 +4,18 @@ namespace ByJoby\ImageTransform\Drivers;
abstract class AbstractCLIDriver extends AbstractDriver
{
protected $executablePath = '';
protected $executablePath;
public function __construct()
public function __construct(string $executablePath = null)
{
if (!function_exists('exec')) {
throw new \Exception("CLI drivers can't be used with the current configuration because exec is disabled");
}
parent::__construct();
$this->executablePath = $executablePath;
}
public function executablePath($name)
public function executablePath()
{
return $this->executablePath.$name;
return $this->executablePath ?? static::DEFAULT_EXECUTABLE;
}
}

View file

@ -8,17 +8,14 @@ use ByJoby\ImageTransform\Sizers\AbstractSizer;
abstract class AbstractDriver implements DriverInterface
{
protected $tmpDir = null;
protected $tempDir = null;
protected $chmod = 0775;
public function __construct()
{
$this->__clone();
}
abstract protected function doSave(Image $image, string $filename);
public function __clone()
{
$this->setTempDir(sys_get_temp_dir() . '/byjoby_image-transform/' . uniqid("",true));
$this->tempDir = null;
}
public function image(string $src, AbstractSizer $sizer): Image
@ -26,12 +23,20 @@ abstract class AbstractDriver implements DriverInterface
return new Image($src, $this, $sizer);
}
public function setTempDir(string $dir)
public function tempDir(): string
{
if (!$this->tempDir) {
$this->setTempDir(sys_get_temp_dir() . '/byjoby_image-transform/' . uniqid("", true));
}
return $this->tempDir;
}
protected function setTempDir(string $dir)
{
if (!$this->mkdir($dir)) {
throw new \Exception("Temp directory " . htmlentities($dir) . " doesn't exist or isn't writeable, and couldn't be created.");
}
$this->tmpDir = $dir;
$this->tempDir = $dir;
}
protected function mkdir(string $dir)
@ -63,4 +68,19 @@ abstract class AbstractDriver implements DriverInterface
return false;
}
}
public function save(Image $image, string $filename)
{
if (is_file($filename)) {
if (!is_writeable($filename)) {
throw new \Exception("Can't save image because file already exists and is not writeable: " . htmlentities($filename));
}
} else {
if (!$this->mkdir(dirname($filename))) {
throw new \Exception("Can't save image because parent directory isn't writeable or couldn't be created: " . htmlentities($filename));
}
touch($filename);
}
$this->doSave($image, realpath($filename));
}
}

View file

@ -0,0 +1,25 @@
<?php
/* image-transform | https://github.com/jobyone/image-transform | MIT License */
namespace ByJoby\ImageTransform\Drivers;
use ByJoby\ImageTransform\Image;
abstract class AbstractExtensionDriver extends AbstractDriver
{
abstract protected function getImageObject(Image $image);
abstract protected function doResize($object, Image $image);
abstract protected function doCrop($object, Image $image);
abstract protected function doFlip($object, Image $image);
abstract protected function doRotation($object, Image $image);
abstract protected function saveImageObject($object, string $filename);
public function doSave(Image $image, string $filename)
{
$object = $this->getImageObject($image);
$object = $this->doRotation($object, $image);
$object = $this->doResize($object, $image);
$object = $this->doCrop($object, $image);
$object = $this->doFlip($object, $image);
$this->saveImageObject($object, $filename);
}
}

View file

@ -2,9 +2,127 @@
/* image-transform | https://github.com/jobyone/image-transform | MIT License */
namespace ByJoby\ImageTransform\Drivers;
use ByJoby\ImageTransform\Image;
/**
* This driver uses PHP's built-in GD libary. This is by far the slowest
* driver, but support is basically universal.
*/
class GDDriver extends AbstractDriver
{}
class GDDriver extends AbstractExtensionDriver
{
public function __construct()
{
if (!extension_loaded('gd')) {
throw new \Exception("GD driver can't be used because the extension is not loaded");
}
}
protected function getImageObject(Image $image)
{
$source = $image->source();
$extension = strtolower(preg_replace('/^.+\./', '', $source));
switch ($extension) {
case 'bmp':
return imagecreatefrombmp($source);
case 'gif':
return imagecreatefromgif($source);
case 'jpg':
return imagecreatefromjpeg($source);
case 'jpeg':
return imagecreatefromjpeg($source);
case 'png':
return imagecreatefrompng($source);
case 'wbmp':
return imagecreatefromwbmp($source);
case 'webp':
return imagecreatefromwebp($source);
case 'xbm':
return imagecreatefromxbm($source);
default:
throw new \Exception("Unsupported input type: " . htmlentities($source));
}
}
protected function doResize($object, Image $image)
{
$sizer = $image->sizer();
if ($sizer->resizeToHeight() && $sizer->resizeToWidth()) {
// sizer is calling for a resize
$new = imagecreatetruecolor($sizer->resizeToWidth(), $sizer->resizeToHeight());
imagecopyresampled(
$new, $object,
0, 0,//dst x/y
0, 0,
$sizer->resizeToWidth(), $sizer->resizeToHeight(),
$sizer->originalWidth(), $sizer->originalHeight()
);
return $new;
} else {
// sizer isn't calling for a resize, return object unchanged
return $object;
}
}
protected function doCrop($object, Image $image)
{
$sizer = $image->sizer();
if ($sizer->cropToHeight() && $sizer->cropToWidth()) {
// sizer is calling for a crop
$new = imagecreatetruecolor($sizer->cropToWidth(), $sizer->cropToHeight());
imagecopyresampled(
$new, $object,
($sizer->cropToWidth()-$sizer->resizeToWidth())/2,($sizer->cropToHeight()-$sizer->resizeToHeight())/2,
0,0,
$sizer->resizetoWidth(),$sizer->resizeToHeight(),$sizer->resizeToWidth(),$sizer->resizeToHeight()
);
return $new;
} else {
// sizer isn't calling for a resize, return object unchanged
return $object;
}
}
protected function doFlip($object, Image $image)
{
if ($image->getFlipH()) {
imageflip($object,IMG_FLIP_HORIZONTAL);
}
if ($image->getFlipV()) {
imageflip($object,IMG_FLIP_VERTICAL);
}
return $object;
}
protected function doRotation($object, Image $image)
{
if ($rotationAmount = 360 - $image->rotation() * 90) {
return imagerotate($object, $rotationAmount, 0);
}
return $object;
}
protected function saveImageObject($object, string $filename)
{
$extension = strtolower(preg_replace('/^.+\./', '', $filename));
switch ($extension) {
case 'bmp':
return imagebmp($object, $filename);
case 'gif':
return imagegif($object, $filename);
case 'jpg':
return imagejpeg($object, $filename);
case 'jpeg':
return imagejpeg($object, $filename);
case 'png':
return imagepng($object, $filename);
case 'wbmp':
return imagewbmp($object, $filename);
case 'webp':
return imagewebp($object, $filename);
case 'xbm':
return imagexbm($object, $filename);
default:
throw new \Exception("Unsupported output type: " . htmlentities($filename));
}
}
}

View file

@ -1,9 +0,0 @@
<?php
/* image-transform | https://github.com/jobyone/image-transform | MIT License */
namespace ByJoby\ImageTransform\Drivers;
/**
* This driver uses the Gmagick extension
*/
class GmagickDriver extends AbstractDriver
{}

View file

@ -2,12 +2,49 @@
/* image-transform | https://github.com/jobyone/image-transform | MIT License */
namespace ByJoby\ImageTransform\Drivers;
use ByJoby\ImageTransform\Image;
/**
* This driver is designed to use exec() and your OS's binary Imagick
* installation to do all of its image transforms. This will likely be
* the highest-performance driver of all in most situations, but does
* require that you have CLI Imagick installed, and that your server allows
* PHP's exec() function to use it.
* This driver uses exec() and command-line ImageMagick utilities to
* transform images. It is likely approaching the limits of how fast this
* library can possibly be. The downside is that it will only run if
* you have exec() enabled, and your server allows it to execute the
* ImageMagick binaries.
*/
class ImagickCLIDriver extends AbstractCLIDriver
{}
{
const DEFAULT_EXECUTABLE = 'magick';
protected function doSave(Image $image, string $filename)
{
// basics of command
$command = [
$this->executablePath(),
'"' . $image->source() . '"',
];
// rotation
if ($image->rotation()) {
$command[] = '-rotate ' . ($image->rotation() * 90);
}
// flip/flop
if ($image->getFlipH()) {
$command[] = '-flop';
}
if ($image->getFlipV()) {
$command[] = '-flip';
}
// resizing/cropping
$sizer = $image->sizer();
if ($sizer->resizeToWidth() || $sizer->cropToWidth()) {
$command[] = '-resize '.$sizer->resizeToWidth().'x'.$sizer->resizeToHeight();
}
if ($sizer->cropToWidth() && $sizer->cropToHeight()) {
$command[] = '-gravity center';
$command[] = '-extent '.$sizer->cropToWidth().'x'.$sizer->cropToHeight();
}
// output file
$command[] = '"' . $filename . '"';
var_dump($command);
exec(implode(' ', $command));
}
}

View file

@ -1,9 +0,0 @@
<?php
/* image-transform | https://github.com/jobyone/image-transform | MIT License */
namespace ByJoby\ImageTransform\Drivers;
/**
* This class uses the ImageMagick extension
*/
class ImagickDriver extends AbstractDriver
{}

View file

@ -15,12 +15,17 @@ class Image
public function __construct(string $source, DriverInterface $driver, AbstractSizer $sizer)
{
$this->source($source);
$this->sizer($sizer);
$this->setSource($source);
$this->setSizer($sizer);
$this->driver = clone $driver;
}
public function source(string $source)
public function source(): string
{
return $this->source;
}
public function setSource(string $source)
{
// set source
$this->source = realpath($source);
@ -38,15 +43,17 @@ class Image
list($this->originalWidth, $this->originalHeight) = getimagesize($this->source);
}
public function sizer(AbstractSizer $sizer = null): AbstractSizer
public function sizer(): AbstractSizer
{
if ($sizer) {
$this->sizer = clone $sizer;
$this->sizer->image($this);
}
return $this->sizer;
}
public function setSizer(AbstractSizer $sizer)
{
$this->sizer = clone $sizer;
$this->sizer->image($this);
}
public function rotate(int $steps = 1)
{
$this->rotation = ($this->rotation + $steps) % 4;
@ -60,17 +67,21 @@ class Image
public function flipH()
{
$this->flipH = !$this->flipH;
if ($this->flipH && $this->flipV) {
$this->flipH = $this->flipV = false;
}
}
public function flipV()
{
$this->flipV = !$this->flipV;
if ($this->flipH && $this->flipV) {
$this->flipH = $this->flipV = false;
}
}
public function getFlipH()
{
return $this->flipH;
}
public function getFlipV()
{
return $this->flipV;
}
public function width(): int
@ -102,4 +113,9 @@ class Image
{
return $this->originalHeight;
}
public function save(string $file)
{
$this->driver->save($this, $file);
}
}

View file

@ -3,7 +3,12 @@
namespace ByJoby\ImageTransform\tests\units\mock;
use ByJoby\ImageTransform\Drivers\AbstractDriver;
use ByJoby\ImageTransform\Image;
class MockDriver extends AbstractDriver
{
protected function doSave(Image $image, string $filename)
{
//does nothing
}
}