some fundamentals are actually working
This commit is contained in:
parent
67b4bd1209
commit
5cac6c5f64
15 changed files with 316 additions and 81 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,3 +2,5 @@ composer.phar
|
|||
/vendor/
|
||||
composer.lock
|
||||
debug.log
|
||||
/examples/out/*
|
||||
!.gitkeep
|
21
README.md
21
README.md
|
@ -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
28
examples/imagick-cli.php
Normal 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
0
examples/out/.gitkeep
Normal 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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
25
src/Drivers/AbstractExtensionDriver.php
Normal file
25
src/Drivers/AbstractExtensionDriver.php
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue