init without trunk

This commit is contained in:
Kevin Adametz 2020-07-09 12:49:32 +02:00
parent ed24ac4994
commit bb809e7233
14652 changed files with 177862 additions and 94817 deletions

View file

@ -0,0 +1,3 @@
**.swp
vendor
composer.lock

View file

@ -0,0 +1,14 @@
language: php
php:
- 5.3.3
- 5.3
- 5.4
- 5.5
- 5.6
script:
- composer install
- mkdir tests/cache
- chmod 777 tests/cache
- phpunit

365
vendor/gregwar/cache/Gregwar/Cache/Cache.php vendored Executable file
View file

@ -0,0 +1,365 @@
<?php
namespace Gregwar\Cache;
/**
* A cache system based on files
*
* @author Gregwar <g.passault@gmail.com>
*/
class Cache implements CacheInterface
{
/**
* Cache directory
*/
protected $cacheDirectory;
/**
* Use a different directory as actual cache
* @var string
*/
protected $actualCacheDirectory = null;
/**
* Prefix directories size
*
* For instance, if the file is helloworld.txt and the prefix size is
* 5, the cache file will be: h/e/l/l/o/helloworld.txt
*
* This is useful to avoid reaching a too large number of files into the
* cache system directories
* @var int
*/
protected $prefixSize = 5;
/**
* Directory mode
*
* Allows setting of the access mode for the directories created.
* @var int
*/
protected $directoryMode = 0755;
/**
* Constructs the cache system
*
* @param string $cacheDirectory the cache directory
*/
public function __construct($cacheDirectory = 'cache')
{
$this->cacheDirectory = $cacheDirectory;
}
/**
* Sets the cache directory
*
* @param string $cacheDirectory the cache directory
* @return self
*/
public function setCacheDirectory($cacheDirectory)
{
$this->cacheDirectory = $cacheDirectory;
return $this;
}
/**
* Gets the cache directory
*
* @return string the cache directory
*/
public function getCacheDirectory()
{
return $this->cacheDirectory;
}
/**
* Sets the actual cache directory
*
* @param string $actualCacheDirectory the actual cache directory
* @return self
*/
public function setActualCacheDirectory($actualCacheDirectory = null)
{
$this->actualCacheDirectory = $actualCacheDirectory;
return $this;
}
/**
* Returns the actual cache directory
*/
public function getActualCacheDirectory()
{
return $this->actualCacheDirectory ?: $this->cacheDirectory;
}
/**
* Change the prefix size
*
* @param int $prefixSize the size of the prefix directories
* @return self
*/
public function setPrefixSize($prefixSize)
{
$this->prefixSize = $prefixSize;
return $this;
}
/**
* Change the directory mode
*
* @param int $directoryMode the directory mode to use
* @return self
*/
public function setDirectoryMode($directoryMode)
{
if (!$directoryMode) {
$directoryMode = 0755;
}
$this->directoryMode = $directoryMode;
return $this;
}
/**
* Creates a directory
*
* @param string $directory the target directory
*/
protected function mkdir($directory)
{
if (!is_dir($directory)) {
@mkdir($directory, $this->directoryMode, true);
}
}
/**
* Gets the cache file name
*
* @param string $filename the name of the cache file
* @param bool $actual get the actual file or the public file
* @param bool $mkdir a boolean to enable/disable the construction of the
* cache file directory
* @return string
*/
public function getCacheFile($filename, $actual = false, $mkdir = false)
{
$path = array();
// Getting the length of the filename before the extension
$parts = explode('.', $filename);
$len = strlen($parts[0]);
for ($i=0; $i<min($len, $this->prefixSize); $i++) {
$path[] = $filename[$i];
}
$path = implode('/', $path);
if ($mkdir) {
$actualDir = $this->getActualCacheDirectory() . '/' . $path;
$this->mkdir($actualDir);
}
$path .= '/' . $filename;
if ($actual) {
return $this->getActualCacheDirectory() . '/' . $path;
} else {
return $this->getCacheDirectory() . '/' . $path;
}
}
/**
* Checks that the cache conditions are respected
*
* @param string $cacheFile the cache file
* @param array $conditions an array of conditions to check
* @return bool
* @throws \Exception
*/
protected function checkConditions($cacheFile, array $conditions = array())
{
// Implicit condition: the cache file should exist
if (!file_exists($cacheFile)) {
return false;
}
foreach ($conditions as $type => $value) {
switch ($type) {
case 'maxage':
case 'max-age':
// Return false if the file is older than $value
$age = time() - filemtime($cacheFile);
if ($age > $value) {
return false;
}
break;
case 'younger-than':
case 'youngerthan':
// Return false if the file is older than the file $value, or the files $value
$check = function($filename) use ($cacheFile) {
return !file_exists($filename) || filemtime($cacheFile) < filemtime($filename);
};
if (!is_array($value)) {
if (!$this->isRemote($value) && $check($value)) {
return false;
}
} else {
foreach ($value as $file) {
if (!$this->isRemote($file) && $check($file)) {
return false;
}
}
}
break;
default:
throw new \Exception('Cache condition '.$type.' not supported');
}
}
return true;
}
/**
* Checks if the target filename exists in the cache and if the conditions
* are respected
*
* @param string $filename the filename
* @param array $conditions the conditions to respect
* @return bool
*/
public function exists($filename, array $conditions = array())
{
$cacheFile = $this->getCacheFile($filename, true);
return $this->checkConditions($cacheFile, $conditions);
}
/**
* Alias for exists
*
* @param string $filename the filename
* @param array $conditions the conditions to respect
* @return bool
*/
public function check($filename, array $conditions = array())
{
return $this->exists($filename, $conditions);
}
/**
* Write data in the cache
*
* @param string $filename the name of the cache file
* @param string $contents the contents to store
* @return self
*/
public function set($filename, $contents = '')
{
$cacheFile = $this->getCacheFile($filename, true, true);
file_put_contents($cacheFile, $contents, \LOCK_EX);
return $this;
}
/**
* Alias for set()
*
* @param string $filename the name of the cache file
* @param string $contents the contents to store
* @return self
*/
public function write($filename, $contents = '')
{
return $this->set($filename, $contents);
}
/**
* Get data from the cache
*
* @param string $filename the cache file name
* @param array $conditions
* @return null|string
*/
public function get($filename, array $conditions = array())
{
if ($this->exists($filename, $conditions)) {
return file_get_contents($this->getCacheFile($filename, true));
} else {
return null;
}
}
/**
* Is this URL remote?
*
* @param string $file
* @return bool
*/
protected function isRemote($file)
{
if (preg_match('/^([a-z]+):\/\//', $file, $match)) {
return ($match[1] != 'file');
}
return false;
}
/**
* Get or create the cache entry
*
* @param string $filename the cache file name
* @param array $conditions an array of conditions about expiration
* @param \Closure $function the closure to call if the file does not exist
* @param bool $file returns the cache file or the file contents
* @param bool $actual returns the actual cache file
* @return string
* @throws \InvalidArgumentException
*/
public function getOrCreate($filename, array $conditions = array(), $function, $file = false, $actual = false)
{
if (!is_callable($function)) {
throw new \InvalidArgumentException('The argument $function should be callable');
}
$cacheFile = $this->getCacheFile($filename, true, true);
$data = null;
if (!$this->check($filename, $conditions)) {
if(file_exists($cacheFile)) {
unlink($cacheFile);
}
$data = call_user_func($function, $cacheFile);
// Test if the closure wrote the file or if it returned the data
if (!file_exists($cacheFile)) {
$this->set($filename, $data);
} else {
$data = file_get_contents($cacheFile);
}
}
return $file ? $this->getCacheFile($filename, $actual) : file_get_contents($cacheFile);
}
/**
* Alias to getOrCreate with $file = true
*
* @param string $filename the cache file name
* @param array $conditions an array of conditions about expiration
* @param \Closure $function the closure to call if the file does not exist
* @param bool $actual returns the actual cache file
* @return string
* @throws \InvalidArgumentException
*/
public function getOrCreateFile($filename, array $conditions = array(), $function, $actual = false)
{
return $this->getOrCreate($filename, $conditions, $function, true, $actual);
}
}

View file

@ -0,0 +1,131 @@
<?php namespace Gregwar\Cache;
interface CacheInterface {
/**
* Sets the cache directory
*
* @param string $cacheDirectory the cache directory
* @return self
*/
public function setCacheDirectory($cacheDirectory);
/**
* Gets the cache directory
*
* @return string the cache directory
*/
public function getCacheDirectory();
/**
* Sets the actual cache directory
*
* @param string $actualCacheDirectory the actual cache directory
* @return self
*/
public function setActualCacheDirectory($actualCacheDirectory = null);
/**
* Returns the actual cache directory
*/
public function getActualCacheDirectory();
/**
* Change the prefix size
*
* @param int $prefixSize the size of the prefix directories
* @return self
*/
public function setPrefixSize($prefixSize);
/**
* Change the directory mode
*
* @param int $directoryMode the directory mode to use
* @return self
*/
public function setDirectoryMode($directoryMode);
/**
* Gets the cache file name
*
* @param string $filename the name of the cache file
* @param bool $actual get the actual file or the public file
* @param bool $mkdir a boolean to enable/disable the construction of the
* cache file directory
* @return string
*/
public function getCacheFile($filename, $actual = false, $mkdir = false);
/**
* Checks if the target filename exists in the cache and if the conditions
* are respected
*
* @param string $filename the filename
* @param array $conditions the conditions to respect
* @return bool
*/
public function exists($filename, array $conditions = array());
/**
* Alias for exists
*
* @param string $filename the filename
* @param array $conditions the conditions to respect
* @return bool
*/
public function check($filename, array $conditions = array());
/**
* Write data in the cache
*
* @param string $filename the name of the cache file
* @param string $contents the contents to store
* @return self
*/
public function set($filename, $contents = '');
/**
* Alias for set()
*
* @param string $filename the name of the cache file
* @param string $contents the contents to store
* @return self
*/
public function write($filename, $contents = '');
/**
* Get data from the cache
*
* @param string $filename the cache file name
* @param array $conditions
* @return null|string
*/
public function get($filename, array $conditions = array());
/**
* Get or create the cache entry
*
* @param string $filename the cache file name
* @param array $conditions an array of conditions about expiration
* @param \Closure $function the closure to call if the file does not exist
* @param bool $file returns the cache file or the file contents
* @param bool $actual returns the actual cache file
* @return string
* @throws \InvalidArgumentException
*/
public function getOrCreate($filename, array $conditions = array(), $function, $file = false, $actual = false);
/**
* Alias to getOrCreate with $file = true
*
* @param string $filename the cache file name
* @param array $conditions an array of conditions about expiration
* @param \Closure $function the closure to call if the file does not exist
* @param bool $actual returns the actual cache file
* @return string
* @throws \InvalidArgumentException
*/
public function getOrCreateFile($filename, array $conditions = array(), $function, $actual = false);
}

View file

@ -0,0 +1,86 @@
<?php
namespace Gregwar\Cache;
/**
* Garbage collect a directory, this will crawl a directory, lookng
* for files older than X days and destroy them
*
* @author Gregwar <g.passault@gmail.com>
*/
class GarbageCollect
{
/**
* Drops old files of a directory
*
* @param string $directory the name of the target directory
* @param int $days the number of days to consider a file old
* @param bool $verbose enable verbose output
*
* @return bool true if all the files/directories of a directory was wiped
*/
public static function dropOldFiles($directory, $days = 30, $verbose = false)
{
$allDropped = true;
$now = time();
$dir = opendir($directory);
if (!$dir) {
if ($verbose) {
echo "! Unable to open $directory\n";
}
return false;
}
while ($file = readdir($dir)) {
if ($file == '.' || $file == '..') {
continue;
}
$fullName = $directory.'/'.$file;
$old = $now-filemtime($fullName);
if (is_dir($fullName)) {
// Directories are recursively crawled
if (static::dropOldFiles($fullName, $days, $verbose)) {
self::drop($fullName, $verbose);
} else {
$allDropped = false;
}
} else {
if ($old > (24*60*60*$days)) {
self::drop($fullName, $verbose);
} else {
$allDropped = false;
}
}
}
closedir($dir);
return $allDropped;
}
/**
* Drops a file or an empty directory
*
* @param string $file the file to be removed
* @param bool $verbose the verbosity
*/
public static function drop($file, $verbose = false)
{
if (is_dir($file)) {
@rmdir($file);
} else {
@unlink($file);
}
if ($verbose) {
echo "> Dropping $file...\n";
}
}
}

View file

@ -0,0 +1,19 @@
Copyright (c) <2013> Grégoire Passault
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,165 @@
Cache
=====
![Build status](https://travis-ci.org/Gregwar/Cache.svg?branch=master)
This is a lightweight cache system based on file and directories.
Usage
=====
Step 1: Install it
------------------
Via composer:
```json
{
"require": {
"gregwar/cache": "1.0.*"
}
}
```
Or with a clone of the repository:
```bash
git clone https://github.com/Gregwar/Cache.git
```
Or downloading it:
* [Download .zip](https://github.com/Gregwar/Cache/archive/master.zip)
* [Download .tar.gz](https://github.com/Gregwar/Cache/archive/master.tar.gz)
Step 2: Setup the rights
------------------------
You need your PHP script to have access to the cache directory, you can for instance
create a `cache` directory (be sure the web server can write it):
```
mkdir cache
```
Step 3: Access the cache
------------------------
To access the cache, you can do like this:
```php
<?php
include('vendor/autoload.php'); // If using composer
use Gregwar\Cache\Cache;
$cache = new Cache;
$cache->setCacheDirectory('cache'); // This is the default
// If the cache exists, this will return it, else, the closure will be called
// to create this image
$data = $cache->getOrCreate('red-square.png', array(), function($filename) {
$i = imagecreatetruecolor(100, 100);
imagefill($i, 0, 0, 0xff0000);
imagepng($i, $filename);
});
header('Content-type: image/png');
echo $data;
```
This will render a red square. If the cache file (which will look like `cache/r/e/d/-/s/red-square.png')
exists, it will be read, else, the closure will be called in order to create the cache file.
API
===
You can use the following methods:
* `setCacheDirectory($directory)`: sets the cache directory (see below).
* `setActualCacheDirectory($directory)`: sets the actual cache directory (see below).
* `exists($filename, $conditions = array())`: check that the $filename file exists in the cache, checking
the conditions (see below).
* `check($filename, $conditions = array())`: alias for `exists`.
* `getCacheFile($filename, $actual = false, $mkdir = false)`: gets the cache file. If the `$actual` flag
is true, the actual cache file name will be returned (see below), if the `$mkdir` flag is true, the
cache file directories tree will be created.
* `set($filename, $contents)`: write contents to `$filename` cache file.
* `write($filename, $contents)`: alias for `set()`
* `get($filename, $conditions = array())`: if the cache file for `$filename` exists, contents will be
returned, else, `NULL` will be returned.
* `setPrefixSize($prefixSize)`: sets the prefix size for directories, default is 5. For instance, the
cache file for `helloworld.txt`, will be `'h/e/l/l/o/helloworld.txt`.
* `setDirectoryMode($directoryMode)`: sets the directory mode when creating directories, default is `0755`.
Does not affect any directories previously created.
* `getOrCreate($filename, $conditions = array(), $function, $file = false)`: this will check if the `$filename`
cache file exists and verifies `$conditions` (see below). If the cache file is OK, it will return its
contents. Else, it will call the `$function`, passing it the target file, this function can write the
file given in parameter or just return data. Then, cache data will be returned. If `$file` flag is set,
the cache file name will be returned instead of file data.
Note: consider using an hash for the `$filename` cache file, to avoid special characters.
Conditions
==========
You can use conditions to manage file expirations on the cache, there is two way of expiring:
* Using `max-age`, in seconds, to set the maximum age of the file
* Using `younger-than`, by passing another file, this will compare the modification date
and regenerate the cache if the given file is younger.
For instance, if you want to uppercase a file:
```php
<?php
use Gregwar\Cache\Cache;
$cache = new Cache;
$data = $cache->getOrCreate('uppercase.txt',
array(
'younger-than' => 'original.txt'
),
function() {
echo "Generating file...\n";
return strtoupper(file_get_contents('original.txt'));
});
echo $data;
```
This will be create the `uppercase.txt` cache file by uppercasing the `original.txt` if the cache file
does not exists or if the `original.txt` file is more recent than the cache file.
For instance:
```
php uppercase.php # Will generate the cache file
php uppercase.php # Will not generate the cache file
touch original.txt # Sets the last modification time to now
php uppercase.php # Will re-generate the cache file
```
Cache directory and actual cache directory
==========================================
In some cases, you'll want to get the cache file name. For instance, if you're caching
images, you'll want to give a string like `cache/s/o/m/e/i/someimage.png` to put it into
an `<img>` tag. This can be done by passing the `$file` argument to the `getOrCreate` to true,
or directly using `getCacheFile` method (see above).
However, the visible `cache` directory of your users is not the same as the absolute path
you want to access. To do that, you can set both the cache directory and the actual cache directory.
The cache directory is the prefix visible by the users (for instance: `cache/s/o/m/e/i/someimage.png`),
and the actual cache directory is the prefix to use to actually access to the image (for instance:
`/var/www/somesite/cache/s/o/m/e/i/someimage.png`). This way, the file will be accessed using absolute
path and the cache file returned will directly be usable for your user's browsers.
License
=======
This repository is under the MIT license, have a look at the `LICENCE` file.

View file

@ -0,0 +1,16 @@
<?php
/**
* Registers an autoload for all the classes in Gregwar\Cache
*/
spl_autoload_register(function ($className) {
$namespace = 'Gregwar\\Cache';
if (strpos($className, $namespace) === 0) {
$className = str_replace($namespace, '', $className);
$fileName = __DIR__ . '/' . str_replace('\\', '/', $className) . '.php';
if (file_exists($fileName)) {
require($fileName);
}
}
});

View file

@ -0,0 +1,21 @@
{
"name": "gregwar/cache",
"description": "A lightweight file-system cache system",
"keywords": ["cache", "caching", "system", "file-system"],
"license": "MIT",
"authors": [
{
"name": "Gregwar",
"email": "g.passault@gmail.com"
}
],
"target-dir": "Gregwar/Cache",
"require": {
"php": ">=5.3"
},
"autoload": {
"psr-0": {
"Gregwar\\Cache": ""
}
}
}

View file

@ -0,0 +1 @@
cache/

View file

@ -0,0 +1,14 @@
<?php
include('../autoload.php'); // If using composer
use Gregwar\Cache\GarbageCollect;
if (!is_dir('cache')) {
`mkdir cache`;
}
`touch -t 9901010101 cache/foo`;
`touch cache/bar`;
GarbageCollect::dropOldFiles(__DIR__.'/cache', 30, true);

View file

@ -0,0 +1,23 @@
<?php
include('../autoload.php');
$cache = new Gregwar\Cache\Cache;
$data = $cache->getOrCreate('uppercase.txt', array('max-age' => 2), function() {
echo "First call: generating file...\n";
return strtoupper(file_get_contents('original.txt'));
});
$data = $cache->getOrCreate('uppercase.txt', array('max-age' => 2), function() {
echo "Second call: generating file, this should not happen!...\n";
return strtoupper(file_get_contents('original.txt'));
});
echo "Waiting 4s...\n";
sleep(4);
$data = $cache->getOrCreate('uppercase.txt', array('max-age' => 2), function() {
echo "Third call: generating cache file, because it expired...\n";
return strtoupper(file_get_contents('original.txt'));
});

View file

@ -0,0 +1,4 @@
There he goes.
One of God's own prototypes.
A high-powered mutant of some kind never even considered for mass production.
Too weird to live, and too rare to die.

View file

@ -0,0 +1,20 @@
<?php
include('../autoload.php'); // If using composer
use Gregwar\Cache\Cache;
$cache = new Cache;
$cache->setCacheDirectory('cache'); // This is the default
// If the cache exists, this will return it, else, the closure will be called
// to create this image
$file = $cache->getOrCreateFile('red-square.png', array(), function($filename) {
$i = imagecreatetruecolor(100, 100);
imagefill($i, 0, 0, 0xff0000);
file_put_contents($filename, 'abc');
imagepng($i, 'a.png');
imagepng($i, $filename);
});
echo $file, "\n";

View file

@ -0,0 +1,20 @@
<?php
include('../autoload.php'); // If using composer
use Gregwar\Cache\Cache;
$cache = new Cache;
$cache->setCacheDirectory('cache'); // This is the default
// If the cache exists, this will return it, else, the closure will be called
// to create this image
$data = $cache->getOrCreate('red-square.png', array(), function($filename) {
$i = imagecreatetruecolor(100, 100);
imagefill($i, 0, 0, 0xff0000);
file_put_contents($filename, 'abc');
imagepng($i, $filename);
});
header('Content-type: image/png');
echo $data;

View file

@ -0,0 +1,14 @@
<?php
include('../autoload.php');
use Gregwar\Cache\Cache;
$cache = new Cache;
$data = $cache->getOrCreate('uppercase.txt', array('younger-than' => 'original.txt'), function() {
echo "Generating file...\n";
return strtoupper(file_get_contents('original.txt'));
});
echo $data;

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<testsuites>
<testsuite name="Cache testing">
<file>./tests/CacheTests.php</file>
</testsuite>
</testsuites>
</phpunit>

View file

@ -0,0 +1,208 @@
<?php
use Gregwar\Cache\Cache;
/**
* Unit testing for Cache
*/
class CacheTests extends \PHPUnit_Framework_TestCase
{
public function testContract()
{
$cache = $this->getCache();
$this->assertInstanceOf('Gregwar\Cache\CacheInterface', $cache);
}
/**
* Testing that file names are good
*/
public function testFileName()
{
$cache = $this->getCache();
$cacheDir = $this->getCacheDirectory();
$actualCacheDir = $this->getActualCacheDirectory();
$cacheFile = $cache->getCacheFile('helloworld.txt');
$actualCacheFile = $cache->getCacheFile('helloworld.txt', true);
$this->assertEquals($cacheDir . '/h/e/l/l/o/helloworld.txt', $cacheFile);
$this->assertEquals($actualCacheDir . '/h/e/l/l/o/helloworld.txt', $actualCacheFile);
$cacheFile = $cache->getCacheFile('xy.txt');
$actualCacheFile = $cache->getCacheFile('xy.txt', true);
$this->assertEquals($cacheDir . '/x/y/xy.txt', $cacheFile);
$this->assertEquals($actualCacheDir . '/x/y/xy.txt', $actualCacheFile);
}
/**
* Testing caching a file
*/
public function testCaching()
{
$cache = $this->getCache();
$this->assertFalse($cache->exists('testing.txt'));
$cache->set('testing.txt', 'toto');
$this->assertTrue($cache->exists('testing.txt'));
$this->assertFalse($cache->exists('testing2.txt'));
$cache->write('testing2.txt', 'toto');
$this->assertTrue($cache->exists('testing2.txt'));
$this->assertFalse($cache->exists('testing.txt', array(
'max-age' => -1
)));
$this->assertTrue($cache->exists('testing.txt', array(
'max-age' => 2
)));
sleep(3);
$this->assertFalse($cache->exists('testing.txt', array(
'max-age' => 2
)));
}
/**
* Testing the getOrCreate function
*/
public function testGetOrCreate()
{
$cache = $this->getCache();
$this->assertFalse($cache->exists('testing.txt'));
$data = $cache->getOrCreate('testing.txt', array(), function() {
return 'zebra';
});
$this->assertTrue($cache->exists('testing.txt'));
$this->assertEquals('zebra', $data);
$data = $cache->getOrCreate('testing.txt', array(), function() {
return 'elephant';
});
$this->assertEquals('zebra', $data);
}
/**
* Testing the getOrCreate function with a callable
*/
public function testGetOrCreateWithCallable()
{
$cache = $this->getCache();
$this->assertFalse($cache->exists('testing.txt'));
$data = $cache->getOrCreate('testing.txt', array(), array($this, 'getAnimal'));
$this->assertTrue($cache->exists('testing.txt'));
$this->assertEquals('orangutan', $data);
}
public function getAnimal()
{
return 'orangutan';
}
/**
* Testing the getOrCreate function with $file=true
*/
public function testGetOrCreateFile()
{
$dir = __DIR__;
$cache = $this->getCache();
$file = $dir.'/'.$cache->getOrCreateFile('file.txt', array(), function() {
return 'xyz';
});
$file2 = $dir.'/'.$cache->getOrCreate('file.txt', array(), function(){}, true);
$this->assertEquals($file, $file2);
$this->assertTrue(file_exists($file));
$this->assertEquals('xyz', file_get_contents($file));
}
/**
* Testing that the not existing younger file works
*/
public function testNotExistingYounger()
{
$cache = $this->getCache();
$data = $cache->getOrCreate('testing.txt', array('younger-than'=> 'i-dont-exist'), function() {
return 'some-data';
});
$this->assertEquals('some-data', $data);
}
/**
* Testing that directory mode works
*/
public function testDirectoryMode()
{
$dir = __DIR__;
$cache = $this->getCache();
$cacheDir = $this->getCacheDirectory();
// default permissions are 0755
$data = $cache->getOrCreate('aaa.txt', array(), function () {
return 'abc';
});
$this->assertTrue((fileperms("$dir/$cacheDir/a") & 0777) == 0755);
$this->assertTrue((fileperms("$dir/$cacheDir/a/a") & 0777) == 0755);
$this->assertTrue((fileperms("$dir/$cacheDir/a/a/a") & 0777) == 0755);
// Change permissions to be more restrictive
$cache->setDirectoryMode(0700);
$data = $cache->getOrCreate('bbb.txt', array(), function () {
return 'abc';
});
$this->assertTrue((fileperms("$dir/$cacheDir/b") & 0777) == 0700);
$this->assertTrue((fileperms("$dir/$cacheDir/b/b") & 0777) == 0700);
$this->assertTrue((fileperms("$dir/$cacheDir/b/b/b") & 0777) == 0700);
}
/**
* Testing that remotes does not cause cache regeneration
*/
public function testRemote()
{
$cache = $this->getCache();
$cache->set('remote', 'original');
$data = $cache->getOrCreate('remote', array('younger-than' => 'http://google.com'), function() {
return 'modified';
});
$data = $cache->getOrCreate('remote', array('younger-than' => 'ftps://google.com'), function() {
return 'modified';
});
$this->assertEquals('original', $data);
}
protected function getCache()
{
$cache = new Cache;
return $cache
->setPrefixSize(5)
->setCacheDirectory($this->getCacheDirectory())
->setActualCacheDirectory($this->getActualCacheDirectory())
;
}
protected function getActualCacheDirectory()
{
return __DIR__.'/'.$this->getCacheDirectory();
}
protected function getCacheDirectory()
{
return 'cache';
}
public function tearDown()
{
$cacheDirectory = $this->getActualCacheDirectory();
`rm -rf $cacheDirectory`;
}
}

View file

@ -0,0 +1,3 @@
<?php
include(__DIR__ . '/../autoload.php');

View file

@ -0,0 +1,3 @@
composer.lock
vendor
/.php_cs.cache

9
vendor/gregwar/image-bundle/.php_cs vendored Normal file
View file

@ -0,0 +1,9 @@
<?php
require_once './vendor/autoload.php';
use SLLH\StyleCIBridge\ConfigBridge;
return ConfigBridge::create()
->setUsingCache(true)
;

View file

@ -0,0 +1,13 @@
preset: symfony
enabled:
- align_double_arrow
- newline_after_open_tag
- ordered_use
- long_array_syntax
- php_unit_construct
- php_unit_strict
disabled:
- unalign_double_arrow
- unalign_equals

View file

@ -0,0 +1,34 @@
<?php
/**
* @Author: Michał Kurzeja, Accesto
* User: michal
* Date: 24.09.13
* Time: 10:18
*/
namespace Gregwar\ImageBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('gregwar_image');
$rootNode = method_exists(TreeBuilder::class, 'getRootNode') ?
$treeBuilder->getRootNode() : $treeBuilder->root('gregwar_image');
$rootNode
->children()
->scalarNode('cache_dir')->defaultValue('cache')->end()
->scalarNode('cache_dir_mode')->defaultNull()->example('0755')->end()
->booleanNode('throw_exception')->defaultFalse()->end()
->scalarNode('fallback_image')->defaultNull()->end()
->scalarNode('web_dir')->defaultValue('%kernel.root_dir%/../web')->end()
->end()
;
return $treeBuilder;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Gregwar\ImageBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* Loading configuration.
*
* @author Gregwar <g.passault@gmail.com>
*/
class GregwarImageExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('gregwar_image.cache_dir', $config['cache_dir']);
$container->setParameter('gregwar_image.cache_dir_mode', $config['cache_dir_mode']);
$container->setParameter('gregwar_image.throw_exception', $config['throw_exception']);
$container->setParameter('gregwar_image.fallback_image', $config['fallback_image']);
$container->setParameter('gregwar_image.web_dir', $config['web_dir']);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace Gregwar\ImageBundle\Extensions;
use Gregwar\ImageBundle\Services\ImageHandling;
/**
* ImageTwig extension.
*
* @author Gregwar <g.passault@gmail.com>
* @author bzikarsky <benjamin.zikarsky@perbility.de>
*/
class ImageTwig extends \Twig_Extension
{
/**
* @var ImageHandling
*/
private $imageHandling;
/**
* @var string
*/
private $webDir;
/**
* @param ImageHandling $imageHandling
* @param string $webDir
*/
public function __construct(ImageHandling $imageHandling, $webDir)
{
$this->imageHandling = $imageHandling;
$this->webDir = $webDir;
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('image', array($this, 'image'), array('is_safe' => array('html'))),
new \Twig_SimpleFunction('new_image', array($this, 'newImage'), array('is_safe' => array('html'))),
new \Twig_SimpleFunction('web_image', array($this, 'webImage'), array('is_safe' => array('html'))),
);
}
/**
* @param string $path
*
* @return object
*/
public function webImage($path)
{
$directory = $this->webDir.'/';
return $this->imageHandling->open($directory.$path);
}
/**
* @param string $path
*
* @return object
*/
public function image($path)
{
return $this->imageHandling->open($path);
}
/**
* @param string $width
* @param string $height
*
* @return object
*/
public function newImage($width, $height)
{
return $this->imageHandling->create($width, $height);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'image';
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Gregwar\ImageBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class GregwarImageBundle extends Bundle
{
}

View file

@ -0,0 +1,61 @@
<?php
namespace Gregwar\ImageBundle;
use Gregwar\Image\Image;
/**
* Image manipulation class.
*
* @author Gregwar <g.passault@gmail.com>
*/
class ImageHandler extends Image
{
protected $fileCallback = null;
/**
* @param null $originalFile
* @param null $width
* @param null $height
* @param bool $throwException
*/
public function __construct($originalFile = null, $width = null, $height = null, $throwException = null, $fallbackImage = null)
{
parent::__construct($originalFile, $width, $height);
$this->useFallback(!$throwException);
$this->setFallback($fallbackImage);
}
/**
* Defines the callback to call to compute the new filename.
*/
public function setFileCallback($fileCallback)
{
$this->fileCallback = $fileCallback;
}
/**
* When processing the filename, call the callback.
*/
protected function getFilename($filename)
{
$callback = $this->fileCallback;
if (null === $callback || substr($filename, 0, 1) == '/') {
return $filename;
}
return $callback($filename);
}
public function save($file, $type = 'guess', $quality = 80)
{
return parent::save($file, $type, $quality);
}
public function __toString()
{
return parent::__toString();
}
}

19
vendor/gregwar/image-bundle/LICENSE vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) <2012> Grégoire Passault
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

2
vendor/gregwar/image-bundle/Makefile vendored Normal file
View file

@ -0,0 +1,2 @@
cs:
php-cs-fixer fix --verbose

190
vendor/gregwar/image-bundle/README.md vendored Normal file
View file

@ -0,0 +1,190 @@
Gregwar's ImageBundle
=====================
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YUXRLWHQSWS6L)
`GregwarImageBundle` provides easy Image manipulation and API for Symfony2 and Twig
Installation
============
### Step 1: Download the GregwarImageBundle
***Using the vendors script***
Add the following lines to your `deps` file:
```
[GregwarImageBundle]
git=http://github.com/Gregwar/ImageBundle.git
target=/bundles/Gregwar/ImageBundle
```
Now, run the vendors script to download the bundle:
``` bash
$ php bin/vendors install
```
***Using submodules***
If you prefer instead to use git submodules, then run the following:
``` bash
$ git submodule add git://github.com/Gregwar/ImageBundle.git vendor/bundles/Gregwar/ImageBundle
$ git submodule update --init
```
***Using Composer***
Add the following to the "require" section of your `composer.json` file:
```
"gregwar/image-bundle": "dev-master"
```
You can also choose a version number, (tag, commit ...)
And update your dependencies
```
php composer.phar update
```
### Step 2: Configure the Autoloader
If you use composer, you can skip this step.
Add it to your `autoload.pp` :
```php
<?php
...
'Gregwar' => __DIR__.'/../vendor/bundles',
```
### Step 3: Enable the bundle
Registers the bundle in your `app/AppKernel.php`:
```php
<?php
...
public function registerBundles()
{
$bundles = array(
...
new Gregwar\ImageBundle\GregwarImageBundle(),
...
);
...
```
### Step 4: Configure the bundle and set up the directories
Adds the following configuration to your `app/config/config.yml`:
gregwar_image: ~
If you want to customize the cache directory name, you can specify it:
gregwar_image:
cache_dir: my_cache_dir
Creates the cache directory and change the permissions so the web server can write
in it:
mkdir web/cache
chmod 777 web/cache
You can also enable the exception thrown if the given file does not exist:
gregwar_image:
throw_exception: true
If you don't throw an exception, you can set the `fallback_image`, to set the
image that should be rendered in this case:
gregwar_image:
fallback_image: /path/to/your/fallback.jpg
If you have to change directories hierarchy or Web's name (e.g. web => public_html),
you can set the `web_dir` to your new Web path:
gregwar_image:
web_dir: %kernel.root_dir%/../../public_html
With Symfony Flex
-----------------
With Symfony Flex architecture, you can create the ``config/packages/gregwar_image.yaml``
file with the following contents:
gregwar_image:
web_dir: %kernel.root_dir%/../public
Usage
=====
Basics
------
This bundle is based on the [Gregwar's Image](http://github.com/Gregwar/Image) class and
provides simple but powerful Twig extension. You can for instance use it this way:
```html
<img src="{{ image('linux.jpg').resize(100,100).negate }}" />
```
And that's all ! The helper will automatically create the cached file on-the-fly if it
doesn't exists yet.
The available methods are the same as the [Gregwar's Image](http://github.com/Gregwar/Image).
You can also use the logical file names for bundle resources :
```html
<img src="{{ image('@AcmeDemoBundle/Resources/images/linux.jpg').resize(100,100).negate }}" />
```
If you use `web_image()` helper, the image file path will be prefixed by the `web/` absolute
directory of your application:
```html
<!-- The image some/image.jpg will be prefixed by web directory prefix -->
<img src="{{ web_image('some/image.jpg').resize('10%') }}" />
```
Using Image API
---------------
The image instance provides also a simple API, you can call some methods to get informations
about the handled image:
Image width: {{ image('linux.jpg').width }}px
Manipulating Image in Controllers
---------------------------------
The Image Handler is accessible via a service called image.handling. So you can do in your
controllers:
```php
<?php
...
$this->get('image.handling')->open('linux.jpg')
->grayscale()
->rotate(12)
->save('out.jpg')
```
Requirements
============
`GregwarImageBundle` needs [GD](http://php.net/gd)
and [exif](http://php.net/exif) extension for PHP to be installed on the web server
License
=======
This bundle is under MIT license

View file

@ -0,0 +1,26 @@
parameters:
image.handling.class: Gregwar\ImageBundle\Services\ImageHandling
image.handler.class: Gregwar\ImageBundle\ImageHandler
services:
# Image handling factory
image.handling:
class: '%image.handling.class%'
public: true
arguments:
- '%gregwar_image.cache_dir%'
- '%gregwar_image.cache_dir_mode%'
- '%image.handler.class%'
- '@service_container'
- '@assets.packages'
- '@file_locator'
- '%gregwar_image.throw_exception%'
- '%gregwar_image.fallback_image%'
# Helper Twig
twig.extension.image:
class: Gregwar\ImageBundle\Extensions\ImageTwig
arguments: ['@image.handling', '%gregwar_image.web_dir%']
tags:
- { name: twig.extension }

View file

@ -0,0 +1,166 @@
<?php
namespace Gregwar\ImageBundle\Services;
use Gregwar\ImageBundle\ImageHandler;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* Image manipulation service.
*
* @author Gregwar <g.passault@gmail.com>
* @author Sullivan Senechal <soullivaneuh@gmail.com>
*/
class ImageHandling
{
/**
* @var string
*/
private $cacheDirectory;
/**
* @var int
*/
private $cacheDirMode;
/**
* @var ContainerInterface
*/
private $container;
/**
* @var Packages
*/
private $assetsPackages;
/**
* @var string
*/
private $handlerClass;
/**
* @var FileLocatorInterface|KernelInterface
*/
private $fileLocator;
/**
* @var bool
*/
private $throwException;
/**
* @param string $cacheDirectory
* @param int $cacheDirMode
* @param string $handlerClass
* @param ContainerInterface $container
* @param KernelInterface|FileLocatorInterface $fileLocator
* @param bool $throwException
* @param string $fallbackImage
*/
public function __construct($cacheDirectory, $cacheDirMode, $handlerClass, ContainerInterface $container, Packages $assetsPackages, $fileLocator, $throwException, $fallbackImage)
{
if (!$fileLocator instanceof FileLocatorInterface && $fileLocator instanceof KernelInterface) {
throw new \InvalidArgumentException(
'Argument 5 passed to '.__METHOD__.' must be an instance of '.
'Symfony\Component\Config\FileLocatorInterface or Symfony\Component\HttpKernel\KernelInterface.'
);
}
if ($fileLocator instanceof KernelInterface) {
@trigger_error(
'Pass Symfony\Component\HttpKernel\KernelInterface to '.__CLASS__.
' is deprecated since version 2.1.0 and will be removed in 3.0.'.
' Use Symfony\Component\Config\FileLocatorInterface instead.',
E_USER_DEPRECATED
);
}
$this->cacheDirectory = $cacheDirectory;
$this->cacheDirMode = intval($cacheDirMode);
$this->handlerClass = $handlerClass;
$this->container = $container;
$this->assetsPackages = $assetsPackages;
$this->fileLocator = $fileLocator;
$this->throwException = $throwException;
$this->fallbackImage = $fallbackImage;
}
/**
* Get a manipulable image instance.
*
* @param string $file the image path
*
* @return ImageHandler a manipulable image instance
*/
public function open($file)
{
if (strlen($file) >= 1 && $file[0] == '@') {
try {
if ($this->fileLocator instanceof FileLocatorInterface) {
$file = $this->fileLocator->locate($file);
} else {
$this->fileLocator->locateResource($file);
}
} catch (\InvalidArgumentException $exception) {
if ($this->throwException || false == $this->fallbackImage) {
throw $exception;
}
$file = $this->fallbackImage;
}
}
return $this->createInstance($file);
}
/**
* Get a new image.
*
* @param string $w the width
* @param string $h the height
*
* @return ImageHandler a manipulable image instance
*/
public function create($w, $h)
{
return $this->createInstance(null, $w, $h);
}
/**
* Creates an instance defining the cache directory.
*
* @param string $file
* @param string|null $w
* @param string|null $h
*
* @return ImageHandler
*/
private function createInstance($file, $w = null, $h = null)
{
$container = $this->container;
$webDir = $container->getParameter('gregwar_image.web_dir');
$handlerClass = $this->handlerClass;
/** @var ImageHandler $image */
$image = new $handlerClass($file, $w, $h, $this->throwException, $this->fallbackImage);
$image->setCacheDir($this->cacheDirectory);
$image->setCacheDirMode($this->cacheDirMode);
$image->setActualCacheDir($webDir.'/'.$this->cacheDirectory);
if ($container->has('templating.helper.assets')) {
$image->setFileCallback(function ($file) use ($container) {
return $container->get('templating.helper.assets')->getUrl($file);
});
} else {
$image->setFileCallback(function ($file) use ($container) {
return $this->assetsPackages->getUrl($file);
});
}
return $image;
}
}

View file

@ -0,0 +1,35 @@
{
"name": "gregwar/image-bundle",
"type": "symfony-bundle",
"description": "Image handling bundle",
"keywords": ["symfony", "image"],
"homepage": "https://github.com/Gregwar/ImageBundle",
"license": "MIT",
"authors": [
{
"name": "Grégoire Passault",
"email": "g.passault@gmail.com",
"homepage": "http://www.gregwar.com/"
}
],
"require": {
"php": ">=5.3.3",
"ext-gd": "*",
"symfony/framework-bundle": "^2.3 || ^3.0 || ^4.0",
"twig/twig": "^1.12 || ^2.0",
"gregwar/image": "2.*"
},
"require-dev": {
"sllh/php-cs-fixer-styleci-bridge": "~1.1"
},
"autoload": {
"psr-4": {
"Gregwar\\ImageBundle\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
}

View file

@ -0,0 +1,12 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[composer.json]
indent_style = space
indent_size = 4
[Makefile]
indent_style = tab

View file

@ -0,0 +1,7 @@
demo/cache
demo/out.jpg
**.swp
vendor
composer.lock
/phpunit.xml
/.php_cs.cache

View file

@ -0,0 +1,9 @@
<?php
require_once './vendor/autoload.php';
use SLLH\StyleCIBridge\ConfigBridge;
return ConfigBridge::create()
->setUsingCache(true)
;

View file

@ -0,0 +1,13 @@
preset: symfony
enabled:
- align_double_arrow
- newline_after_open_tag
- ordered_use
- long_array_syntax
- php_unit_construct
- php_unit_strict
disabled:
- unalign_double_arrow
- unalign_equals

View file

@ -0,0 +1,36 @@
language: php
php:
- 7.0
- 7.1
- 7.2
- nightly
- hhvm
env:
global:
- PATH="$HOME/.composer/vendor/bin:$PATH"
matrix:
fast_finish: true
include:
- php: 5.3
env: COMPOSER_FLAGS="--prefer-lowest"
allow_failures:
- php: nightly
- php: hhvm
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
before_script:
- composer selfupdate
- composer config -g github-oauth.github.com $GITHUB_OAUTH_TOKEN
- composer global require phpunit/phpunit --no-update
- composer global update --prefer-dist --no-interaction
- composer update --prefer-dist --no-interaction $COMPOSER_FLAGS
script: make test

View file

@ -0,0 +1,58 @@
<?php
namespace Gregwar\Image\Adapter;
use Gregwar\Image\Source\Source;
/**
* Base Adapter Implementation to handle Image information.
*/
abstract class Adapter implements AdapterInterface
{
/**
* @var Source
*/
protected $source;
/**
* The image resource handler.
*/
protected $resource;
public function __construct()
{
}
/**
* {@inheritdoc}
*/
public function setSource(Source $source)
{
$this->source = $source;
return $this;
}
/**
* {@inheritdoc}
*/
public function getResource()
{
return $this->resource;
}
/**
* Does this adapter supports the given type ?
*/
protected function supports($type)
{
return false;
}
/**
* Converts the image to true color.
*/
protected function convertToTrueColor()
{
}
}

View file

@ -0,0 +1,399 @@
<?php
namespace Gregwar\Image\Adapter;
use Gregwar\Image\Image;
use Gregwar\Image\Source\Source;
/**
* all the functions / methods to work on images.
*
* if changing anything please also add it to \Gregwar\Image\Image
*
* @author wodka <michael.schramm@gmail.com>
*/
interface AdapterInterface
{
/**
* set the image source for the adapter.
*
* @param Source $source
*
* @return $this
*/
public function setSource(Source $source);
/**
* get the raw resource.
*
* @return resource
*/
public function getResource();
/**
* Gets the name of the adapter.
*
* @return string
*/
public function getName();
/**
* Image width.
*
* @return int
*/
public function width();
/**
* Image height.
*
* @return int
*/
public function height();
/**
* Init the resource.
*
* @return $this
*/
public function init();
/**
* Unload the resource
*/
public function deinit();
/**
* Save the image as a gif.
*
* @return $this
*/
public function saveGif($file);
/**
* Save the image as a png.
*
* @return $this
*/
public function savePng($file);
/**
* Save the image as a Webp.
*
* @return $this
*/
public function saveWebp($file, $quality);
/**
* Save the image as a jpeg.
*
* @return $this
*/
public function saveJpeg($file, $quality);
/**
* Works as resize() excepts that the layout will be cropped.
*
* @param int $width the width
* @param int $height the height
* @param int $background the background
*
* @return $this
*/
public function cropResize($width = null, $height = null, $background = 0xffffff);
/**
* Resize the image preserving scale. Can enlarge it.
*
* @param int $width the width
* @param int $height the height
* @param int $background the background
* @param bool $crop
*
* @return $this
*/
public function scaleResize($width = null, $height = null, $background = 0xffffff, $crop = false);
/**
* Resizes the image. It will never be enlarged.
*
* @param int $width the width
* @param int $height the height
* @param int $background the background
* @param bool $force
* @param bool $rescale
* @param bool $crop
*
* @return $this
*/
public function resize($width = null, $height = null, $background = 0xffffff, $force = false, $rescale = false, $crop = false);
/**
* Crops the image.
*
* @param int $x the top-left x position of the crop box
* @param int $y the top-left y position of the crop box
* @param int $width the width of the crop box
* @param int $height the height of the crop box
*
* @return $this
*/
public function crop($x, $y, $width, $height);
/**
* enable progressive image loading.
*
* @return $this
*/
public function enableProgressive();
/**
* Resizes the image forcing the destination to have exactly the
* given width and the height.
*
* @param int $width the width
* @param int $height the height
* @param int $background the background
*
* @return $this
*/
public function forceResize($width = null, $height = null, $background = 0xffffff);
/**
* Perform a zoom crop of the image to desired width and height.
*
* @param int $width Desired width
* @param int $height Desired height
* @param int $background
*
* @return $this
*/
public function zoomCrop($width, $height, $background = 0xffffff);
/**
* Fills the image background to $bg if the image is transparent.
*
* @param int $background background color
*
* @return $this
*/
public function fillBackground($background = 0xffffff);
/**
* Negates the image.
*
* @return $this
*/
public function negate();
/**
* Changes the brightness of the image.
*
* @param int $brightness the brightness
*
* @return $this
*/
public function brightness($brightness);
/**
* Contrasts the image.
*
* @param int $contrast the contrast [-100, 100]
*
* @return $this
*/
public function contrast($contrast);
/**
* Apply a grayscale level effect on the image.
*
* @return $this
*/
public function grayscale();
/**
* Emboss the image.
*
* @return $this
*/
public function emboss();
/**
* Smooth the image.
*
* @param int $p value between [-10,10]
*
* @return $this
*/
public function smooth($p);
/**
* Sharps the image.
*
* @return $this
*/
public function sharp();
/**
* Edges the image.
*
* @return $this
*/
public function edge();
/**
* Colorize the image.
*
* @param int $red value in range [-255, 255]
* @param int $green value in range [-255, 255]
* @param int $blue value in range [-255, 255]
*
* @return $this
*/
public function colorize($red, $green, $blue);
/**
* apply sepia to the image.
*
* @return $this
*/
public function sepia();
/**
* Merge with another image.
*
* @param Image $other
* @param int $x
* @param int $y
* @param int $width
* @param int $height
*
* @return $this
*/
public function merge(Image $other, $x = 0, $y = 0, $width = null, $height = null);
/**
* Rotate the image.
*
* @param float $angle
* @param int $background
*
* @return $this
*/
public function rotate($angle, $background = 0xffffff);
/**
* Fills the image.
*
* @param int $color
* @param int $x
* @param int $y
*
* @return $this
*/
public function fill($color = 0xffffff, $x = 0, $y = 0);
/**
* write text to the image.
*
* @param string $font
* @param string $text
* @param int $x
* @param int $y
* @param int $size
* @param int $angle
* @param int $color
* @param string $align
*/
public function write($font, $text, $x = 0, $y = 0, $size = 12, $angle = 0, $color = 0x000000, $align = 'left');
/**
* Draws a rectangle.
*
* @param int $x1
* @param int $y1
* @param int $x2
* @param int $y2
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function rectangle($x1, $y1, $x2, $y2, $color, $filled = false);
/**
* Draws a rounded rectangle.
*
* @param int $x1
* @param int $y1
* @param int $x2
* @param int $y2
* @param int $radius
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function roundedRectangle($x1, $y1, $x2, $y2, $radius, $color, $filled = false);
/**
* Draws a line.
*
* @param int $x1
* @param int $y1
* @param int $x2
* @param int $y2
* @param int $color
*
* @return $this
*/
public function line($x1, $y1, $x2, $y2, $color = 0x000000);
/**
* Draws an ellipse.
*
* @param int $cx
* @param int $cy
* @param int $width
* @param int $height
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function ellipse($cx, $cy, $width, $height, $color = 0x000000, $filled = false);
/**
* Draws a circle.
*
* @param int $cx
* @param int $cy
* @param int $r
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function circle($cx, $cy, $r, $color = 0x000000, $filled = false);
/**
* Draws a polygon.
*
* @param array $points
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function polygon(array $points, $color, $filled = false);
/**
* Flips the image.
*
* @param int $flipVertical
* @param int $flipHorizontal
*
* @return $this
*/
public function flip($flipVertical, $flipHorizontal);
}

View file

@ -0,0 +1,376 @@
<?php
namespace Gregwar\Image\Adapter;
abstract class Common extends Adapter
{
/**
* {@inheritdoc}
*/
public function zoomCrop($width, $height, $background = 'transparent', $xPosLetter = 'center', $yPosLetter = 'center')
{
// Calculate the different ratios
$originalRatio = $this->width() / $this->height();
$newRatio = $width / $height;
// Compare ratios
if ($originalRatio > $newRatio) {
// Original image is wider
$newHeight = $height;
$newWidth = (int) $height * $originalRatio;
} else {
// Equal width or smaller
$newHeight = (int) $width / $originalRatio;
$newWidth = $width;
}
// Perform resize
$this->resize($newWidth, $newHeight, $background, true);
// Define x position
switch ($xPosLetter) {
case 'L':
case 'left':
$xPos = 0;
break;
case 'R':
case 'right':
$xPos = (int) $newWidth - $width;
break;
default:
$xPos = (int) ($newWidth - $width) / 2;
}
// Define y position
switch ($yPosLetter) {
case 'T':
case 'top':
$yPos = 0;
break;
case 'B':
case 'bottom':
$yPos = (int) $newHeight - $height;
break;
default:
$yPos = (int) ($newHeight - $height) / 2;
}
// Crop image to reach desired size
$this->crop($xPos, $yPos, $width, $height);
return $this;
}
/**
* Resizes the image forcing the destination to have exactly the
* given width and the height.
*
* @param int $w the width
* @param int $h the height
* @param int $bg the background
*/
public function forceResize($width = null, $height = null, $background = 'transparent')
{
return $this->resize($width, $height, $background, true);
}
/**
* {@inheritdoc}
*/
public function scaleResize($width = null, $height = null, $background = 'transparent', $crop = false)
{
return $this->resize($width, $height, $background, false, true, $crop);
}
/**
* {@inheritdoc}
*/
public function cropResize($width = null, $height = null, $background = 'transparent')
{
return $this->resize($width, $height, $background, false, false, true);
}
/**
* Fix orientation using Exif informations.
*/
public function fixOrientation()
{
if (!in_array(exif_imagetype($this->source->getInfos()), array(IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM))) {
return $this;
}
if (!extension_loaded('exif')) {
throw new \RuntimeException('You need to EXIF PHP Extension to use this function');
}
$exif = @exif_read_data($this->source->getInfos());
if ($exif === false || !array_key_exists('Orientation', $exif)) {
return $this;
}
switch ($exif['Orientation']) {
case 1:
break;
case 2:
$this->flip(false, true);
break;
case 3: // 180 rotate left
$this->rotate(180);
break;
case 4: // vertical flip
$this->flip(true, false);
break;
case 5: // vertical flip + 90 rotate right
$this->flip(true, false);
$this->rotate(-90);
break;
case 6: // 90 rotate right
$this->rotate(-90);
break;
case 7: // horizontal flip + 90 rotate right
$this->flip(false, true);
$this->rotate(-90);
break;
case 8: // 90 rotate left
$this->rotate(90);
break;
}
return $this;
}
/**
* Opens the image.
*/
abstract protected function openGif($file);
abstract protected function openJpeg($file);
abstract protected function openPng($file);
/**
* Creates an image.
*/
abstract protected function createImage($width, $height);
/**
* Creating an image using $data.
*/
abstract protected function createImageFromData($data);
/**
* Loading image from $resource.
*/
protected function loadResource($resource)
{
$this->resource = $resource;
}
protected function loadFile($file, $type)
{
if (!$this->supports($type)) {
throw new \RuntimeException('Type '.$type.' is not supported by GD');
}
if ($type == 'jpeg') {
$this->openJpeg($file);
}
if ($type == 'gif') {
$this->openGif($file);
}
if ($type == 'png') {
$this->openPng($file);
}
if (false === $this->resource) {
throw new \UnexpectedValueException('Unable to open file ('.$file.')');
} else {
$this->convertToTrueColor();
}
}
/**
* {@inheritdoc}
*/
public function init()
{
$source = $this->source;
if ($source instanceof \Gregwar\Image\Source\File) {
$this->loadFile($source->getFile(), $source->guessType());
} elseif ($source instanceof \Gregwar\Image\Source\Create) {
$this->createImage($source->getWidth(), $source->getHeight());
} elseif ($source instanceof \Gregwar\Image\Source\Data) {
$this->createImageFromData($source->getData());
} elseif ($source instanceof \Gregwar\Image\Source\Resource) {
$this->loadResource($source->getResource());
} else {
throw new \Exception('Unsupported image source type '.get_class($source));
}
return $this;
}
/**
* {@inheritdoc}
*/
public function deinit()
{
$this->resource = null;
}
/**
* {@inheritdoc}
*/
public function resize($width = null, $height = null, $background = 'transparent', $force = false, $rescale = false, $crop = false)
{
$current_width = $this->width();
$current_height = $this->height();
$new_width = 0;
$new_height = 0;
$scale = 1.0;
if ($height === null && preg_match('#^(.+)%$#mUsi', $width, $matches)) {
$width = round($current_width * ((float) $matches[1] / 100.0));
$height = round($current_height * ((float) $matches[1] / 100.0));
}
if (!$rescale && (!$force || $crop)) {
if ($width != null && $current_width > $width) {
$scale = $current_width / $width;
}
if ($height != null && $current_height > $height) {
if ($current_height / $height > $scale) {
$scale = $current_height / $height;
}
}
} else {
if ($width != null) {
$scale = $current_width / $width;
$new_width = $width;
}
if ($height != null) {
if ($width != null && $rescale) {
$scale = max($scale, $current_height / $height);
} else {
$scale = $current_height / $height;
}
$new_height = $height;
}
}
if (!$force || $width == null || $rescale) {
$new_width = round($current_width / $scale);
}
if (!$force || $height == null || $rescale) {
$new_height = round($current_height / $scale);
}
if ($width == null || $crop) {
$width = $new_width;
}
if ($height == null || $crop) {
$height = $new_height;
}
$this->doResize($background, $width, $height, $new_width, $new_height);
}
/**
* Trim background color arround the image.
*
* @param int $bg the background
*/
protected function _trimColor($background = 'transparent')
{
$width = $this->width();
$height = $this->height();
$b_top = 0;
$b_lft = 0;
$b_btm = $height - 1;
$b_rt = $width - 1;
//top
for (; $b_top < $height; ++$b_top) {
for ($x = 0; $x < $width; ++$x) {
if ($this->getColor($x, $b_top) != $background) {
break 2;
}
}
}
// bottom
for (; $b_btm >= 0; --$b_btm) {
for ($x = 0; $x < $width; ++$x) {
if ($this->getColor($x, $b_btm) != $background) {
break 2;
}
}
}
// left
for (; $b_lft < $width; ++$b_lft) {
for ($y = $b_top; $y <= $b_btm; ++$y) {
if ($this->getColor($b_lft, $y) != $background) {
break 2;
}
}
}
// right
for (; $b_rt >= 0; --$b_rt) {
for ($y = $b_top; $y <= $b_btm; ++$y) {
if ($this->getColor($b_rt, $y) != $background) {
break 2;
}
}
}
++$b_btm;
++$b_rt;
$this->crop($b_lft, $b_top, $b_rt - $b_lft, $b_btm - $b_top);
}
/**
* Resizes the image to an image having size of $target_width, $target_height, using
* $new_width and $new_height and padding with $bg color.
*/
abstract protected function doResize($bg, $target_width, $target_height, $new_width, $new_height);
/**
* Gets the color of the $x, $y pixel.
*/
abstract protected function getColor($x, $y);
/**
* {@inheritdoc}
*/
public function enableProgressive()
{
throw new \Exception('The Adapter '.$this->getName().' does not support Progressive Image loading');
}
/**
* This does nothing, but can be used to tag a ressource for instance (having a final image hash
* for the cache different depending on the tag)
*/
public function tag($tag)
{
}
}

View file

@ -0,0 +1,652 @@
<?php
namespace Gregwar\Image\Adapter;
use Gregwar\Image\Image;
use Gregwar\Image\ImageColor;
class GD extends Common
{
public static $gdTypes = array(
'jpeg' => \IMG_JPG,
'gif' => \IMG_GIF,
'png' => \IMG_PNG,
);
protected function loadResource($resource)
{
parent::loadResource($resource);
imagesavealpha($this->resource, true);
}
/**
* Gets the width and the height for writing some text.
*/
public static function TTFBox($font, $text, $size, $angle = 0)
{
$box = imagettfbbox($size, $angle, $font, $text);
return array(
'width' => abs($box[2] - $box[0]),
'height' => abs($box[3] - $box[5]),
);
}
public function __construct()
{
parent::__construct();
if (!(extension_loaded('gd') && function_exists('gd_info'))) {
throw new \RuntimeException('You need to install GD PHP Extension to use this library');
}
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'GD';
}
/**
* {@inheritdoc}
*/
public function fillBackground($background = 0xffffff)
{
$w = $this->width();
$h = $this->height();
$n = imagecreatetruecolor($w, $h);
imagefill($n, 0, 0, ImageColor::gdAllocate($this->resource, $background));
imagecopyresampled($n, $this->resource, 0, 0, 0, 0, $w, $h, $w, $h);
imagedestroy($this->resource);
$this->resource = $n;
return $this;
}
/**
* Do the image resize.
*
* @return $this
*/
protected function doResize($bg, $target_width, $target_height, $new_width, $new_height)
{
$width = $this->width();
$height = $this->height();
$n = imagecreatetruecolor($target_width, $target_height);
if ($bg != 'transparent') {
imagefill($n, 0, 0, ImageColor::gdAllocate($this->resource, $bg));
} else {
imagealphablending($n, false);
$color = ImageColor::gdAllocate($this->resource, 'transparent');
imagefill($n, 0, 0, $color);
imagesavealpha($n, true);
}
imagecopyresampled($n, $this->resource, ($target_width - $new_width) / 2, ($target_height - $new_height) / 2, 0, 0, $new_width, $new_height, $width, $height);
imagedestroy($this->resource);
$this->resource = $n;
return $this;
}
/**
* {@inheritdoc}
*/
public function crop($x, $y, $width, $height)
{
$destination = imagecreatetruecolor($width, $height);
imagealphablending($destination, false);
imagesavealpha($destination, true);
imagecopy($destination, $this->resource, 0, 0, $x, $y, $this->width(), $this->height());
imagedestroy($this->resource);
$this->resource = $destination;
return $this;
}
/**
* {@inheritdoc}
*/
public function negate()
{
imagefilter($this->resource, IMG_FILTER_NEGATE);
return $this;
}
/**
* {@inheritdoc}
*/
public function brightness($brightness)
{
imagefilter($this->resource, IMG_FILTER_BRIGHTNESS, $brightness);
return $this;
}
/**
* {@inheritdoc}
*/
public function contrast($contrast)
{
imagefilter($this->resource, IMG_FILTER_CONTRAST, $contrast);
return $this;
}
/**
* {@inheritdoc}
*/
public function grayscale()
{
imagefilter($this->resource, IMG_FILTER_GRAYSCALE);
return $this;
}
/**
* {@inheritdoc}
*/
public function emboss()
{
imagefilter($this->resource, IMG_FILTER_EMBOSS);
return $this;
}
/**
* {@inheritdoc}
*/
public function smooth($p)
{
imagefilter($this->resource, IMG_FILTER_SMOOTH, $p);
return $this;
}
/**
* {@inheritdoc}
*/
public function sharp()
{
imagefilter($this->resource, IMG_FILTER_MEAN_REMOVAL);
return $this;
}
/**
* {@inheritdoc}
*/
public function edge()
{
imagefilter($this->resource, IMG_FILTER_EDGEDETECT);
return $this;
}
/**
* {@inheritdoc}
*/
public function colorize($red, $green, $blue)
{
imagefilter($this->resource, IMG_FILTER_COLORIZE, $red, $green, $blue);
return $this;
}
/**
* {@inheritdoc}
*/
public function sepia()
{
imagefilter($this->resource, IMG_FILTER_GRAYSCALE);
imagefilter($this->resource, IMG_FILTER_COLORIZE, 100, 50, 0);
return $this;
}
/**
* {@inheritdoc}
*/
public function gaussianBlur($blurFactor = 1)
{
$blurFactor = round($blurFactor); // blurFactor has to be an integer
$originalWidth = $this->width();
$originalHeight = $this->height();
$smallestWidth = ceil($originalWidth * pow(0.5, $blurFactor));
$smallestHeight = ceil($originalHeight * pow(0.5, $blurFactor));
// for the first run, the previous image is the original input
$prevImage = $this->resource;
$prevWidth = $originalWidth;
$prevHeight = $originalHeight;
// scale way down and gradually scale back up, blurring all the way
for ($i = 0; $i < $blurFactor; ++$i) {
// determine dimensions of next image
$nextWidth = $smallestWidth * pow(2, $i);
$nextHeight = $smallestHeight * pow(2, $i);
// resize previous image to next size
$nextImage = imagecreatetruecolor($nextWidth, $nextHeight);
imagecopyresized($nextImage, $prevImage, 0, 0, 0, 0,
$nextWidth, $nextHeight, $prevWidth, $prevHeight);
// apply blur filter
imagefilter($nextImage, IMG_FILTER_GAUSSIAN_BLUR);
// now the new image becomes the previous image for the next step
$prevImage = $nextImage;
$prevWidth = $nextWidth;
$prevHeight = $nextHeight;
}
// scale back to original size and blur one more time
imagecopyresized($this->resource, $nextImage,
0, 0, 0, 0, $originalWidth, $originalHeight, $nextWidth, $nextHeight);
imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR);
// clean up
imagedestroy($prevImage);
return $this;
}
/**
* {@inheritdoc}
*/
public function merge(Image $other, $x = 0, $y = 0, $width = null, $height = null)
{
$other = clone $other;
$other->init();
$other->applyOperations();
imagealphablending($this->resource, true);
if (null == $width) {
$width = $other->width();
}
if (null == $height) {
$height = $other->height();
}
imagecopyresampled($this->resource, $other->getAdapter()->getResource(), $x, $y, 0, 0, $width, $height, $width, $height);
return $this;
}
/**
* {@inheritdoc}
*/
public function rotate($angle, $background = 0xffffff)
{
$this->resource = imagerotate($this->resource, $angle, ImageColor::gdAllocate($this->resource, $background));
imagealphablending($this->resource, true);
imagesavealpha($this->resource, true);
return $this;
}
/**
* {@inheritdoc}
*/
public function fill($color = 0xffffff, $x = 0, $y = 0)
{
imagealphablending($this->resource, false);
imagefill($this->resource, $x, $y, ImageColor::gdAllocate($this->resource, $color));
return $this;
}
/**
* {@inheritdoc}
*/
public function write($font, $text, $x = 0, $y = 0, $size = 12, $angle = 0, $color = 0x000000, $align = 'left')
{
imagealphablending($this->resource, true);
if ($align != 'left') {
$sim_size = self::TTFBox($font, $text, $size, $angle);
if ($align == 'center') {
$x -= $sim_size['width'] / 2;
}
if ($align == 'right') {
$x -= $sim_size['width'];
}
}
imagettftext($this->resource, $size, $angle, $x, $y, ImageColor::gdAllocate($this->resource, $color), $font, $text);
return $this;
}
/**
* {@inheritdoc}
*/
public function rectangle($x1, $y1, $x2, $y2, $color, $filled = false)
{
if ($filled) {
imagefilledrectangle($this->resource, $x1, $y1, $x2, $y2, ImageColor::gdAllocate($this->resource, $color));
} else {
imagerectangle($this->resource, $x1, $y1, $x2, $y2, ImageColor::gdAllocate($this->resource, $color));
}
return $this;
}
/**
* {@inheritdoc}
*/
public function roundedRectangle($x1, $y1, $x2, $y2, $radius, $color, $filled = false)
{
if ($color) {
$color = ImageColor::gdAllocate($this->resource, $color);
}
if ($filled == true) {
imagefilledrectangle($this->resource, $x1 + $radius, $y1, $x2 - $radius, $y2, $color);
imagefilledrectangle($this->resource, $x1, $y1 + $radius, $x1 + $radius - 1, $y2 - $radius, $color);
imagefilledrectangle($this->resource, $x2 - $radius + 1, $y1 + $radius, $x2, $y2 - $radius, $color);
imagefilledarc($this->resource, $x1 + $radius, $y1 + $radius, $radius * 2, $radius * 2, 180, 270, $color, IMG_ARC_PIE);
imagefilledarc($this->resource, $x2 - $radius, $y1 + $radius, $radius * 2, $radius * 2, 270, 360, $color, IMG_ARC_PIE);
imagefilledarc($this->resource, $x1 + $radius, $y2 - $radius, $radius * 2, $radius * 2, 90, 180, $color, IMG_ARC_PIE);
imagefilledarc($this->resource, $x2 - $radius, $y2 - $radius, $radius * 2, $radius * 2, 360, 90, $color, IMG_ARC_PIE);
} else {
imageline($this->resource, $x1 + $radius, $y1, $x2 - $radius, $y1, $color);
imageline($this->resource, $x1 + $radius, $y2, $x2 - $radius, $y2, $color);
imageline($this->resource, $x1, $y1 + $radius, $x1, $y2 - $radius, $color);
imageline($this->resource, $x2, $y1 + $radius, $x2, $y2 - $radius, $color);
imagearc($this->resource, $x1 + $radius, $y1 + $radius, $radius * 2, $radius * 2, 180, 270, $color);
imagearc($this->resource, $x2 - $radius, $y1 + $radius, $radius * 2, $radius * 2, 270, 360, $color);
imagearc($this->resource, $x1 + $radius, $y2 - $radius, $radius * 2, $radius * 2, 90, 180, $color);
imagearc($this->resource, $x2 - $radius, $y2 - $radius, $radius * 2, $radius * 2, 360, 90, $color);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function line($x1, $y1, $x2, $y2, $color = 0x000000)
{
imageline($this->resource, $x1, $y1, $x2, $y2, ImageColor::gdAllocate($this->resource, $color));
return $this;
}
/**
* {@inheritdoc}
*/
public function ellipse($cx, $cy, $width, $height, $color = 0x000000, $filled = false)
{
if ($filled) {
imagefilledellipse($this->resource, $cx, $cy, $width, $height, ImageColor::gdAllocate($this->resource, $color));
} else {
imageellipse($this->resource, $cx, $cy, $width, $height, ImageColor::gdAllocate($this->resource, $color));
}
return $this;
}
/**
* {@inheritdoc}
*/
public function circle($cx, $cy, $r, $color = 0x000000, $filled = false)
{
return $this->ellipse($cx, $cy, $r, $r, ImageColor::gdAllocate($this->resource, $color), $filled);
}
/**
* {@inheritdoc}
*/
public function polygon(array $points, $color, $filled = false)
{
if ($filled) {
imagefilledpolygon($this->resource, $points, count($points) / 2, ImageColor::gdAllocate($this->resource, $color));
} else {
imagepolygon($this->resource, $points, count($points) / 2, ImageColor::gdAllocate($this->resource, $color));
}
return $this;
}
/**
* {@inheritdoc}
*/
public function flip($flipVertical, $flipHorizontal)
{
if (!$flipVertical && !$flipHorizontal) {
return $this;
}
if (function_exists('imageflip')) {
if ($flipVertical && $flipHorizontal) {
$flipMode = \IMG_FLIP_BOTH;
} elseif ($flipVertical && !$flipHorizontal) {
$flipMode = \IMG_FLIP_VERTICAL;
} elseif (!$flipVertical && $flipHorizontal) {
$flipMode = \IMG_FLIP_HORIZONTAL;
}
imageflip($this->resource, $flipMode);
} else {
$width = $this->width();
$height = $this->height();
$src_x = 0;
$src_y = 0;
$src_width = $width;
$src_height = $height;
if ($flipVertical) {
$src_y = $height - 1;
$src_height = -$height;
}
if ($flipHorizontal) {
$src_x = $width - 1;
$src_width = -$width;
}
$imgdest = imagecreatetruecolor($width, $height);
imagealphablending($imgdest, false);
imagesavealpha($imgdest, true);
if (imagecopyresampled($imgdest, $this->resource, 0, 0, $src_x, $src_y, $width, $height, $src_width, $src_height)) {
imagedestroy($this->resource);
$this->resource = $imgdest;
}
}
return $this;
}
/**
* {@inheritdoc}
*/
public function width()
{
if (null === $this->resource) {
$this->init();
}
return imagesx($this->resource);
}
/**
* {@inheritdoc}
*/
public function height()
{
if (null === $this->resource) {
$this->init();
}
return imagesy($this->resource);
}
protected function createImage($width, $height)
{
$this->resource = imagecreatetruecolor($width, $height);
}
protected function createImageFromData($data)
{
$this->resource = @imagecreatefromstring($data);
}
/**
* Converts the image to true color.
*/
protected function convertToTrueColor()
{
if (!imageistruecolor($this->resource)) {
if (function_exists('imagepalettetotruecolor')) {
// Available in PHP 5.5
imagepalettetotruecolor($this->resource);
} else {
$transparentIndex = imagecolortransparent($this->resource);
$w = $this->width();
$h = $this->height();
$img = imagecreatetruecolor($w, $h);
imagecopy($img, $this->resource, 0, 0, 0, 0, $w, $h);
if ($transparentIndex != -1) {
$width = $this->width();
$height = $this->height();
imagealphablending($img, false);
imagesavealpha($img, true);
for ($x = 0; $x < $width; ++$x) {
for ($y = 0; $y < $height; ++$y) {
if (imagecolorat($this->resource, $x, $y) == $transparentIndex) {
imagesetpixel($img, $x, $y, 127 << 24);
}
}
}
}
$this->resource = $img;
}
}
imagesavealpha($this->resource, true);
}
/**
* {@inheritdoc}
*/
public function saveGif($file)
{
$transColor = imagecolorallocatealpha($this->resource, 255, 255, 255, 127);
imagecolortransparent($this->resource, $transColor);
imagegif($this->resource, $file);
return $this;
}
/**
* {@inheritdoc}
*/
public function savePng($file)
{
imagepng($this->resource, $file);
return $this;
}
/**
* {@inheritdoc}
*/
public function saveWebp($file, $quality)
{
imagewebp($this->resource, $file, $quality);
return $this;
}
/**
* {@inheritdoc}
*/
public function saveJpeg($file, $quality)
{
imagejpeg($this->resource, $file, $quality);
return $this;
}
/**
* Try to open the file using jpeg.
*/
protected function openJpeg($file)
{
if (file_exists($file) && filesize($file)) {
$this->resource = @imagecreatefromjpeg($file);
} else {
$this->resource = false;
}
}
/**
* Try to open the file using gif.
*/
protected function openGif($file)
{
if (file_exists($file) && filesize($file)) {
$this->resource = @imagecreatefromgif($file);
} else {
$this->resource = false;
}
}
/**
* Try to open the file using PNG.
*/
protected function openPng($file)
{
if (file_exists($file) && filesize($file)) {
$this->resource = @imagecreatefrompng($file);
} else {
$this->resource = false;
}
}
/**
* Does this adapter supports type ?
*/
protected function supports($type)
{
return imagetypes() & self::$gdTypes[$type];
}
protected function getColor($x, $y)
{
return imagecolorat($this->resource, $x, $y);
}
/**
* {@inheritdoc}
*/
public function enableProgressive()
{
imageinterlace($this->resource, 1);
return $this;
}
}

View file

@ -0,0 +1,432 @@
<?php
namespace Gregwar\Image\Adapter;
use Gregwar\Image\Image;
class Imagick extends Common
{
public function __construct()
{
throw new \Exception('Imagick is not supported right now');
}
/**
* Gets the name of the adapter.
*
* @return string
*/
public function getName()
{
return 'ImageMagick';
}
/**
* Image width.
*
* @return int
*/
public function width()
{
// TODO: Implement width() method.
}
/**
* Image height.
*
* @return int
*/
public function height()
{
// TODO: Implement height() method.
}
/**
* Save the image as a gif.
*
* @return $this
*/
public function saveGif($file)
{
// TODO: Implement saveGif() method.
}
/**
* Save the image as a png.
*
* @return $this
*/
public function savePng($file)
{
// TODO: Implement savePng() method.
}
/**
* Save the image as a jpeg.
*
* @return $this
*/
public function saveJpeg($file, $quality)
{
// TODO: Implement saveJpeg() method.
}
/**
* Crops the image.
*
* @param int $x the top-left x position of the crop box
* @param int $y the top-left y position of the crop box
* @param int $width the width of the crop box
* @param int $height the height of the crop box
*
* @return $this
*/
public function crop($x, $y, $width, $height)
{
// TODO: Implement crop() method.
}
/**
* Fills the image background to $bg if the image is transparent.
*
* @param int $background background color
*
* @return $this
*/
public function fillBackground($background = 0xffffff)
{
// TODO: Implement fillBackground() method.
}
/**
* Negates the image.
*
* @return $this
*/
public function negate()
{
// TODO: Implement negate() method.
}
/**
* Changes the brightness of the image.
*
* @param int $brightness the brightness
*
* @return $this
*/
public function brightness($brightness)
{
// TODO: Implement brightness() method.
}
/**
* Contrasts the image.
*
* @param int $contrast the contrast [-100, 100]
*
* @return $this
*/
public function contrast($contrast)
{
// TODO: Implement contrast() method.
}
/**
* Apply a grayscale level effect on the image.
*
* @return $this
*/
public function grayscale()
{
// TODO: Implement grayscale() method.
}
/**
* Emboss the image.
*
* @return $this
*/
public function emboss()
{
// TODO: Implement emboss() method.
}
/**
* Smooth the image.
*
* @param int $p value between [-10,10]
*
* @return $this
*/
public function smooth($p)
{
// TODO: Implement smooth() method.
}
/**
* Sharps the image.
*
* @return $this
*/
public function sharp()
{
// TODO: Implement sharp() method.
}
/**
* Edges the image.
*
* @return $this
*/
public function edge()
{
// TODO: Implement edge() method.
}
/**
* Colorize the image.
*
* @param int $red value in range [-255, 255]
* @param int $green value in range [-255, 255]
* @param int $blue value in range [-255, 255]
*
* @return $this
*/
public function colorize($red, $green, $blue)
{
// TODO: Implement colorize() method.
}
/**
* apply sepia to the image.
*
* @return $this
*/
public function sepia()
{
// TODO: Implement sepia() method.
}
/**
* Merge with another image.
*
* @param Image $other
* @param int $x
* @param int $y
* @param int $width
* @param int $height
*
* @return $this
*/
public function merge(Image $other, $x = 0, $y = 0, $width = null, $height = null)
{
// TODO: Implement merge() method.
}
/**
* Rotate the image.
*
* @param float $angle
* @param int $background
*
* @return $this
*/
public function rotate($angle, $background = 0xffffff)
{
// TODO: Implement rotate() method.
}
/**
* Fills the image.
*
* @param int $color
* @param int $x
* @param int $y
*
* @return $this
*/
public function fill($color = 0xffffff, $x = 0, $y = 0)
{
// TODO: Implement fill() method.
}
/**
* write text to the image.
*
* @param string $font
* @param string $text
* @param int $x
* @param int $y
* @param int $size
* @param int $angle
* @param int $color
* @param string $align
*/
public function write($font, $text, $x = 0, $y = 0, $size = 12, $angle = 0, $color = 0x000000, $align = 'left')
{
// TODO: Implement write() method.
}
/**
* Draws a rectangle.
*
* @param int $x1
* @param int $y1
* @param int $x2
* @param int $y2
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function rectangle($x1, $y1, $x2, $y2, $color, $filled = false)
{
// TODO: Implement rectangle() method.
}
/**
* Draws a rounded rectangle.
*
* @param int $x1
* @param int $y1
* @param int $x2
* @param int $y2
* @param int $radius
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function roundedRectangle($x1, $y1, $x2, $y2, $radius, $color, $filled = false)
{
// TODO: Implement roundedRectangle() method.
}
/**
* Draws a line.
*
* @param int $x1
* @param int $y1
* @param int $x2
* @param int $y2
* @param int $color
*
* @return $this
*/
public function line($x1, $y1, $x2, $y2, $color = 0x000000)
{
// TODO: Implement line() method.
}
/**
* Draws an ellipse.
*
* @param int $cx
* @param int $cy
* @param int $width
* @param int $height
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function ellipse($cx, $cy, $width, $height, $color = 0x000000, $filled = false)
{
// TODO: Implement ellipse() method.
}
/**
* Draws a circle.
*
* @param int $cx
* @param int $cy
* @param int $r
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function circle($cx, $cy, $r, $color = 0x000000, $filled = false)
{
// TODO: Implement circle() method.
}
/**
* Draws a polygon.
*
* @param array $points
* @param int $color
* @param bool $filled
*
* @return $this
*/
public function polygon(array $points, $color, $filled = false)
{
// TODO: Implement polygon() method.
}
/**
* {@inheritdoc}
*/
public function flip($flipVertical, $flipHorizontal)
{
// TODO: Implement flip method
}
/**
* Opens the image.
*/
protected function openGif($file)
{
// TODO: Implement openGif() method.
}
protected function openJpeg($file)
{
// TODO: Implement openJpeg() method.
}
protected function openPng($file)
{
// TODO: Implement openPng() method.
}
protected function openWebp($file)
{
// TODO: Implement openWebp() method.
}
/**
* Creates an image.
*/
protected function createImage($width, $height)
{
// TODO: Implement createImage() method.
}
/**
* Creating an image using $data.
*/
protected function createImageFromData($data)
{
// TODO: Implement createImageFromData() method.
}
/**
* Resizes the image to an image having size of $target_width, $target_height, using
* $new_width and $new_height and padding with $bg color.
*/
protected function doResize($bg, $target_width, $target_height, $new_width, $new_height)
{
// TODO: Implement doResize() method.
}
/**
* Gets the color of the $x, $y pixel.
*/
protected function getColor($x, $y)
{
// TODO: Implement getColor() method.
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Gregwar\Image\Exceptions;
class GenerationError extends \Exception
{
public function __construct($newNewFile)
{
$this->newNewFile = $newNewFile;
}
public function getNewFile()
{
return $this->newNewFile;
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace Gregwar\Image;
/**
* Garbage collect a directory, this will crawl a directory, lookng
* for files older than X days and destroy them.
*
* @author Gregwar <g.passault@gmail.com>
*/
class GarbageCollect
{
/**
* Drops old files of a directory.
*
* @param string $directory the name of the target directory
* @param int $days the number of days to consider a file old
* @param bool $verbose enable verbose output
*
* @return true if all the files/directories of a directory was wiped
*/
public static function dropOldFiles($directory, $days = 30, $verbose = false)
{
$allDropped = true;
$now = time();
$dir = opendir($directory);
if (!$dir) {
if ($verbose) {
echo "! Unable to open $directory\n";
}
return false;
}
while ($file = readdir($dir)) {
if ($file == '.' || $file == '..') {
continue;
}
$fullName = $directory.'/'.$file;
$old = $now - filemtime($fullName);
if (is_dir($fullName)) {
// Directories are recursively crawled
if (static::dropOldFiles($fullName, $days, $verbose)) {
self::drop($fullName, $verbose);
} else {
$allDropped = false;
}
} else {
if ($old > (24 * 60 * 60 * $days)) {
self::drop($fullName, $verbose);
} else {
$allDropped = false;
}
}
}
closedir($dir);
return $allDropped;
}
/**
* Drops a file or an empty directory.
*/
public static function drop($file, $verbose = false)
{
if (is_dir($file)) {
@rmdir($file);
} else {
@unlink($file);
}
if ($verbose) {
echo "> Dropping $file...\n";
}
}
}

View file

@ -0,0 +1,796 @@
<?php
namespace Gregwar\Image;
use Gregwar\Cache\CacheInterface;
use Gregwar\Image\Adapter\AdapterInterface;
use Gregwar\Image\Exceptions\GenerationError;
/**
* Images handling class.
*
* @author Gregwar <g.passault@gmail.com>
*
* @method Image saveGif($file)
* @method Image savePng($file)
* @method Image saveJpeg($file, $quality)
* @method Image resize($width = null, $height = null, $background = 'transparent', $force = false, $rescale = false, $crop = false)
* @method Image forceResize($width = null, $height = null, $background = 'transparent')
* @method Image scaleResize($width = null, $height = null, $background = 'transparent', $crop = false)
* @method Image cropResize($width = null, $height = null, $background=0xffffff)
* @method Image scale($width = null, $height = null, $background=0xffffff, $crop = false)
* @method Image ($width = null, $height = null, $background = 0xffffff, $force = false, $rescale = false, $crop = false)
* @method Image crop($x, $y, $width, $height)
* @method Image enableProgressive()
* @method Image force($width = null, $height = null, $background = 0xffffff)
* @method Image zoomCrop($width, $height, $background = 0xffffff, $xPos, $yPos)
* @method Image fillBackground($background = 0xffffff)
* @method Image negate()
* @method Image brightness($brightness)
* @method Image contrast($contrast)
* @method Image grayscale()
* @method Image emboss()
* @method Image smooth($p)
* @method Image sharp()
* @method Image edge()
* @method Image colorize($red, $green, $blue)
* @method Image sepia()
* @method Image merge(Image $other, $x = 0, $y = 0, $width = null, $height = null)
* @method Image rotate($angle, $background = 0xffffff)
* @method Image fill($color = 0xffffff, $x = 0, $y = 0)
* @method Image write($font, $text, $x = 0, $y = 0, $size = 12, $angle = 0, $color = 0x000000, $align = 'left')
* @method Image rectangle($x1, $y1, $x2, $y2, $color, $filled = false)
* @method Image roundedRectangle($x1, $y1, $x2, $y2, $radius, $color, $filled = false)
* @method Image line($x1, $y1, $x2, $y2, $color = 0x000000)
* @method Image ellipse($cx, $cy, $width, $height, $color = 0x000000, $filled = false)
* @method Image circle($cx, $cy, $r, $color = 0x000000, $filled = false)
* @method Image polygon(array $points, $color, $filled = false)
* @method Image flip($flipVertical, $flipHorizontal)
*/
class Image
{
/**
* Directory to use for file caching.
*/
protected $cacheDir = 'cache/images';
/**
* Directory cache mode.
*/
protected $cacheMode = null;
/**
* Internal adapter.
*
* @var AdapterInterface
*/
protected $adapter = null;
/**
* Pretty name for the image.
*/
protected $prettyName = '';
protected $prettyPrefix;
/**
* Transformations hash.
*/
protected $hash = null;
/**
* The image source.
*/
protected $source = null;
/**
* Force image caching, even if there is no operation applied.
*/
protected $forceCache = true;
/**
* Supported types.
*/
public static $types = array(
'jpg' => 'jpeg',
'jpeg' => 'jpeg',
'webp' => 'webp',
'png' => 'png',
'gif' => 'gif',
);
/**
* Fallback image.
*/
protected $fallback;
/**
* Use fallback image.
*/
protected $useFallbackImage = true;
/**
* Cache system.
*
* @var \Gregwar\Cache\CacheInterface
*/
protected $cache;
/**
* Get the cache system.
*
* @return \Gregwar\Cache\CacheInterface
*/
public function getCacheSystem()
{
if (is_null($this->cache)) {
$this->cache = new \Gregwar\Cache\Cache();
$this->cache->setCacheDirectory($this->cacheDir);
}
return $this->cache;
}
/**
* Set the cache system.
*
* @param \Gregwar\Cache\CacheInterface $cache
*/
public function setCacheSystem(CacheInterface $cache)
{
$this->cache = $cache;
}
/**
* Change the caching directory.
*/
public function setCacheDir($cacheDir)
{
$this->getCacheSystem()->setCacheDirectory($cacheDir);
return $this;
}
/**
* @param int $dirMode
*/
public function setCacheDirMode($dirMode)
{
$this->cache->setDirectoryMode($dirMode);
}
/**
* Enable or disable to force cache even if the file is unchanged.
*/
public function setForceCache($forceCache = true)
{
$this->forceCache = $forceCache;
return $this;
}
/**
* The actual cache dir.
*/
public function setActualCacheDir($actualCacheDir)
{
$this->getCacheSystem()->setActualCacheDirectory($actualCacheDir);
return $this;
}
/**
* Sets the pretty name of the image.
*/
public function setPrettyName($name, $prefix = true)
{
if (empty($name)) {
return $this;
}
$this->prettyName = $this->urlize($name);
$this->prettyPrefix = $prefix;
return $this;
}
/**
* Urlizes the prettyName.
*/
protected function urlize($name)
{
$transliterator = '\Behat\Transliterator\Transliterator';
if (class_exists($transliterator)) {
$name = $transliterator::transliterate($name);
$name = $transliterator::urlize($name);
} else {
$name = strtolower($name);
$name = str_replace(' ', '-', $name);
$name = preg_replace('/([^a-z0-9\-]+)/m', '', $name);
}
return $name;
}
/**
* Operations array.
*/
protected $operations = array();
public function __construct($originalFile = null, $width = null, $height = null)
{
$this->setFallback(null);
if ($originalFile) {
$this->source = new Source\File($originalFile);
} else {
$this->source = new Source\Create($width, $height);
}
}
/**
* Sets the image data.
*/
public function setData($data)
{
$this->source = new Source\Data($data);
}
/**
* Sets the resource.
*/
public function setResource($resource)
{
$this->source = new Source\Resource($resource);
}
/**
* Use the fallback image or not.
*/
public function useFallback($useFallbackImage = true)
{
$this->useFallbackImage = $useFallbackImage;
return $this;
}
/**
* Sets the fallback image to use.
*/
public function setFallback($fallback = null)
{
if ($fallback === null) {
$this->fallback = __DIR__.'/images/error.jpg';
} else {
$this->fallback = $fallback;
}
return $this;
}
/**
* Gets the fallack image path.
*/
public function getFallback()
{
return $this->fallback;
}
/**
* Gets the fallback into the cache dir.
*/
public function getCacheFallback()
{
$fallback = $this->fallback;
return $this->getCacheSystem()->getOrCreateFile('fallback.jpg', array(), function ($target) use ($fallback) {
copy($fallback, $target);
});
}
/**
* @return AdapterInterface
*/
public function getAdapter()
{
if (null === $this->adapter) {
// Defaults to GD
$this->setAdapter('gd');
}
return $this->adapter;
}
public function setAdapter($adapter)
{
if ($adapter instanceof Adapter\Adapter) {
$this->adapter = $adapter;
} else {
if (is_string($adapter)) {
$adapter = strtolower($adapter);
switch ($adapter) {
case 'gd':
$this->adapter = new Adapter\GD();
break;
case 'imagemagick':
case 'imagick':
$this->adapter = new Adapter\Imagick();
break;
default:
throw new \Exception('Unknown adapter: '.$adapter);
break;
}
} else {
throw new \Exception('Unable to load the given adapter (not string or Adapter)');
}
}
$this->adapter->setSource($this->source);
}
/**
* Get the file path.
*
* @return mixed a string with the filen name, null if the image
* does not depends on a file
*/
public function getFilePath()
{
if ($this->source instanceof Source\File) {
return $this->source->getFile();
} else {
return;
}
}
/**
* Defines the file only after instantiation.
*
* @param string $originalFile the file path
*/
public function fromFile($originalFile)
{
$this->source = new Source\File($originalFile);
return $this;
}
/**
* Tells if the image is correct.
*/
public function correct()
{
return $this->source->correct();
}
/**
* Guess the file type.
*/
public function guessType()
{
return $this->source->guessType();
}
/**
* Adds an operation.
*/
protected function addOperation($method, $args)
{
$this->operations[] = array($method, $args);
}
/**
* Generic function.
*/
public function __call($methodName, $args)
{
$adapter = $this->getAdapter();
$reflection = new \ReflectionClass(get_class($adapter));
if ($reflection->hasMethod($methodName)) {
$method = $reflection->getMethod($methodName);
if ($method->getNumberOfRequiredParameters() > count($args)) {
throw new \InvalidArgumentException('Not enough arguments given for '.$methodName);
}
$this->addOperation($methodName, $args);
return $this;
}
throw new \BadFunctionCallException('Invalid method: '.$methodName);
}
/**
* Serialization of operations.
*/
public function serializeOperations()
{
$datas = array();
foreach ($this->operations as $operation) {
$method = $operation[0];
$args = $operation[1];
foreach ($args as &$arg) {
if ($arg instanceof self) {
$arg = $arg->getHash();
}
}
$datas[] = array($method, $args);
}
return serialize($datas);
}
/**
* Generates the hash.
*/
public function generateHash($type = 'guess', $quality = 80)
{
$inputInfos = $this->source->getInfos();
$datas = array(
$inputInfos,
$this->serializeOperations(),
$type,
$quality,
);
$this->hash = sha1(serialize($datas));
}
/**
* Gets the hash.
*/
public function getHash($type = 'guess', $quality = 80)
{
if (null === $this->hash) {
$this->generateHash($type, $quality);
}
return $this->hash;
}
/**
* Gets the cache file name and generate it if it does not exists.
* Note that if it exists, all the image computation process will
* not be done.
*
* @param string $type the image type
* @param int $quality the quality (for JPEG)
*/
public function cacheFile($type = 'jpg', $quality = 80, $actual = false)
{
if ($type == 'guess') {
$type = $this->guessType();
}
if (!count($this->operations) && $type == $this->guessType() && !$this->forceCache) {
return $this->getFilename($this->getFilePath());
}
// Computes the hash
$this->hash = $this->getHash($type, $quality);
// Generates the cache file
$cacheFile = '';
if (!$this->prettyName || $this->prettyPrefix) {
$cacheFile .= $this->hash;
}
if ($this->prettyPrefix) {
$cacheFile .= '-';
}
if ($this->prettyName) {
$cacheFile .= $this->prettyName;
}
$cacheFile .= '.'.$type;
// If the files does not exists, save it
$image = $this;
// Target file should be younger than all the current image
// dependencies
$conditions = array(
'younger-than' => $this->getDependencies(),
);
// The generating function
$generate = function ($target) use ($image, $type, $quality) {
$result = $image->save($target, $type, $quality);
if ($result != $target) {
throw new GenerationError($result);
}
};
// Asking the cache for the cacheFile
try {
$file = $this->getCacheSystem()->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
} catch (GenerationError $e) {
$file = $e->getNewFile();
}
// Nulling the resource
$this->getAdapter()->setSource(new Source\File($file));
$this->getAdapter()->deinit();
if ($actual) {
return $file;
} else {
return $this->getFilename($file);
}
}
/**
* Get cache data (to render the image).
*
* @param string $type the image type
* @param int $quality the quality (for JPEG)
*/
public function cacheData($type = 'jpg', $quality = 80)
{
return file_get_contents($this->cacheFile($type, $quality));
}
/**
* Hook to helps to extends and enhance this class.
*/
protected function getFilename($filename)
{
return $filename;
}
/**
* Generates and output a jpeg cached file.
*/
public function jpeg($quality = 80)
{
return $this->cacheFile('jpg', $quality);
}
/**
* Generates and output a gif cached file.
*/
public function gif()
{
return $this->cacheFile('gif');
}
/**
* Generates and output a png cached file.
*/
public function png()
{
return $this->cacheFile('png');
}
/**
* Generates and output a png cached file.
*/
public function webp()
{
return $this->cacheFile('webp');
}
/**
* Generates and output an image using the same type as input.
*/
public function guess($quality = 80)
{
return $this->cacheFile('guess', $quality);
}
/**
* Get all the files that this image depends on.
*
* @return string[] this is an array of strings containing all the files that the
* current Image depends on
*/
public function getDependencies()
{
$dependencies = array();
$file = $this->getFilePath();
if ($file) {
$dependencies[] = $file;
}
foreach ($this->operations as $operation) {
foreach ($operation[1] as $argument) {
if ($argument instanceof self) {
$dependencies = array_merge($dependencies, $argument->getDependencies());
}
}
}
return $dependencies;
}
/**
* Applies the operations.
*/
public function applyOperations()
{
// Renders the effects
foreach ($this->operations as $operation) {
call_user_func_array(array($this->adapter, $operation[0]), $operation[1]);
}
}
/**
* Initialize the adapter.
*/
public function init()
{
$this->getAdapter()->init();
}
/**
* Save the file to a given output.
*/
public function save($file, $type = 'guess', $quality = 80)
{
if ($file) {
$directory = dirname($file);
if (!is_dir($directory)) {
@mkdir($directory, 0777, true);
}
}
if (is_int($type)) {
$quality = $type;
$type = 'jpeg';
}
if ($type == 'guess') {
$type = $this->guessType();
}
if (!isset(self::$types[$type])) {
throw new \InvalidArgumentException('Given type ('.$type.') is not valid');
}
$type = self::$types[$type];
try {
$this->init();
$this->applyOperations();
$success = false;
if (null == $file) {
ob_start();
}
if ($type == 'jpeg') {
$success = $this->getAdapter()->saveJpeg($file, $quality);
}
if ($type == 'gif') {
$success = $this->getAdapter()->saveGif($file);
}
if ($type == 'png') {
$success = $this->getAdapter()->savePng($file);
}
if ($type == 'webp') {
$success = $this->getAdapter()->saveWebP($file, $quality);
}
if (!$success) {
return false;
}
return null === $file ? ob_get_clean() : $file;
} catch (\Exception $e) {
if ($this->useFallbackImage) {
return null === $file ? file_get_contents($this->fallback) : $this->getCacheFallback();
} else {
throw $e;
}
}
}
/**
* Get the contents of the image.
*/
public function get($type = 'guess', $quality = 80)
{
return $this->save(null, $type, $quality);
}
/* Image API */
/**
* Image width.
*/
public function width()
{
return $this->getAdapter()->width();
}
/**
* Image height.
*/
public function height()
{
return $this->getAdapter()->height();
}
/**
* Tostring defaults to jpeg.
*/
public function __toString()
{
return $this->guess();
}
/**
* Returning basic html code for this image.
*/
public function html($title = '', $type = 'jpg', $quality = 80)
{
return '<img title="'.$title.'" src="'.$this->cacheFile($type, $quality).'" />';
}
/**
* Returns the Base64 inlinable representation.
*/
public function inline($type = 'jpg', $quality = 80)
{
$mime = $type;
if ($mime == 'jpg') {
$mime = 'jpeg';
}
return 'data:image/'.$mime.';base64,'.base64_encode(file_get_contents($this->cacheFile($type, $quality, true)));
}
/**
* Creates an instance, usefull for one-line chaining.
*/
public static function open($file = '')
{
return new static($file);
}
/**
* Creates an instance of a new resource.
*/
public static function create($width, $height)
{
return new static(null, $width, $height);
}
/**
* Creates an instance of image from its data.
*/
public static function fromData($data)
{
$image = new static();
$image->setData($data);
return $image;
}
/**
* Creates an instance of image from resource.
*/
public static function fromResource($resource)
{
$image = new static();
$image->setResource($resource);
return $image;
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Gregwar\Image;
/**
* Color manipulation class.
*/
class ImageColor
{
private static $colors = array(
'black' => 0x000000,
'silver' => 0xc0c0c0,
'gray' => 0x808080,
'teal' => 0x008080,
'aqua' => 0x00ffff,
'blue' => 0x0000ff,
'navy' => 0x000080,
'green' => 0x008000,
'lime' => 0x00ff00,
'white' => 0xffffff,
'fuschia' => 0xff00ff,
'purple' => 0x800080,
'olive' => 0x808000,
'yellow' => 0xffff00,
'orange' => 0xffA500,
'red' => 0xff0000,
'maroon' => 0x800000,
'transparent' => 0x7fffffff,
);
public static function gdAllocate($image, $color)
{
$colorRGBA = self::parse($color);
$b = ($colorRGBA) & 0xff;
$colorRGBA >>= 8;
$g = ($colorRGBA) & 0xff;
$colorRGBA >>= 8;
$r = ($colorRGBA) & 0xff;
$colorRGBA >>= 8;
$a = ($colorRGBA) & 0xff;
$c = imagecolorallocatealpha($image, $r, $g, $b, $a);
if ($color == 'transparent') {
imagecolortransparent($image, $c);
}
return $c;
}
public static function parse($color)
{
// Direct color representation (ex: 0xff0000)
if (!is_string($color) && is_numeric($color)) {
return $color;
}
// Color name (ex: "red")
if (isset(self::$colors[$color])) {
return self::$colors[$color];
}
if (is_string($color)) {
$color_string = str_replace(' ', '', $color);
// Color string (ex: "ff0000", "#ff0000" or "0xfff")
if (preg_match('/^(#|0x|)([0-9a-f]{3,6})/i', $color_string, $matches)) {
$col = $matches[2];
if (strlen($col) == 6) {
return hexdec($col);
}
if (strlen($col) == 3) {
$r = '';
for ($i = 0; $i < 3; ++$i) {
$r .= $col[$i].$col[$i];
}
return hexdec($r);
}
}
// Colors like "rgb(255, 0, 0)"
if (preg_match('/^rgb\(([0-9]+),([0-9]+),([0-9]+)\)/i', $color_string, $matches)) {
$r = $matches[1];
$g = $matches[2];
$b = $matches[3];
if ($r >= 0 && $r <= 0xff && $g >= 0 && $g <= 0xff && $b >= 0 && $b <= 0xff) {
return ($r << 16) | ($g << 8) | ($b);
}
}
}
throw new \InvalidArgumentException('Invalid color: '.$color);
}
}

View file

@ -0,0 +1,19 @@
Copyright (c) <2012-2017> Grégoire Passault
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,5 @@
cs:
php-cs-fixer fix --verbose
test:
phpunit -c phpunit.xml.dist

View file

@ -0,0 +1,276 @@
# Gregwar's Image class
[![Build status](https://travis-ci.org/Gregwar/Image.svg?branch=master)](https://travis-ci.org/Gregwar/Image)
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YUXRLWHQSWS6L)
The `Gregwar\Image` class purpose is to provide a simple object-oriented images handling and caching API.
# Installation
With composer :
``` json
{
...
"require": {
"gregwar/image": "2.*"
}
}
```
# Usage
## Basic handling
Using methods chaining, you can open, transform and save a file in a single line:
```php
<?php use Gregwar\Image\Image;
Image::open('in.png')
->resize(100, 100)
->negate()
->save('out.jpg');
```
Here are the resize methods:
* `resize($width, $height, $background)`: resizes the image, will preserve scale and never
enlarge it (background is `red` in order to understand what happens):
![resize()](doc/resize.jpg)
* `scaleResize($width, $height, $background)`: resizes the image, will preserve scale, can enlarge
it (background is `red` in order to understand what happens):
![scaleResize()](doc/scaleResize.jpg)
* `forceResize($width, $height, $background)`: resizes the image forcing it to
be exactly `$width` by `$height`
![forceResize()](doc/forceResize.jpg)
* `cropResize($width, $height, $background)`: resizes the image preserving scale (just like `resize()`)
and croping the whitespaces:
![cropResize()](doc/cropResize.jpg)
* `zoomCrop($width, $height, $background, $xPos, $yPos)`: resize and crop the image to fit to given dimensions:
![zoomCrop()](doc/zoomCrop.jpg)
* In `zoomCrop()`, You can change the position of the resized image using the `$xPos` (center, left or right) and `$yPos` (center,
top or bottom):
![zoomCrop() with yPos=top](doc/zoomCropTop.jpg)
The other methods available are:
* `crop($x, $y, $w, $h)`: crops the image to a box located on coordinates $x,y and
which size is $w by $h
* `negate()`: negates the image colors
* `brighness($b)`: applies a brightness effect to the image (from -255 to +255)
* `contrast($c)`: applies a contrast effect to the image (from -100 to +100)
* `grayscale()`: converts the image to grayscale
* `emboss()`: emboss the image
* `smooth($p)`: smooth the image
* `sharp()`: applies a mean removal filter on the image
* `edge()`: applies an edge effect on the image
* `colorize($red, $green, $blue)`: colorize the image (from -255 to +255 for each color)
* `sepia()`: applies a sepia effect
* `merge($image, $x, $y, $width, $height)`: merges two images
* `fill($color, $x, $y)`: fills the image with the given color
* `write($font, $text, $x, $y, $size, $angle, $color, $position)`: writes text over image, $position can be any of 'left', 'right', or 'center'
* `rectangle($x1, $y1, $x2, $y2, $color, $filled=false)`: draws a rectangle
* `rotate($angle, $background = 0xffffff)` : rotate the image to given angle
* `roundedRectangle($x1, $y1, $x2, $y2, $radius, $color, $filled=false)`: draws a rounded rectangle ($radius can be anything from 0)
* `line($x1, $y1, $x2, $y2, $color)`: draws a line
* `ellipse($cx, $cy, $width, $height, $color, $filled=false)`: draws an ellipse
* `circle($cx, $cy, $r, $color, $filled=false)`: draws a circle
* `fillBackground($bg=0xffffff)`: fills the background of a transparent image to the 'bg' color
* `fixOrientation()`: return the image rotated and flipped using image exif information
* `html($title = '', $type = 'jpg')`: return the `<img ... />` tag with the cache image
* `flip($flipVertical, $flipHorizontal)`: flips the image in the given directions. Both params are boolean and at least one must be true.
* `inline($type = 'jpg')`: returns the HTML inlinable base64 string (see `demo/inline.php`)
You can also create image from scratch using:
```php
<?php
Image::create(200, 100);
```
Where 200 is the width and 100 the height
## Saving the image
You can save the image to an explicit file using `save($file, $type = 'jpg', $quality = 80)`:
```php
<?php
// ...
$image->save('output.jpg', 'jpg', 85);
```
You can also get the contents of the image using `get($type = 'jpg', $quality = 80)`, which will return the binary contents of the image
## Using cache
Each operation above is not actually applied on the opened image, but added in an operations
array. This operation array, the name, type and modification time of file are hashed using
`sha1()` and the hash is used to look up for a cache file.
Once the cache directory configured, you can call the following methods:
* `jpeg($quality = 80)`: lookup or create a jpeg cache file on-the-fly
* `gif()`: lookup or create a gif cache file on-the-fly
* `png()`: lookup or create a png cache file on-the-fly
* `guess($quality = 80)`: guesses the type (use the same as input) and lookup or create a
cache file on-the-fly
* `setPrettyName($prettyName, $prefix = true)`: sets a "pretty" name suffix for the file, if you want it to be more SEO-friendly.
for instance, if you call it "Fancy Image", the cache will look like something/something-fancy-image.jpg.
If `$prefix` is passed to `false` (default `true`), the pretty name won't have any hash prefix.
If you want to use non-latin1 pretty names, **behat/transliterator** package must be installed.
For instance:
```php
<?php use Gregwar\Image\Image;
echo Image::open('test.png')
->sepia()
->jpeg();
//Outputs: cache/images/1/8/6/9/c/86e4532dbd9c073075ef08e9751fc9bc0f4.jpg
```
If the original file and operations do not change, the hashed value will be the same and the
cache will not be generated again.
You can use this directly in an HTML document:
```php
<?php use Gregwar\Image\Image;
// ...
<img src="<?php echo Image::open('image.jpg')->resize(150, 150)->jpeg(); ?>" />
// ...
```
This is powerful since if you change the original image or any of your code the cached hash
will change and the file will be regenerated.
Writing image
-------------
You can also create your own image on-the-fly using drawing functions:
```php
<?php
$img_src = Image::create(300, 300)
->fill(0xffaaaa) // Filling with a light red
->rectangle(0xff3333, 0, 100, 300, 200, true) // Drawing a red rectangle
// Writing "Hello $username !" on the picture using a custom TTF font file
->write('./fonts/CaviarDreams.ttf', 'Hello '.$username.'!', 150, 150, 20, 0, 'white', 'center')
->jpeg();
?>
<img src="<?= $img_src ?>" />
```
## Using fallback image
If the image file doesn't exists, you can configurate a fallback image that will be used
by the class (note that this require the cache directory to be available).
A default "error" image which is used is in `images/error.jpg`, you can change it with:
```php
<?php
$img->setFallback('/path/to/my/fallback.jpg');
```
## Garbage Collect
To prevent the cache from growing forever, you can use the provided GarbageCollect class as below:
```php
<?php use Gregwar\Image\GarbageCollect;
// This could be a cron called each day @3:00AM for instance
// Removes all the files from ../cache that are more than 30 days
// old. A verbose output will explain which files are deleted
GarbageCollect::dropOldFiles(__DIR__.'/../cache', 30, true);
```
# Development
`Gregwar\Image` is using PHP metaprogramming paradigms that makes it easy to enhance.
Each function that handles the image is implemented in an *Adapter*, this is where
all the specific actions take place.
The `Common` adapter is design to contain common abstract actions, while the
specific adapters (like `GD`) are designed to contain actions specific to the low
level layer.
You can add your own methods by adding it in the corresponding adapter.
```php
<?php
// In the adapter
private function myFilter()
{
$this->negate();
$this->sepia();
}
```
Which could be used on the Image
```php
<?php
$image->myFilter();
```
You can also write your own adapter which could extend one of this repository and use it by calling `setAdapter()`:
```php
<?php
$image->setAdapter(new MyCustomAdapter);
```
# License
`Gregwar\Image` is under MIT License, please read the LICENSE file for further details.
Do not hesitate to fork this repository and customize it !

View file

@ -0,0 +1,38 @@
<?php
namespace Gregwar\Image\Source;
/**
* Creates a new image from scratch.
*/
class Create extends Source
{
protected $width;
protected $height;
public function __construct($width, $height)
{
$this->width = $width;
$this->height = $height;
}
public function getWidth()
{
return $this->width;
}
public function getHeight()
{
return $this->height;
}
public function getInfos()
{
return array($this->width, $this->height);
}
public function correct()
{
return $this->width > 0 && $this->height > 0;
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Gregwar\Image\Source;
/**
* Having image in some string.
*/
class Data extends Source
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function getData()
{
return $this->data;
}
public function getInfos()
{
return sha1($this->data);
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Gregwar\Image\Source;
use Gregwar\Image\Image;
/**
* Open an image from a file.
*/
class File extends Source
{
protected $file;
public function __construct($file)
{
$this->file = $file;
}
public function getFile()
{
return $this->file;
}
public function correct()
{
return false !== @exif_imagetype($this->file);
}
public function guessType()
{
if (function_exists('exif_imagetype')) {
$type = @exif_imagetype($this->file);
if (false !== $type) {
if ($type == IMAGETYPE_JPEG) {
return 'jpeg';
}
if ($type == IMAGETYPE_GIF) {
return 'gif';
}
if ($type == IMAGETYPE_PNG) {
return 'png';
}
}
}
$parts = explode('.', $this->file);
$ext = strtolower($parts[count($parts) - 1]);
if (isset(Image::$types[$ext])) {
return Image::$types[$ext];
}
return 'jpeg';
}
public function getInfos()
{
return $this->file;
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Gregwar\Image\Source;
/**
* Have the image directly in a specific resource.
*/
class Resource extends Source
{
protected $resource;
public function __construct($resource)
{
$this->resource = $resource;
}
public function getResource()
{
return $this->resource;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Gregwar\Image\Source;
/**
* An Image source.
*/
class Source
{
/**
* Guess the type of the image.
*/
public function guessType()
{
return 'jpeg';
}
/**
* Is this image correct ?
*/
public function correct()
{
return true;
}
/**
* Returns information about images, these informations should
* change only if the original image changed.
*/
public function getInfos()
{
return;
}
}

View file

@ -0,0 +1,21 @@
<?php
$vendors = __DIR__.'/vendor/autoload.php';
if (file_exists($vendors)) {
return require $vendors;
}
/*
* Registers an autoload for all the classes in Gregwar\Image
*/
spl_autoload_register(function ($className) {
$namespace = 'Gregwar\\Image';
if (strpos($className, $namespace) === 0) {
$className = str_replace($namespace, '', $className);
$fileName = __DIR__.'/'.str_replace('\\', '/', $className).'.php';
if (file_exists($fileName)) {
require $fileName;
}
}
});

View file

@ -0,0 +1,32 @@
{
"name": "gregwar/image",
"description": "Image handling",
"keywords": ["image", "gd"],
"homepage": "https://github.com/Gregwar/Image",
"license": "MIT",
"authors": [
{
"name": "Grégoire Passault",
"email": "g.passault@gmail.com",
"homepage": "http://www.gregwar.com/"
}
],
"require": {
"php": "^5.3 || ^7.0",
"ext-gd": "*",
"gregwar/cache": "^1.0.6"
},
"require-dev": {
"sllh/php-cs-fixer-styleci-bridge": "~1.0",
"symfony/phpunit-bridge": "^2.7.4 || ^3.0"
},
"suggest": {
"behat/transliterator": "Transliterator provides ability to set non-latin1 pretty names"
},
"target-dir": "Gregwar/Image",
"autoload": {
"psr-0": {
"Gregwar\\Image": ""
}
}
}

View file

@ -0,0 +1,9 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
Image::open('in.gif')
->resize(500, 500)
->save('out.png', 'png');

View file

@ -0,0 +1,10 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
// Note: create a "cache" directory before try this
echo Image::open('img/test.png')
->sepia();
echo "\n";

View file

@ -0,0 +1,18 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
/*
* Use the cache with a from-scratch image
*/
echo
Image::create(300, 350)
->fill('rgb(255, 150, 150)')
->circle(150, 150, 200, 'red', true)
->write('./fonts/CaviarDreams.ttf', 'Hello world!', 150, 150, 20, 0, 'white', 'center')
->jpeg();
echo "\n";

View file

@ -0,0 +1,14 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
// Note: create a "cache" directory before try this
echo Image::open('img/test.png')
->sepia()
->setPrettyName('Some FanCY TestING!')
->jpeg();
echo "\n";

View file

@ -0,0 +1,9 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
Image::open('img/mona.jpg')
->cropResize(500, 150)
->save('out.jpg');

View file

@ -0,0 +1,14 @@
<?php
$data = 'iVBORw0KGgoAAAANSUhEUgAAABwAAAASCAMAAAB/2U7WAAAABl'
.'BMVEUAAAD///+l2Z/dAAAASUlEQVR4XqWQUQoAIAxC2/0vXZDr'
.'EX4IJTRkb7lobNUStXsB0jIXIAMSsQnWlsV+wULF4Avk9fLq2r'
.'8a5HSE35Q3eO2XP1A1wQkZSgETvDtKdQAAAABJRU5ErkJggg==';
$data = base64_decode($data);
require_once '../Image.php';
use Gregwar\Image\Image;
Image::fromData($data)
->save('out.jpg');

View file

@ -0,0 +1,11 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
echo Image::open('img/something-wrong.png')
->resize(100, 0)
->negate()
->jpeg()
;

Binary file not shown.

View file

@ -0,0 +1,7 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\GarbageCollect;
GarbageCollect::dropOldFiles(__DIR__.'/cache', 5, true);

View file

@ -0,0 +1,12 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
$image = Image::open('img/test.png')
->resize(100, 100)
->negate()
->get('jpeg');
echo $image;

View file

@ -0,0 +1,10 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
Image::open('img/test.png')
->resize(100, 100)
->negate()
->guess(55);

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,7 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
?>
<img src="<?php echo Image::open('img/test.png')->resize('50%')->inline() ?>" />

View file

@ -0,0 +1,9 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
Image::open('img/test.png')
->merge(Image::open('img/test2.jpg')->cropResize(100, 100))
->save('out.jpg', 'jpg');

View file

@ -0,0 +1,10 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
Image::open('img/test.png')
->resize('26%')
->negate()
->save('out.jpg', 'jpg');

View file

@ -0,0 +1,16 @@
<?php
$data = 'iVBORw0KGgoAAAANSUhEUgAAABwAAAASCAMAAAB/2U7WAAAABl'
.'BMVEUAAAD///+l2Z/dAAAASUlEQVR4XqWQUQoAIAxC2/0vXZDr'
.'EX4IJTRkb7lobNUStXsB0jIXIAMSsQnWlsV+wULF4Avk9fLq2r'
.'8a5HSE35Q3eO2XP1A1wQkZSgETvDtKdQAAAABJRU5ErkJggg==';
$data = base64_decode($data);
require_once '../autoload.php';
use Gregwar\Image\Image;
$gd = imagecreatefromstring($data);
Image::fromResource($gd)
->save('out.jpg');

View file

@ -0,0 +1,16 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
// Opening mona.jpg
$img = Image::open('img/mona.jpg');
// Opening vinci.png
$watermark = Image::open('img/vinci.png');
// Mergine vinci text into mona in the top-right corner
$img->merge($watermark, $img->width()-$watermark->width(),
$img->height()-$watermark->height())
->save('out.jpg', 'jpg');

View file

@ -0,0 +1,11 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
Image::create(300, 300)
->fill('rgb(255, 150, 150)')
->circle(150, 150, 200, 'red', true)
->write('./fonts/CaviarDreams.ttf', 'Hello world!', 150, 150, 20, 0, 'white', 'center')
->save('out.jpg', 'jpeg', 95);

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View file

@ -0,0 +1,38 @@
<?php
require_once '../autoload.php';
use Gregwar\Image\Image;
// resize() will never enlarge the image
Image::open('mona.jpg')
->resize(250, 250, 'red')
->save('resize.jpg');
// scaleResize() will also preserve the scale, but won't
// enlage the image
Image::open('mona.jpg')
->scaleResize(250, 250, 'red')
->save('scaleResize.jpg');
// forceResize() will resize matching the *exact* given size
Image::open('mona.jpg')
->forceResize(250, 250)
->save('forceResize.jpg');
// cropResize() preserves scale just like resize() but will
// trim the whitespace (if any) in the resulting image
Image::open('mona.jpg')
->cropResize(250, 250)
->save('cropResize.jpg');
// zoomCrop() resizes the image so that a part of it appear in
// the given area
Image::open('mona.jpg')
->zoomCrop(200, 200)
->save('zoomCrop.jpg');
// You can specify the position using the xPos and yPos arguments
Image::open('mona.jpg')
->zoomCrop(200, 200, 'transparent', 'center', 'top')
->save('zoomCropTop.jpg');

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<testsuites>
<testsuite name="Image testing">
<file>./tests/ImageTests.php</file>
</testsuite>
</testsuites>
</phpunit>

View file

@ -0,0 +1 @@
output

View file

@ -0,0 +1,485 @@
<?php
use Gregwar\Image\Image;
use Gregwar\Image\ImageColor;
/**
* Unit testing for Image.
*/
class ImageTests extends \PHPUnit\Framework\TestCase
{
/**
* Testing the basic width & height.
*/
public function testBasics()
{
$image = $this->open('monalisa.jpg');
$this->assertSame($image->width(), 771);
$this->assertSame($image->height(), 961);
}
/**
* Testing the resize.
*/
public function testResize()
{
$image = $this->open('monalisa.jpg');
$out = $this->output('monalisa_small.jpg');
$image
->resize(300, 200)
->save($out)
;
$this->assertTrue(file_exists($out));
$i = imagecreatefromjpeg($out);
$this->assertSame(300, imagesx($i));
$this->assertSame(200, imagesy($i));
}
/**
* Testing the resize %.
*/
public function testResizePercent()
{
$image = $this->open('monalisa.jpg');
$out = $this->output('monalisa_small.jpg');
$image
->resize('50%')
->save($out)
;
$this->assertTrue(file_exists($out));
$i = imagecreatefromjpeg($out);
$this->assertSame(386, imagesx($i));
$this->assertSame(481, imagesy($i));
}
/**
* Testing to create an image, jpeg, gif and png.
*/
public function testCreateImage()
{
$black = $this->output('black.jpg');
Image::create(150, 200)
->fill('black')
->save($black, 100);
$i = imagecreatefromjpeg($black);
$this->assertTrue(file_exists($black));
$this->assertSame(150, imagesx($i));
$this->assertSame(200, imagesy($i));
$j = imagecolorat($i, 40, 40);
$this->assertSame(0, $j);
$black = $this->output('black.png');
Image::create(150, 200)
->fill('black')
->save($black, 'png');
$i = imagecreatefrompng($black);
$this->assertTrue(file_exists($black));
$this->assertSame(150, imagesx($i));
$this->assertSame(200, imagesy($i));
$black = $this->output('black.gif');
Image::create(150, 200)
->fill('black')
->save($black, 'gif');
$i = imagecreatefromgif($black);
$this->assertTrue(file_exists($black));
$this->assertSame(150, imagesx($i));
$this->assertSame(200, imagesy($i));
}
/**
* Testing type guess.
*/
public function testGuess()
{
$image = $this->open('monalisa.jpg');
$this->assertSame('jpeg', $image->guessType());
$image = $this->open('monalisa.png');
$this->assertSame('png', $image->guessType());
$image = $this->open('monalisa.gif');
$this->assertSame('gif', $image->guessType());
}
public function testDefaultCacheSystem()
{
$image = $this->open('monalisa.jpg');
$this->assertInstanceOf('Gregwar\Cache\Cache', $image->getCacheSystem());
}
public function testCustomCacheSystem()
{
$image = $this->open('monalisa.jpg');
$cache = $this->createMock('Gregwar\Cache\CacheInterface');
$image->setCacheSystem($cache);
$this->assertTrue($image->getCacheSystem() instanceof Gregwar\Cache\CacheInterface);
}
/**
* Testing that caching an image without operations will result
* in the original image when force cache is disabled.
*/
public function testNoCache()
{
$monalisa = __DIR__.'/files/monalisa.jpg';
$image = $this->open('monalisa.jpg')->setForceCache(false);
$this->assertSame($monalisa, $image->guess());
$image = $this->open('monalisa.jpg');
$this->assertNotSame($monalisa, $image->guess());
$image = $this->open('monalisa.jpg')->setForceCache(true);
$this->assertNotSame($monalisa, $image->guess());
}
public function testActualCache()
{
$output = $this->open('monalisa.jpg')
->setCacheDir('/magic/path/to/cache/')
->resize(100, 50)->negate()
->guess();
$this->assertContains('/magic/path/to/cache', $output);
$file = str_replace('/magic/path/to', __DIR__.'/output/', $output);
$this->assertTrue(file_exists($file));
}
public function testCacheData()
{
$output = $this->open('monalisa.jpg')
->resize(300, 200)
->cacheData();
$jpg = imagecreatefromstring($output);
$this->assertSame(300, imagesx($jpg));
$this->assertSame(200, imagesy($jpg));
}
/**
* Testing using cache.
*/
public function testCache()
{
$output = $this->open('monalisa.jpg')
->resize(100, 50)->negate()
->guess();
$this->assertTrue(file_exists($output));
$i = imagecreatefromjpeg($output);
$this->assertSame(100, imagesx($i));
$this->assertSame(50, imagesy($i));
$output2 = $this->open('monalisa.jpg')
->resize(100, 50)->negate()
->guess();
$this->assertSame($output, $output2);
$output3 = $this->open('monalisa.jpg')
->resize(100, 50)->negate()
->png();
$this->assertTrue(file_exists($output));
$i = imagecreatefrompng($output3);
$this->assertSame(100, imagesx($i));
$this->assertSame(50, imagesy($i));
$output4 = $this->open('monalisa.jpg')
->resize(100, 50)->negate()
->gif();
$this->assertTrue(file_exists($output));
$i = imagecreatefromgif($output4);
$this->assertSame(100, imagesx($i));
$this->assertSame(50, imagesy($i));
}
/**
* Testing Gaussian blur filter.
*/
public function testGaussianBlur()
{
$image = $this->open('monalisa.jpg')
->gaussianBlur();
$secondImage = $this->open('monalisa.jpg')
->gaussianBlur(5);
$this->assertTrue(file_exists($image));
$this->assertTrue(file_exists($secondImage));
}
/**
* Testing creating image from data.
*/
public function testData()
{
$data = file_get_contents(__DIR__.'/files/monalisa.jpg');
$output = $this->output('mona.jpg');
$image = Image::fromData($data);
$image->save($output);
$this->assertTrue(file_exists($output));
$i = imagecreatefromjpeg($output);
$this->assertSame(771, imagesx($i));
$this->assertSame(961, imagesy($i));
}
/**
* Opening an image.
*/
protected function open($file)
{
$image = Image::open(__DIR__.'/files/'.$file);
$image->setCacheDir(__DIR__.'/output/cache/');
$image->setActualCacheDir(__DIR__.'/output/cache/');
return $image;
}
/**
* Testing transparent image.
*/
public function testTransparent()
{
$gif = $this->output('transparent.gif');
$i = Image::create(200, 100)
->fill('transparent')
->save($gif, 'gif');
$this->assertTrue(file_exists($gif));
$img = imagecreatefromgif($gif);
$this->assertSame(200, imagesx($img));
$this->assertSame(100, imagesy($img));
$index = imagecolorat($img, 2, 2);
$color = imagecolorsforindex($img, $index);
$this->assertSame(127, $color['alpha']);
}
public function testNonExistingFile()
{
$jpg = $this->output('a.jpg');
$img = $this->open('non_existing_file.jpg')
->negate();
$error = $img->save($jpg);
$this->assertTrue(file_exists($error));
}
/**
* * @expectedException \UnexpectedValueException
*/
public function testNonExistingFileNoFallback()
{
Image::open('non_existing_file.jpg')
->useFallback(false)
->save($this->output('a.jpg'));
}
/**
* Test that image::save returns the file name.
*/
public function testSaveReturn()
{
$red = $this->output('red.jpg');
$result = Image::create(10, 10)
->fill('red')
->save($red);
$this->assertSame($red, $result);
}
/**
* Testing merge.
*/
public function testMerge()
{
$out = $this->output('merge.jpg');
Image::create(100, 100)
->fill('red')
->merge(Image::create(50, 50)
->fill('black')
)
->save($out);
// Merge file exists
$this->assertTrue(file_exists($out));
// Test that the upper left zone contain a black pixel, and the lower
// down contains a red one
$img = imagecreatefromjpeg($out);
$this->assertColorEquals('black', imagecolorat($img, 5, 12));
$this->assertColorEquals('red', imagecolorat($img, 55, 62));
}
/**
* Test that dependencies are well generated.
*/
public function testDependencies()
{
$this->assertSame(array(), Image::create(100, 100)
->getDependencies());
$this->assertSame(array(), Image::create(100, 100)
->merge(Image::create(100, 50))
->getDependencies());
$this->assertSame(array('toto.jpg'), Image::open('toto.jpg')
->merge(Image::create(100, 50))
->getDependencies());
$this->assertSame(array('toto.jpg', 'titi.jpg'), Image::open('toto.jpg')
->merge(Image::open('titi.jpg'))
->getDependencies());
$this->assertSame(array('toto.jpg', 'titi.jpg', 'tata.jpg'), Image::open('toto.jpg')
->merge(Image::open('titi.jpg')
->merge(Image::open('tata.jpg')))
->getDependencies());
}
/**
* Test that pretty name (for referencing) is well respected.
*/
public function testPrettyName()
{
$output = $this->open('monalisa.jpg')
->resize(100, 50)->negate()
->setPrettyName('davinci', false)
->guess();
$this->assertContains('davinci', $output);
$output2 = $this->open('monalisa.jpg')
->resize(100, 55)->negate()
->setPrettyName('davinci', false)
->guess();
$this->assertSame($output, $output2);
$prefix1 = $this->open('monalisa.jpg')
->resize(100, 50)->negate()
->setPrettyName('davinci')
->guess();
$prefix2 = $this->open('monalisa.jpg')
->resize(100, 55)->negate()
->setPrettyName('davinci')
->guess();
$this->assertContains('davinci', $prefix1);
$this->assertContains('davinci', $prefix2);
$this->assertNotSame($prefix1, $prefix2);
$transliterator = '\Behat\Transliterator\Transliterator';
if (class_exists($transliterator)) {
$nonLatinName1 = 'ダヴィンチ';
$nonLatinOutput1 = $this->open('monalisa.jpg')
->resize(100, 50)->negate()
->setPrettyName($nonLatinName1, false)
->guess();
$this->assertContains(
$transliterator::urlize($transliterator::transliterate($nonLatinName1)),
$nonLatinOutput1
);
$nonLatinName2 = 'давинчи';
$nonLatinOutput2 = $this->open('monalisa.jpg')
->resize(100, 55)->negate()
->setPrettyName($nonLatinName2)
->guess();
$this->assertContains(
$transliterator::urlize($transliterator::transliterate($nonLatinName2)),
$nonLatinOutput2
);
}
}
/**
* Testing inlining.
*/
public function testInline()
{
$output = $this->open('monalisa.jpg')
->resize(20, 20)
->inline();
$this->assertSame(0, strpos($output, 'data:image/jpeg;base64,'));
$data = base64_decode(substr($output, 23));
$image = imagecreatefromstring($data);
$this->assertSame(20, imagesx($image));
$this->assertSame(20, imagesy($image));
}
/**
* Testing that width() can be called after cache
*/
public function testWidthPostCache()
{
$this->open('monalisa.jpg')
->resize(100, 50)->negate()
->png();
$dummy2 = $this->open('monalisa.jpg')
->resize(100, 50)->negate();
$png = $dummy2->png();
$i = imagecreatefrompng($png);
$this->assertEquals(imagesx($i), $dummy2->width());
}
/**
* Asaserting that two colors are equals
* (JPG compression is not preserving colors for instance, so we
* need a non-strict way to compare it).
*/
protected function assertColorEquals($c1, $c2, $delta = 8)
{
$c1 = ImageColor::parse($c1);
$c2 = ImageColor::parse($c2);
list($r1, $g1, $b1) = $this->toRGB($c1);
list($r2, $g2, $b2) = $this->toRGB($c2);
$this->assertLessThan($delta, abs($r1 - $r2));
$this->assertLessThan($delta, abs($g1 - $g2));
$this->assertLessThan($delta, abs($b1 - $b2));
}
protected function toRGB($color)
{
$b = $color & 0xff;
$g = ($color >> 8) & 0xff;
$r = ($color >> 16) & 0xff;
return array($r, $g, $b);
}
/**
* Outputing an image to a file.
*/
protected function output($file)
{
return __DIR__.'/output/'.$file;
}
/**
* Reinitialize the output dir.
*/
public function setUp()
{
$dir = $this->output('');
`rm -rf $dir`;
mkdir($dir);
mkdir($this->output('cache'));
}
}

View file

@ -0,0 +1,9 @@
<?php
// backward compatibility
if (!class_exists('\PHPUnit\Framework\TestCase') &&
class_exists('\PHPUnit_Framework_TestCase')) {
class_alias('\PHPUnit_Framework_TestCase', '\PHPUnit\Framework\TestCase');
}
require_once(__DIR__.'/../autoload.php');

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB