1
0
mirror of https://github.com/mosbth/cimage.git synced 2025-08-29 02:19:47 +02:00

Compare commits

..

34 Commits

Author SHA1 Message Date
Mikael Roos
1bd6fc8a2c improving dummy image 2015-10-18 17:15:14 +02:00
Mikael Roos
39b6078d84 travis working again, unintentionally removed the cache dirs 2015-10-18 16:25:50 +02:00
Mikael Roos
e7e73fc38c testin travis 2015-10-18 16:22:42 +02:00
Mikael Roos
fa77908616 testin travis 2015-10-18 16:13:45 +02:00
Mikael Roos
0f05cd06e5 testin travis 2015-10-18 16:11:59 +02:00
Mikael Roos
3d773b502d testint travis 2015-10-18 16:04:48 +02:00
Mikael Roos
575c712bd0 testint travis 2015-10-18 16:02:27 +02:00
Mikael Roos
cdd159e142 testing travis 2015-10-18 15:52:06 +02:00
Mikael Roos
f21cd923f3 testing travis 2015-10-18 15:50:22 +02:00
Mikael Roos
18fb271961 add writable cache dir 2015-10-18 15:48:11 +02:00
Mikael Roos
1fdc701f3d prepare to test v0.7.5 2015-10-18 15:43:43 +02:00
Mikael Roos
dc6e9a8708 test tag 2015-10-18 14:52:17 +02:00
Mikael Roos
d9947251ab prepare to merge 2015-10-18 14:44:27 +02:00
Mikael Roos
0fb00df972 adding feature for creating dummy images, #101 2015-09-17 11:24:30 +02:00
Mikael Roos
f00b2e0cd3 adding feature for creating dummy images, #101 2015-09-17 11:22:50 +02:00
Mikael Roos
fd2ddadc44 Add png compression to generated cache filename, fix #103. 2015-09-16 09:51:04 +02:00
Mikael Roos
ea4794f4a2 remove commented code 2015-09-16 09:19:46 +02:00
Mikael Roos
7af0b6ef8a remove commented code 2015-09-16 09:18:51 +02:00
Mikael Roos
850fb76c8b cleanup unused variables 2015-09-16 09:09:30 +02:00
Mikael Roos
49f5f66ad3 calling setSource(null) should unset imageSrc and pathToImage 2015-09-16 08:26:28 +02:00
Mikael Roos
a787593533 Removing unneded internal variable this->imageFolder 2015-09-16 08:21:32 +02:00
Mikael Roos
8d6a8a452e Prepare to tag v0.7.4 2015-09-15 17:26:04 +02:00
Mikael Roos
1907feffc8 Rewriting some comments 2015-09-15 17:24:10 +02:00
Mikael Roos
afff3589dc Generate filename with filters, does not work on Windows, fix #100. 2015-09-15 17:23:22 +02:00
Mikael Roos
76bc09f2ee Add CAsciiArt.php to composer for autoloading, fix #102. 2015-09-15 17:19:29 +02:00
Mikael Roos
c4d3f7cdf6 prepare to tag 2015-09-01 17:02:11 +02:00
Mikael Roos
a50aab9d8e Prepare to test asciiart 2015-09-01 16:53:19 +02:00
Mikael Roos
de942268c9 Updating version information 2015-09-01 16:53:02 +02:00
Mikael Roos
d8090e7531 Merge branch 'master' into ascii 2015-09-01 16:49:30 +02:00
Mikael Roos
c94027a752 Prepare to generate bundles with asciiart 2015-09-01 16:46:44 +02:00
Mikael Roos
2959f11f33 Adding support for AsciiArt of an image. 2015-09-01 16:45:10 +02:00
Mikael Roos
cf8bb8973d prepared to tag v0.7.2 and regenerated bundles 2015-08-17 20:35:25 +02:00
Mikael Roos
d8db2b9105 Prepare to test #98 2015-08-17 20:28:08 +02:00
Mikael Roos
b65b420313 Updating with release information 2015-07-25 23:28:24 +02:00
18 changed files with 2510 additions and 470 deletions

212
CAsciiArt.php Normal file
View File

@@ -0,0 +1,212 @@
<?php
/**
* Create an ASCII version of an image.
*
*/
class CAsciiArt
{
/**
* Character set to use.
*/
private $characterSet = array(
'one' => "#0XT|:,.' ",
'two' => "@%#*+=-:. ",
'three' => "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
);
/**
* Current character set.
*/
private $characters = null;
/**
* Length of current character set.
*/
private $charCount = null;
/**
* Scale of the area to swap to a character.
*/
private $scale = null;
/**
* Strategy to calculate luminance.
*/
private $luminanceStrategy = null;
/**
* Constructor which sets default options.
*/
public function __construct()
{
$this->setOptions();
}
/**
* Add a custom character set.
*
* @param string $key for the character set.
* @param string $value for the character set.
*
* @return $this
*/
public function addCharacterSet($key, $value)
{
$this->characterSet[$key] = $value;
return $this;
}
/**
* Set options for processing, defaults are available.
*
* @param array $options to use as default settings.
*
* @return $this
*/
public function setOptions($options = array())
{
$default = array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
);
$default = array_merge($default, $options);
if (!is_null($default['customCharacterSet'])) {
$this->addCharacterSet('custom', $default['customCharacterSet']);
$default['characterSet'] = 'custom';
}
$this->scale = $default['scale'];
$this->characters = $this->characterSet[$default['characterSet']];
$this->charCount = strlen($this->characters);
$this->luminanceStrategy = $default['luminanceStrategy'];
return $this;
}
/**
* Create an Ascii image from an image file.
*
* @param string $filename of the image to use.
*
* @return string $ascii with the ASCII image.
*/
public function createFromFile($filename)
{
$img = imagecreatefromstring(file_get_contents($filename));
list($width, $height) = getimagesize($filename);
$ascii = null;
$incY = $this->scale;
$incX = $this->scale / 2;
for ($y = 0; $y < $height - 1; $y += $incY) {
for ($x = 0; $x < $width - 1; $x += $incX) {
$toX = min($x + $this->scale / 2, $width - 1);
$toY = min($y + $this->scale, $height - 1);
$luminance = $this->luminanceAreaAverage($img, $x, $y, $toX, $toY);
$ascii .= $this->luminance2character($luminance);
}
$ascii .= PHP_EOL;
}
return $ascii;
}
/**
* Get the luminance from a region of an image using average color value.
*
* @param string $img the image.
* @param integer $x1 the area to get pixels from.
* @param integer $y1 the area to get pixels from.
* @param integer $x2 the area to get pixels from.
* @param integer $y2 the area to get pixels from.
*
* @return integer $luminance with a value between 0 and 100.
*/
public function luminanceAreaAverage($img, $x1, $y1, $x2, $y2)
{
$numPixels = ($x2 - $x1 + 1) * ($y2 - $y1 + 1);
$luminance = 0;
for ($x = $x1; $x <= $x2; $x++) {
for ($y = $y1; $y <= $y2; $y++) {
$rgb = imagecolorat($img, $x, $y);
$red = (($rgb >> 16) & 0xFF);
$green = (($rgb >> 8) & 0xFF);
$blue = ($rgb & 0xFF);
$luminance += $this->getLuminance($red, $green, $blue);
}
}
return $luminance / $numPixels;
}
/**
* Calculate luminance value with different strategies.
*
* @param integer $red The color red.
* @param integer $green The color green.
* @param integer $blue The color blue.
*
* @return float $luminance with a value between 0 and 1.
*/
public function getLuminance($red, $green, $blue)
{
switch($this->luminanceStrategy) {
case 1:
$luminance = ($red * 0.2126 + $green * 0.7152 + $blue * 0.0722) / 255;
break;
case 2:
$luminance = ($red * 0.299 + $green * 0.587 + $blue * 0.114) / 255;
break;
case 3:
$luminance = sqrt(0.299 * pow($red, 2) + 0.587 * pow($green, 2) + 0.114 * pow($blue, 2)) / 255;
break;
case 0:
default:
$luminance = ($red + $green + $blue) / (255 * 3);
}
return $luminance;
}
/**
* Translate the luminance value to a character.
*
* @param string $position a value between 0-100 representing the
* luminance.
*
* @return string with the ascii character.
*/
public function luminance2character($luminance)
{
$position = (int) round($luminance * ($this->charCount - 1));
$char = $this->characters[$position];
return $char;
}
}

View File

@@ -21,6 +21,33 @@ class CHttpGet
/**
* Build an encoded url.
*
* @param string $baseUrl This is the original url which will be merged.
* @param string $merge Thse parts should be merged into the baseUrl,
* the format is as parse_url.
*
* @return string $url as the modified url.
*/
public function buildUrl($baseUrl, $merge)
{
$parts = parse_url($baseUrl);
$parts = array_merge($parts, $merge);
$url = $parts['scheme'];
$url .= "://";
$url .= $parts['host'];
$url .= isset($parts['port'])
? ":" . $parts['port']
: "" ;
$url .= $parts['path'];
return $url;
}
/**
* Set the url for the request.
*
@@ -30,6 +57,18 @@ class CHttpGet
*/
public function setUrl($url)
{
$parts = parse_url($url);
$path = "";
if (isset($parts['path'])) {
$pathParts = explode('/', $parts['path']);
unset($pathParts[0]);
foreach ($pathParts as $value) {
$path .= "/" . rawurlencode($value);
}
}
$url = $this->buildUrl($url, array("path" => $path));
$this->request['url'] = $url;
return $this;
}
@@ -93,6 +132,8 @@ class CHttpGet
*
* @param boolean $debug set to true to dump headers.
*
* @throws Exception when curl fails to retrieve url.
*
* @return boolean
*/
public function doGet($debug = false)
@@ -115,7 +156,7 @@ class CHttpGet
$response = curl_exec($ch);
if (!$response) {
return false;
throw new Exception("Failed retrieving url, details follows: " . curl_error($ch));
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

View File

@@ -113,13 +113,6 @@ class CImage
/**
* The root folder of images (only used in constructor to create $pathToImage?).
*/
private $imageFolder;
/**
* Image filename, may include subdirectory, relative from $imageFolder
*/
@@ -193,6 +186,7 @@ class CImage
* Path to command for filter optimize, for example optipng or null.
*/
private $pngFilter;
private $pngFilterCmd;
@@ -200,13 +194,16 @@ class CImage
* Path to command for deflate optimize, for example pngout or null.
*/
private $pngDeflate;
private $pngDeflateCmd;
/**
* Path to command to optimize jpeg images, for example jpegtran or null.
*/
private $jpegOptimize;
private $jpegOptimize;
private $jpegOptimizeCmd;
/**
@@ -282,6 +279,56 @@ class CImage
private $fillToFit;
/**
* To store value for option scale.
*/
private $scale;
/**
* To store value for option.
*/
private $rotateBefore;
/**
* To store value for option.
*/
private $rotateAfter;
/**
* To store value for option.
*/
private $autoRotate;
/**
* To store value for option.
*/
private $sharpen;
/**
* To store value for option.
*/
private $emboss;
/**
* To store value for option.
*/
private $blur;
/**
* Used with option area to set which parts of the image to use.
*/
@@ -334,6 +381,13 @@ class CImage
/*
* output to ascii can take som options as an array.
*/
private $asciiOptions = array();
/**
* Properties, the class is mutable and the method setOptions()
* decides (partly) what properties are created.
@@ -348,9 +402,7 @@ class CImage
public $crop_x;
public $crop_y;
public $filters;
private $type; // Calculated from source image
private $attr; // Calculated from source image
private $useOriginal; // Use original image if possible
@@ -407,9 +459,7 @@ class CImage
/**
* Use cache or not.
*
* @todo clean up how $this->noCache is used in other methods.
*
* @param string $use true or false to use cache.
* @param boolean $use true or false to use cache.
*
* @return $this
*/
@@ -421,6 +471,27 @@ class CImage
/**
* Create and save a dummy image. Use dimensions as stated in
* $this->newWidth, or $width or default to 100 (same for height.
*
* @param integer $width use specified width for image dimension.
* @param integer $height use specified width for image dimension.
*
* @return $this
*/
public function createDummyImage($width = null, $height = null)
{
$this->newWidth = $this->newWidth ?: $width ?: 100;
$this->newHeight = $this->newHeight ?: $height ?: 100;
$this->image = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
return $this;
}
/**
* Allow or disallow remote image download.
*
@@ -571,16 +642,18 @@ class CImage
/**
* Set src file.
* Set source file to use as image source.
*
* @param string $src of image.
* @param string $dir as base directory where images are.
* @param string $dir as optional base directory where images are.
*
* @return $this
*/
public function setSource($src, $dir = null)
{
if (!isset($src)) {
$this->imageSrc = null;
$this->pathToImage = null;
return $this;
}
@@ -594,9 +667,9 @@ class CImage
$src = basename($src);
}
$this->imageSrc = ltrim($src, '/');
$this->imageFolder = rtrim($dir, '/');
$this->pathToImage = $this->imageFolder . '/' . $this->imageSrc;
$this->imageSrc = ltrim($src, '/');
$imageFolder = rtrim($dir, '/');
$this->pathToImage = $imageFolder . '/' . $this->imageSrc;
return $this;
}
@@ -607,23 +680,23 @@ class CImage
* Set target file.
*
* @param string $src of target image.
* @param string $dir as base directory where images are stored.
* @param string $dir as optional base directory where images are stored.
* Uses $this->saveFolder if null.
*
* @return $this
*/
public function setTarget($src = null, $dir = null)
{
if (!(isset($src) && isset($dir))) {
if (!isset($src)) {
$this->cacheFileName = null;
return $this;
}
$this->saveFolder = $dir;
$this->cacheFileName = $dir . '/' . $src;
if (isset($dir)) {
$this->saveFolder = rtrim($dir, '/');
}
/* Allow readonly cache
is_writable($this->saveFolder)
or $this->raiseError('Target directory is not writable.');
*/
$this->cacheFileName = $this->saveFolder . '/' . $src;
// Sanitize filename
$this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName);
@@ -634,6 +707,18 @@ class CImage
/**
* Get filename of target file.
*
* @return Boolean|String as filename of target or false if not set.
*/
public function getTarget()
{
return $this->cacheFileName;
}
/**
* Set options to use when processing image.
*
@@ -681,11 +766,6 @@ class CImage
// Output format
'outputFormat' => null,
'dpr' => 1,
// Options for saving
//'quality' => null,
//'compress' => null,
//'saveAs' => null,
);
// Convert crop settings from string to array
@@ -1179,13 +1259,15 @@ class CImage
/**
* Generate filename to save file in cache.
*
* @param string $base as basepath for storing file.
* @param string $base as optional basepath for storing file.
* @param boolean $useSubdir use or skip the subdir part when creating the
* filename.
*
* @return $this
*/
public function generateFilename($base)
public function generateFilename($base = null, $useSubdir = true)
{
$parts = pathinfo($this->pathToImage);
$filename = basename($this->pathToImage);
$cropToFit = $this->cropToFit ? '_cf' : null;
$fillToFit = $this->fillToFit ? '_ff' : null;
$crop_x = $this->crop_x ? "_x{$this->crop_x}" : null;
@@ -1214,7 +1296,7 @@ class CImage
if (is_array($filter)) {
$filters .= "_f{$filter['id']}";
for ($i=1; $i<=$filter['argc']; $i++) {
$filters .= ":".$filter["arg{$i}"];
$filters .= "-".$filter["arg{$i}"];
}
}
}
@@ -1227,16 +1309,6 @@ class CImage
$autoRotate = $this->autoRotate ? 'ar' : null;
$this->extension = isset($this->extension)
? $this->extension
: (isset($parts['extension'])
? $parts['extension']
: null);
$extension = empty($this->extension)
? null
: "." . $this->extension;
$optimize = $this->jpegOptimize ? 'o' : null;
$optimize .= $this->pngFilter ? 'f' : null;
$optimize .= $this->pngDeflate ? 'd' : null;
@@ -1251,14 +1323,20 @@ class CImage
$upscale = '_nu';
}
$subdir = str_replace('/', '-', dirname($this->imageSrc));
$subdir = ($subdir == '.') ? '_.' : $subdir;
$file = $subdir . '_' . $parts['filename'] . '_' . $width . '_'
$subdir = null;
if ($useSubdir === true) {
$subdir = str_replace('/', '-', dirname($this->imageSrc));
$subdir = ($subdir == '.') ? '_.' : $subdir;
$subdir .= '_';
}
$file = $subdir . $filename . '_' . $width . '_'
. $height . $offset . $crop . $cropToFit . $fillToFit
. $crop_x . $crop_y . $upscale
. $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize
. $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve
. $extension;
. $quality . $filters . $sharpen . $emboss . $blur . $palette
. $optimize . $compress
. $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor
. $convolve;
return $this->setTarget($file, $base);
}
@@ -2026,7 +2104,10 @@ class CImage
imagealphablending($img, false);
imagesavealpha($img, true);
$index = imagecolortransparent($this->image);
$index = $this->image
? imagecolortransparent($this->image)
: -1;
if ($index != -1) {
imagealphablending($img, true);
@@ -2099,17 +2180,23 @@ class CImage
/**
* Save image.
*
* @param string $src as target filename.
* @param string $base as base directory where to store images.
* @param string $src as target filename.
* @param string $base as base directory where to store images.
* @param boolean $overwrite or not, default to always overwrite file.
*
* @return $this or false if no folder is set.
*/
public function save($src = null, $base = null)
public function save($src = null, $base = null, $overwrite = true)
{
if (isset($src)) {
$this->setTarget($src, $base);
}
if ($overwrite === false && is_file($this->cacheFileName)) {
$this->Log("Not overwriting file since its already exists and \$overwrite if false.");
return;
}
is_writable($this->saveFolder)
or $this->raiseError('Target directory is not writable.');
@@ -2250,6 +2337,10 @@ class CImage
header('Content-type: application/json');
echo $this->json($file);
exit;
} elseif ($format == 'ascii') {
header('Content-type: text/plain');
echo $this->ascii($file);
exit;
}
$this->log("Outputting image: $file");
@@ -2341,6 +2432,38 @@ class CImage
/**
* Set options for creating ascii version of image.
*
* @param array $options empty to use default or set options to change.
*
* @return void.
*/
public function setAsciiOptions($options = array())
{
$this->asciiOptions = $options;
}
/**
* Create an ASCII version from the image details.
*
* @param string $file the file to output.
*
* @return string ASCII representation of the image.
*/
public function ascii($file = null)
{
$file = $file ? $file : $this->cacheFileName;
$asciiArt = new CAsciiArt();
$asciiArt->setOptions($this->asciiOptions);
return $asciiArt->createFromFile($file);
}
/**
* Log an event if verbose mode.
*

View File

@@ -147,7 +147,7 @@ class CRemoteImage
*/
public function setHeaderFields()
{
$this->http->setHeader("User-Agent", "CImage/0.7.0 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("User-Agent", "CImage/0.7.2 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("Accept", "image/jpeg,image/png,image/gif");
if ($this->useCache) {
@@ -226,6 +226,8 @@ class CRemoteImage
*
* @param string $url a remote url.
*
* @throws Exception when status code does not match 200 or 304.
*
* @return string as path to downloaded file or false if failed.
*/
public function download($url)
@@ -258,7 +260,7 @@ class CRemoteImage
return $this->updateCacheDetails();
}
return false;
throw new Exception("Unknown statuscode when downloading remote image: " . $this->status);
}

View File

@@ -37,7 +37,7 @@ Requirements
Installation
--------------------------------------
There are two ways of installing. You either install the whole project which uses the autoloader to include the various files, or you install the all-included bundle that -- for convenience -- contains all code in one script.
There are several ways of installing. You either install the whole project which uses the autoloader to include the various files, or you install the all-included bundle that -- for convenience -- contains all code in one script.
@@ -45,14 +45,14 @@ There are two ways of installing. You either install the whole project which use
The [sourcode is available on GitHub](https://github.com/mosbth/cimage). Clone, fork or [download as zip](https://github.com/mosbth/cimage/archive/master.zip).
**Latest stable version is v0.7.0 released 2015-02-10.**
**Latest stable version is v0.7.6 released 2015-10-18.**
I prefer cloning like this. Do switch to the latest stable version.
```bash
git clone git://github.com/mosbth/cimage.git
cd cimage
git checkout v0.7.0
git checkout v0.7.6
```
Make the cache-directory writable by the webserver.
@@ -75,13 +75,19 @@ There are some all-included bundles of `img.php` that can be downloaded and used
Dowload the version of your choice like this.
```bash
wget https://github.com/mosbth/cimage/tree/v0.7.0/webroot/imgp.php
wget https://github.com/mosbth/cimage/tree/v0.7.6/webroot/imgp.php
```
Open up the file in your editor and edit the array `$config`. Ensure that the paths to the image directory and the cache directory matches your environment, or create an own config-file for the script.
###Install from Packagist
You can install the package [`mos/cimage` from Packagist](https://packagist.org/packages/mos/cimage) using composer.
Use cases
--------------------------------------

View File

@@ -5,6 +5,41 @@ Revision history
[![Build Status](https://scrutinizer-ci.com/g/mosbth/cimage/badges/build.png?b=master)](https://scrutinizer-ci.com/g/mosbth/cimage/build-status/master)
v0.7.6 (2015-10-18)
-------------------------------------
* Adding testpage for dummy images `webroot/test/test_issue101-dummy.php`.
* Adding width and height when creating dummy image.
v0.7.5 (2015-10-18)
-------------------------------------
* Adding feature for creating dummy images `src=dummy`, #101.
* Add png compression to generated cache filename, fix #103.
* Removed file prefix from storing images in cache, breaking filenamestructure for cache images.
* Code cleaning in `CImage.php`.
v0.7.4 (2015-09-15)
-------------------------------------
* Add CAsciiArt.php to composer for autoloading, fix #102.
* Generate filename with filters, does not work on Windows, fix #100.
v0.7.3 (2015-09-01)
-------------------------------------
* Support output of ascii images, #67.
v0.7.2 (2015-08-17)
-------------------------------------
* Allow space in remote filenames, fix #98.
v0.7.1 (2015-07-25)
-------------------------------------

View File

@@ -37,6 +37,7 @@ $ECHO "\n webroot/img_header.php"
$ECHO "\n CHttpGet.php"
$ECHO "\n CRemoteImage.php"
$ECHO "\n CWhitelist.php"
$ECHO "\n CAsciiArt.php"
$ECHO "\n CImage.php"
$ECHO "\n webroot/img.php"
$ECHO "\n"
@@ -64,10 +65,13 @@ $ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
tail -n +2 CRemoteImage.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
tail -n +2 CImage.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
tail -n +2 CWhitelist.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
tail -n +2 CWhitelist.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
tail -n +2 CAsciiArt.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
tail -n +2 CImage.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null
tail -n +2 webroot/img.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null

1
cache/README.md vendored
View File

@@ -1 +0,0 @@
This directory must be writable by the webserver.

0
cache/dummy/README.md vendored Normal file
View File

View File

@@ -25,12 +25,8 @@
"CImage.php",
"CHttpGet.php",
"CRemoteImage.php",
"CWhitelist.php"
"CWhitelist.php",
"CAsciiArt.php"
]
},
"extra": {
"branch-alias": {
"dev-master": "0.7.x-dev"
}
}
}

49
test/CImageDummyTest.php Normal file
View File

@@ -0,0 +1,49 @@
<?php
/**
* A testclass
*
*/
class CImageDummyTest extends \PHPUnit_Framework_TestCase
{
/**
* Test
*
* @return void
*/
public function testCreate1()
{
$img = new CImage();
$img->setSaveFolder(CACHE_PATH . "/dummy");
$img->setSource('dummy', CACHE_PATH . "/dummy");
$img->createDummyImage();
$img->generateFilename(null, false);
$img->save(null, null, false);
$filename = $img->getTarget();
$this->assertEquals(basename($filename), "dummy_100_100", "Filename not as expected on dummy image.");
}
/**
* Test
*
* @return void
*/
public function testCreate2()
{
$img = new CImage();
$img->setSaveFolder(CACHE_PATH . "/dummy");
$img->setSource('dummy', CACHE_PATH . "/dummy");
$img->createDummyImage(200, 400);
$img->generateFilename(null, false);
$img->save(null, null, false);
$filename = $img->getTarget();
$this->assertEquals(basename($filename), "dummy_200_400", "Filename not as expected on dummy image.");
}
}

View File

@@ -4,3 +4,6 @@
*
*/
require __DIR__ . "/../autoload.php";
define('IMAGE_PATH', __DIR__ . '/../webroot/img/');
define('CACHE_PATH', __DIR__ . '/../cache/');

View File

@@ -8,12 +8,12 @@
*
*/
$version = "v0.7.1 (2015-07-25)";
$version = "v0.7.6 (2015-10-18)";
/**
* Default configuration options, can be overridden in own config-file.
* Display error message.
*
* @param string $msg to display.
*
@@ -373,10 +373,20 @@ $imagePath = getConfig('image_path', __DIR__ . '/img/');
$imagePathConstraint = getConfig('image_path_constraint', true);
$validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#');
// Dummy image feature
$dummyEnabled = getConfig('dummy_enabled', true);
$dummyFilename = getConfig('dummy_filename', 'dummy');
$dummyImage = false;
preg_match($validFilename, $srcImage)
or errorPage('Filename contains invalid characters.');
if ($allowRemote && $img->isRemoteSource($srcImage)) {
if ($dummyEnabled && $srcImage === $dummyFilename) {
// Prepare to create a dummy image and use it as the source image.
$dummyImage = true;
} elseif ($allowRemote && $img->isRemoteSource($srcImage)) {
// If source is a remote file, ignore local file checks.
@@ -754,11 +764,51 @@ verbose("filters = " . print_r($filters, 1));
/**
* json - output the image as a JSON object with details on the image.
* json - output the image as a JSON object with details on the image.
* ascii - output the image as ASCII art.
*/
$outputFormat = getDefined('json', 'json', null);
$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
verbose("outputformat = $outputFormat");
if ($outputFormat == 'ascii') {
$defaultOptions = getConfig(
'ascii-options',
array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
)
);
$options = get('ascii');
$options = explode(',', $options);
if (isset($options[0]) && !empty($options[0])) {
$defaultOptions['characterSet'] = $options[0];
}
if (isset($options[1]) && !empty($options[1])) {
$defaultOptions['scale'] = $options[1];
}
if (isset($options[2]) && !empty($options[2])) {
$defaultOptions['luminanceStrategy'] = $options[2];
}
if (count($options) > 3) {
// Last option is custom character string
unset($options[0]);
unset($options[1]);
unset($options[2]);
$characterString = implode($options);
$defaultOptions['customCharacterSet'] = $characterString;
}
$img->setAsciiOptions($defaultOptions);
}
verbose("json = $outputFormat");
@@ -893,9 +943,47 @@ if ($verboseFile) {
/**
* Load, process and output the image
*/
/**
* Set basic options for image processing.
*/
/**
* Prepare a dummy image and use it as source image.
*/
if ($dummyImage === true) {
$dummyDir = getConfig('dummy_dir', $cachePath. "/" . $dummyFilename);
is_writable($dummyDir)
or verbose("dummy dir not writable = $dummyDir");
$img->setSaveFolder($dummyDir)
->setSource($dummyFilename, $dummyDir)
->setOptions(
array(
'newWidth' => $newWidth,
'newHeight' => $newHeight,
'bgColor' => $bgColor,
)
)
->setJpegQuality($quality)
->setPngCompression($compress)
->createDummyImage()
->generateFilename(null, false)
->save(null, null, false);
$srcImage = $img->getTarget();
$imagePath = null;
verbose("src (updated) = $srcImage");
}
/**
* Load, process and output the image
*/
$img->log("Incoming arguments: " . print_r(verbose(), 1))
->setSaveFolder($cachePath)
->useCache($useCache)

View File

@@ -13,7 +13,6 @@ return array(
* Default values:
* mode: 'production'
*/
'mode' => 'development',
//'mode' => 'production', // 'development', 'strict'
@@ -109,6 +108,28 @@ return array(
/**
* The name representing a dummy image which is automatically created
* and stored at the defined path. The dummy image can then be used
* inplace of an original image as a placeholder.
* The dummy_dir must be writable and it defaults to a subdir of the
* cache directory.
* Write protect the dummy_dir to prevent creation of new dummy images,
* but continue to use the existing ones.
*
* Default value:
* dummy_enabled: true as default, disable dummy feature by setting
* to false.
* dummy_filename: 'dummy' use this as ?src=dummy to create a dummy image.
* dummy_dir: Defaults to subdirectory of 'cache_path',
* named the same as 'dummy_filename'
*/
//'dummy_enabled' => true,
//'dummy_filename' => 'dummy',
//'dummy_dir' => 'some writable directory',
/**
* Check that the imagefile is a file below 'image_path' using realpath().
* Security constraint to avoid reaching images outside image_path.
@@ -299,4 +320,25 @@ return array(
'golden' => 1.618,
);
},*/
/**
* default options for ascii image.
*
* Default values as specified below in the array.
* ascii-options:
* characterSet: Choose any character set available in CAsciiArt.
* scale: How many pixels should each character
* translate to.
* luminanceStrategy: Choose any strategy available in CAsciiArt.
* customCharacterSet: Define your own character set.
*/
/*'ascii-options' => array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
);
},*/
);

View File

@@ -58,6 +58,33 @@ class CHttpGet
/**
* Build an encoded url.
*
* @param string $baseUrl This is the original url which will be merged.
* @param string $merge Thse parts should be merged into the baseUrl,
* the format is as parse_url.
*
* @return string $url as the modified url.
*/
public function buildUrl($baseUrl, $merge)
{
$parts = parse_url($baseUrl);
$parts = array_merge($parts, $merge);
$url = $parts['scheme'];
$url .= "://";
$url .= $parts['host'];
$url .= isset($parts['port'])
? ":" . $parts['port']
: "" ;
$url .= $parts['path'];
return $url;
}
/**
* Set the url for the request.
*
@@ -67,6 +94,18 @@ class CHttpGet
*/
public function setUrl($url)
{
$parts = parse_url($url);
$path = "";
if (isset($parts['path'])) {
$pathParts = explode('/', $parts['path']);
unset($pathParts[0]);
foreach ($pathParts as $value) {
$path .= "/" . rawurlencode($value);
}
}
$url = $this->buildUrl($url, array("path" => $path));
$this->request['url'] = $url;
return $this;
}
@@ -130,6 +169,8 @@ class CHttpGet
*
* @param boolean $debug set to true to dump headers.
*
* @throws Exception when curl fails to retrieve url.
*
* @return boolean
*/
public function doGet($debug = false)
@@ -152,7 +193,7 @@ class CHttpGet
$response = curl_exec($ch);
if (!$response) {
return false;
throw new Exception("Failed retrieving url, details follows: " . curl_error($ch));
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
@@ -435,7 +476,7 @@ class CRemoteImage
*/
public function setHeaderFields()
{
$this->http->setHeader("User-Agent", "CImage/0.7.0 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("User-Agent", "CImage/0.7.2 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("Accept", "image/jpeg,image/png,image/gif");
if ($this->useCache) {
@@ -514,6 +555,8 @@ class CRemoteImage
*
* @param string $url a remote url.
*
* @throws Exception when status code does not match 200 or 304.
*
* @return string as path to downloaded file or false if failed.
*/
public function download($url)
@@ -546,7 +589,7 @@ class CRemoteImage
return $this->updateCacheDetails();
}
return false;
throw new Exception("Unknown statuscode when downloading remote image: " . $this->status);
}
@@ -597,6 +640,284 @@ class CRemoteImage
/**
* Act as whitelist (or blacklist).
*
*/
class CWhitelist
{
/**
* Array to contain the whitelist options.
*/
private $whitelist = array();
/**
* Set the whitelist from an array of strings, each item in the
* whitelist should be a regexp without the surrounding / or #.
*
* @param array $whitelist with all valid options,
* default is to clear the whitelist.
*
* @return $this
*/
public function set($whitelist = array())
{
if (!is_array($whitelist)) {
throw new Exception("Whitelist is not of a supported format.");
}
$this->whitelist = $whitelist;
return $this;
}
/**
* Check if item exists in the whitelist.
*
* @param string $item string to check.
* @param array $whitelist optional with all valid options, default is null.
*
* @return boolean true if item is in whitelist, else false.
*/
public function check($item, $whitelist = null)
{
if ($whitelist !== null) {
$this->set($whitelist);
}
if (empty($item) or empty($this->whitelist)) {
return false;
}
foreach ($this->whitelist as $regexp) {
if (preg_match("#$regexp#", $item)) {
return true;
}
}
return false;
}
}
/**
* Create an ASCII version of an image.
*
*/
class CAsciiArt
{
/**
* Character set to use.
*/
private $characterSet = array(
'one' => "#0XT|:,.' ",
'two' => "@%#*+=-:. ",
'three' => "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
);
/**
* Current character set.
*/
private $characters = null;
/**
* Length of current character set.
*/
private $charCount = null;
/**
* Scale of the area to swap to a character.
*/
private $scale = null;
/**
* Strategy to calculate luminance.
*/
private $luminanceStrategy = null;
/**
* Constructor which sets default options.
*/
public function __construct()
{
$this->setOptions();
}
/**
* Add a custom character set.
*
* @param string $key for the character set.
* @param string $value for the character set.
*
* @return $this
*/
public function addCharacterSet($key, $value)
{
$this->characterSet[$key] = $value;
return $this;
}
/**
* Set options for processing, defaults are available.
*
* @param array $options to use as default settings.
*
* @return $this
*/
public function setOptions($options = array())
{
$default = array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
);
$default = array_merge($default, $options);
if (!is_null($default['customCharacterSet'])) {
$this->addCharacterSet('custom', $default['customCharacterSet']);
$default['characterSet'] = 'custom';
}
$this->scale = $default['scale'];
$this->characters = $this->characterSet[$default['characterSet']];
$this->charCount = strlen($this->characters);
$this->luminanceStrategy = $default['luminanceStrategy'];
return $this;
}
/**
* Create an Ascii image from an image file.
*
* @param string $filename of the image to use.
*
* @return string $ascii with the ASCII image.
*/
public function createFromFile($filename)
{
$img = imagecreatefromstring(file_get_contents($filename));
list($width, $height) = getimagesize($filename);
$ascii = null;
$incY = $this->scale;
$incX = $this->scale / 2;
for ($y = 0; $y < $height - 1; $y += $incY) {
for ($x = 0; $x < $width - 1; $x += $incX) {
$toX = min($x + $this->scale / 2, $width - 1);
$toY = min($y + $this->scale, $height - 1);
$luminance = $this->luminanceAreaAverage($img, $x, $y, $toX, $toY);
$ascii .= $this->luminance2character($luminance);
}
$ascii .= PHP_EOL;
}
return $ascii;
}
/**
* Get the luminance from a region of an image using average color value.
*
* @param string $img the image.
* @param integer $x1 the area to get pixels from.
* @param integer $y1 the area to get pixels from.
* @param integer $x2 the area to get pixels from.
* @param integer $y2 the area to get pixels from.
*
* @return integer $luminance with a value between 0 and 100.
*/
public function luminanceAreaAverage($img, $x1, $y1, $x2, $y2)
{
$numPixels = ($x2 - $x1 + 1) * ($y2 - $y1 + 1);
$luminance = 0;
for ($x = $x1; $x <= $x2; $x++) {
for ($y = $y1; $y <= $y2; $y++) {
$rgb = imagecolorat($img, $x, $y);
$red = (($rgb >> 16) & 0xFF);
$green = (($rgb >> 8) & 0xFF);
$blue = ($rgb & 0xFF);
$luminance += $this->getLuminance($red, $green, $blue);
}
}
return $luminance / $numPixels;
}
/**
* Calculate luminance value with different strategies.
*
* @param integer $red The color red.
* @param integer $green The color green.
* @param integer $blue The color blue.
*
* @return float $luminance with a value between 0 and 1.
*/
public function getLuminance($red, $green, $blue)
{
switch($this->luminanceStrategy) {
case 1:
$luminance = ($red * 0.2126 + $green * 0.7152 + $blue * 0.0722) / 255;
break;
case 2:
$luminance = ($red * 0.299 + $green * 0.587 + $blue * 0.114) / 255;
break;
case 3:
$luminance = sqrt(0.299 * pow($red, 2) + 0.587 * pow($green, 2) + 0.114 * pow($blue, 2)) / 255;
break;
case 0:
default:
$luminance = ($red + $green + $blue) / (255 * 3);
}
return $luminance;
}
/**
* Translate the luminance value to a character.
*
* @param string $position a value between 0-100 representing the
* luminance.
*
* @return string with the ascii character.
*/
public function luminance2character($luminance)
{
$position = (int) round($luminance * ($this->charCount - 1));
$char = $this->characters[$position];
return $char;
}
}
/**
* Resize and crop images on the fly, store generated images in a cache.
*
@@ -711,13 +1032,6 @@ class CImage
/**
* The root folder of images (only used in constructor to create $pathToImage?).
*/
private $imageFolder;
/**
* Image filename, may include subdirectory, relative from $imageFolder
*/
@@ -791,6 +1105,7 @@ class CImage
* Path to command for filter optimize, for example optipng or null.
*/
private $pngFilter;
private $pngFilterCmd;
@@ -798,13 +1113,16 @@ class CImage
* Path to command for deflate optimize, for example pngout or null.
*/
private $pngDeflate;
private $pngDeflateCmd;
/**
* Path to command to optimize jpeg images, for example jpegtran or null.
*/
private $jpegOptimize;
private $jpegOptimize;
private $jpegOptimizeCmd;
/**
@@ -880,6 +1198,56 @@ class CImage
private $fillToFit;
/**
* To store value for option scale.
*/
private $scale;
/**
* To store value for option.
*/
private $rotateBefore;
/**
* To store value for option.
*/
private $rotateAfter;
/**
* To store value for option.
*/
private $autoRotate;
/**
* To store value for option.
*/
private $sharpen;
/**
* To store value for option.
*/
private $emboss;
/**
* To store value for option.
*/
private $blur;
/**
* Used with option area to set which parts of the image to use.
*/
@@ -932,6 +1300,13 @@ class CImage
/*
* output to ascii can take som options as an array.
*/
private $asciiOptions = array();
/**
* Properties, the class is mutable and the method setOptions()
* decides (partly) what properties are created.
@@ -946,9 +1321,7 @@ class CImage
public $crop_x;
public $crop_y;
public $filters;
private $type; // Calculated from source image
private $attr; // Calculated from source image
private $useOriginal; // Use original image if possible
@@ -1005,9 +1378,7 @@ class CImage
/**
* Use cache or not.
*
* @todo clean up how $this->noCache is used in other methods.
*
* @param string $use true or false to use cache.
* @param boolean $use true or false to use cache.
*
* @return $this
*/
@@ -1019,6 +1390,27 @@ class CImage
/**
* Create and save a dummy image. Use dimensions as stated in
* $this->newWidth, or $width or default to 100 (same for height.
*
* @param integer $width use specified width for image dimension.
* @param integer $height use specified width for image dimension.
*
* @return $this
*/
public function createDummyImage($width = null, $height = null)
{
$this->newWidth = $this->newWidth ?: $width ?: 100;
$this->newHeight = $this->newHeight ?: $height ?: 100;
$this->image = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
return $this;
}
/**
* Allow or disallow remote image download.
*
@@ -1169,16 +1561,18 @@ class CImage
/**
* Set src file.
* Set source file to use as image source.
*
* @param string $src of image.
* @param string $dir as base directory where images are.
* @param string $dir as optional base directory where images are.
*
* @return $this
*/
public function setSource($src, $dir = null)
{
if (!isset($src)) {
$this->imageSrc = null;
$this->pathToImage = null;
return $this;
}
@@ -1192,9 +1586,9 @@ class CImage
$src = basename($src);
}
$this->imageSrc = ltrim($src, '/');
$this->imageFolder = rtrim($dir, '/');
$this->pathToImage = $this->imageFolder . '/' . $this->imageSrc;
$this->imageSrc = ltrim($src, '/');
$imageFolder = rtrim($dir, '/');
$this->pathToImage = $imageFolder . '/' . $this->imageSrc;
return $this;
}
@@ -1205,23 +1599,23 @@ class CImage
* Set target file.
*
* @param string $src of target image.
* @param string $dir as base directory where images are stored.
* @param string $dir as optional base directory where images are stored.
* Uses $this->saveFolder if null.
*
* @return $this
*/
public function setTarget($src = null, $dir = null)
{
if (!(isset($src) && isset($dir))) {
if (!isset($src)) {
$this->cacheFileName = null;
return $this;
}
$this->saveFolder = $dir;
$this->cacheFileName = $dir . '/' . $src;
if (isset($dir)) {
$this->saveFolder = rtrim($dir, '/');
}
/* Allow readonly cache
is_writable($this->saveFolder)
or $this->raiseError('Target directory is not writable.');
*/
$this->cacheFileName = $this->saveFolder . '/' . $src;
// Sanitize filename
$this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName);
@@ -1232,6 +1626,18 @@ class CImage
/**
* Get filename of target file.
*
* @return Boolean|String as filename of target or false if not set.
*/
public function getTarget()
{
return $this->cacheFileName;
}
/**
* Set options to use when processing image.
*
@@ -1279,11 +1685,6 @@ class CImage
// Output format
'outputFormat' => null,
'dpr' => 1,
// Options for saving
//'quality' => null,
//'compress' => null,
//'saveAs' => null,
);
// Convert crop settings from string to array
@@ -1777,13 +2178,15 @@ class CImage
/**
* Generate filename to save file in cache.
*
* @param string $base as basepath for storing file.
* @param string $base as optional basepath for storing file.
* @param boolean $useSubdir use or skip the subdir part when creating the
* filename.
*
* @return $this
*/
public function generateFilename($base)
public function generateFilename($base = null, $useSubdir = true)
{
$parts = pathinfo($this->pathToImage);
$filename = basename($this->pathToImage);
$cropToFit = $this->cropToFit ? '_cf' : null;
$fillToFit = $this->fillToFit ? '_ff' : null;
$crop_x = $this->crop_x ? "_x{$this->crop_x}" : null;
@@ -1812,7 +2215,7 @@ class CImage
if (is_array($filter)) {
$filters .= "_f{$filter['id']}";
for ($i=1; $i<=$filter['argc']; $i++) {
$filters .= ":".$filter["arg{$i}"];
$filters .= "-".$filter["arg{$i}"];
}
}
}
@@ -1825,16 +2228,6 @@ class CImage
$autoRotate = $this->autoRotate ? 'ar' : null;
$this->extension = isset($this->extension)
? $this->extension
: (isset($parts['extension'])
? $parts['extension']
: null);
$extension = empty($this->extension)
? null
: "." . $this->extension;
$optimize = $this->jpegOptimize ? 'o' : null;
$optimize .= $this->pngFilter ? 'f' : null;
$optimize .= $this->pngDeflate ? 'd' : null;
@@ -1849,14 +2242,20 @@ class CImage
$upscale = '_nu';
}
$subdir = str_replace('/', '-', dirname($this->imageSrc));
$subdir = ($subdir == '.') ? '_.' : $subdir;
$file = $subdir . '_' . $parts['filename'] . '_' . $width . '_'
$subdir = null;
if ($useSubdir === true) {
$subdir = str_replace('/', '-', dirname($this->imageSrc));
$subdir = ($subdir == '.') ? '_.' : $subdir;
$subdir .= '_';
}
$file = $subdir . $filename . '_' . $width . '_'
. $height . $offset . $crop . $cropToFit . $fillToFit
. $crop_x . $crop_y . $upscale
. $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize
. $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve
. $extension;
. $quality . $filters . $sharpen . $emboss . $blur . $palette
. $optimize . $compress
. $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor
. $convolve;
return $this->setTarget($file, $base);
}
@@ -2624,7 +3023,10 @@ class CImage
imagealphablending($img, false);
imagesavealpha($img, true);
$index = imagecolortransparent($this->image);
$index = $this->image
? imagecolortransparent($this->image)
: -1;
if ($index != -1) {
imagealphablending($img, true);
@@ -2697,17 +3099,23 @@ class CImage
/**
* Save image.
*
* @param string $src as target filename.
* @param string $base as base directory where to store images.
* @param string $src as target filename.
* @param string $base as base directory where to store images.
* @param boolean $overwrite or not, default to always overwrite file.
*
* @return $this or false if no folder is set.
*/
public function save($src = null, $base = null)
public function save($src = null, $base = null, $overwrite = true)
{
if (isset($src)) {
$this->setTarget($src, $base);
}
if ($overwrite === false && is_file($this->cacheFileName)) {
$this->Log("Not overwriting file since its already exists and \$overwrite if false.");
return;
}
is_writable($this->saveFolder)
or $this->raiseError('Target directory is not writable.');
@@ -2848,6 +3256,10 @@ class CImage
header('Content-type: application/json');
echo $this->json($file);
exit;
} elseif ($format == 'ascii') {
header('Content-type: text/plain');
echo $this->ascii($file);
exit;
}
$this->log("Outputting image: $file");
@@ -2939,6 +3351,38 @@ class CImage
/**
* Set options for creating ascii version of image.
*
* @param array $options empty to use default or set options to change.
*
* @return void.
*/
public function setAsciiOptions($options = array())
{
$this->asciiOptions = $options;
}
/**
* Create an ASCII version from the image details.
*
* @param string $file the file to output.
*
* @return string ASCII representation of the image.
*/
public function ascii($file = null)
{
$file = $file ? $file : $this->cacheFileName;
$asciiArt = new CAsciiArt();
$asciiArt->setOptions($this->asciiOptions);
return $asciiArt->createFromFile($file);
}
/**
* Log an event if verbose mode.
*
@@ -3028,70 +3472,6 @@ EOD;
/**
* Act as whitelist (or blacklist).
*
*/
class CWhitelist
{
/**
* Array to contain the whitelist options.
*/
private $whitelist = array();
/**
* Set the whitelist from an array of strings, each item in the
* whitelist should be a regexp without the surrounding / or #.
*
* @param array $whitelist with all valid options,
* default is to clear the whitelist.
*
* @return $this
*/
public function set($whitelist = array())
{
if (!is_array($whitelist)) {
throw new Exception("Whitelist is not of a supported format.");
}
$this->whitelist = $whitelist;
return $this;
}
/**
* Check if item exists in the whitelist.
*
* @param string $item string to check.
* @param array $whitelist optional with all valid options, default is null.
*
* @return boolean true if item is in whitelist, else false.
*/
public function check($item, $whitelist = null)
{
if ($whitelist !== null) {
$this->set($whitelist);
}
if (empty($item) or empty($this->whitelist)) {
return false;
}
foreach ($this->whitelist as $regexp) {
if (preg_match("#$regexp#", $item)) {
return true;
}
}
return false;
}
}
/**
* Resize and crop images on the fly, store generated images in a cache.
*
@@ -3101,12 +3481,12 @@ class CWhitelist
*
*/
$version = "v0.7.1 (2015-07-25)";
$version = "v0.7.6 (2015-10-18)";
/**
* Default configuration options, can be overridden in own config-file.
* Display error message.
*
* @param string $msg to display.
*
@@ -3466,10 +3846,20 @@ $imagePath = getConfig('image_path', __DIR__ . '/img/');
$imagePathConstraint = getConfig('image_path_constraint', true);
$validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#');
// Dummy image feature
$dummyEnabled = getConfig('dummy_enabled', true);
$dummyFilename = getConfig('dummy_filename', 'dummy');
$dummyImage = false;
preg_match($validFilename, $srcImage)
or errorPage('Filename contains invalid characters.');
if ($allowRemote && $img->isRemoteSource($srcImage)) {
if ($dummyEnabled && $srcImage === $dummyFilename) {
// Prepare to create a dummy image and use it as the source image.
$dummyImage = true;
} elseif ($allowRemote && $img->isRemoteSource($srcImage)) {
// If source is a remote file, ignore local file checks.
@@ -3847,11 +4237,51 @@ verbose("filters = " . print_r($filters, 1));
/**
* json - output the image as a JSON object with details on the image.
* json - output the image as a JSON object with details on the image.
* ascii - output the image as ASCII art.
*/
$outputFormat = getDefined('json', 'json', null);
$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
verbose("outputformat = $outputFormat");
if ($outputFormat == 'ascii') {
$defaultOptions = getConfig(
'ascii-options',
array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
)
);
$options = get('ascii');
$options = explode(',', $options);
if (isset($options[0]) && !empty($options[0])) {
$defaultOptions['characterSet'] = $options[0];
}
if (isset($options[1]) && !empty($options[1])) {
$defaultOptions['scale'] = $options[1];
}
if (isset($options[2]) && !empty($options[2])) {
$defaultOptions['luminanceStrategy'] = $options[2];
}
if (count($options) > 3) {
// Last option is custom character string
unset($options[0]);
unset($options[1]);
unset($options[2]);
$characterString = implode($options);
$defaultOptions['customCharacterSet'] = $characterString;
}
$img->setAsciiOptions($defaultOptions);
}
verbose("json = $outputFormat");
@@ -3986,9 +4416,47 @@ if ($verboseFile) {
/**
* Load, process and output the image
*/
/**
* Set basic options for image processing.
*/
/**
* Prepare a dummy image and use it as source image.
*/
if ($dummyImage === true) {
$dummyDir = getConfig('dummy_dir', $cachePath. "/" . $dummyFilename);
is_writable($dummyDir)
or verbose("dummy dir not writable = $dummyDir");
$img->setSaveFolder($dummyDir)
->setSource($dummyFilename, $dummyDir)
->setOptions(
array(
'newWidth' => $newWidth,
'newHeight' => $newHeight,
'bgColor' => $bgColor,
)
)
->setJpegQuality($quality)
->setPngCompression($compress)
->createDummyImage()
->generateFilename(null, false)
->save(null, null, false);
$srcImage = $img->getTarget();
$imagePath = null;
verbose("src (updated) = $srcImage");
}
/**
* Load, process and output the image
*/
$img->log("Incoming arguments: " . print_r(verbose(), 1))
->setSaveFolder($cachePath)
->useCache($useCache)

View File

@@ -58,6 +58,33 @@ class CHttpGet
/**
* Build an encoded url.
*
* @param string $baseUrl This is the original url which will be merged.
* @param string $merge Thse parts should be merged into the baseUrl,
* the format is as parse_url.
*
* @return string $url as the modified url.
*/
public function buildUrl($baseUrl, $merge)
{
$parts = parse_url($baseUrl);
$parts = array_merge($parts, $merge);
$url = $parts['scheme'];
$url .= "://";
$url .= $parts['host'];
$url .= isset($parts['port'])
? ":" . $parts['port']
: "" ;
$url .= $parts['path'];
return $url;
}
/**
* Set the url for the request.
*
@@ -67,6 +94,18 @@ class CHttpGet
*/
public function setUrl($url)
{
$parts = parse_url($url);
$path = "";
if (isset($parts['path'])) {
$pathParts = explode('/', $parts['path']);
unset($pathParts[0]);
foreach ($pathParts as $value) {
$path .= "/" . rawurlencode($value);
}
}
$url = $this->buildUrl($url, array("path" => $path));
$this->request['url'] = $url;
return $this;
}
@@ -130,6 +169,8 @@ class CHttpGet
*
* @param boolean $debug set to true to dump headers.
*
* @throws Exception when curl fails to retrieve url.
*
* @return boolean
*/
public function doGet($debug = false)
@@ -152,7 +193,7 @@ class CHttpGet
$response = curl_exec($ch);
if (!$response) {
return false;
throw new Exception("Failed retrieving url, details follows: " . curl_error($ch));
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
@@ -435,7 +476,7 @@ class CRemoteImage
*/
public function setHeaderFields()
{
$this->http->setHeader("User-Agent", "CImage/0.7.0 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("User-Agent", "CImage/0.7.2 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("Accept", "image/jpeg,image/png,image/gif");
if ($this->useCache) {
@@ -514,6 +555,8 @@ class CRemoteImage
*
* @param string $url a remote url.
*
* @throws Exception when status code does not match 200 or 304.
*
* @return string as path to downloaded file or false if failed.
*/
public function download($url)
@@ -546,7 +589,7 @@ class CRemoteImage
return $this->updateCacheDetails();
}
return false;
throw new Exception("Unknown statuscode when downloading remote image: " . $this->status);
}
@@ -597,6 +640,284 @@ class CRemoteImage
/**
* Act as whitelist (or blacklist).
*
*/
class CWhitelist
{
/**
* Array to contain the whitelist options.
*/
private $whitelist = array();
/**
* Set the whitelist from an array of strings, each item in the
* whitelist should be a regexp without the surrounding / or #.
*
* @param array $whitelist with all valid options,
* default is to clear the whitelist.
*
* @return $this
*/
public function set($whitelist = array())
{
if (!is_array($whitelist)) {
throw new Exception("Whitelist is not of a supported format.");
}
$this->whitelist = $whitelist;
return $this;
}
/**
* Check if item exists in the whitelist.
*
* @param string $item string to check.
* @param array $whitelist optional with all valid options, default is null.
*
* @return boolean true if item is in whitelist, else false.
*/
public function check($item, $whitelist = null)
{
if ($whitelist !== null) {
$this->set($whitelist);
}
if (empty($item) or empty($this->whitelist)) {
return false;
}
foreach ($this->whitelist as $regexp) {
if (preg_match("#$regexp#", $item)) {
return true;
}
}
return false;
}
}
/**
* Create an ASCII version of an image.
*
*/
class CAsciiArt
{
/**
* Character set to use.
*/
private $characterSet = array(
'one' => "#0XT|:,.' ",
'two' => "@%#*+=-:. ",
'three' => "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
);
/**
* Current character set.
*/
private $characters = null;
/**
* Length of current character set.
*/
private $charCount = null;
/**
* Scale of the area to swap to a character.
*/
private $scale = null;
/**
* Strategy to calculate luminance.
*/
private $luminanceStrategy = null;
/**
* Constructor which sets default options.
*/
public function __construct()
{
$this->setOptions();
}
/**
* Add a custom character set.
*
* @param string $key for the character set.
* @param string $value for the character set.
*
* @return $this
*/
public function addCharacterSet($key, $value)
{
$this->characterSet[$key] = $value;
return $this;
}
/**
* Set options for processing, defaults are available.
*
* @param array $options to use as default settings.
*
* @return $this
*/
public function setOptions($options = array())
{
$default = array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
);
$default = array_merge($default, $options);
if (!is_null($default['customCharacterSet'])) {
$this->addCharacterSet('custom', $default['customCharacterSet']);
$default['characterSet'] = 'custom';
}
$this->scale = $default['scale'];
$this->characters = $this->characterSet[$default['characterSet']];
$this->charCount = strlen($this->characters);
$this->luminanceStrategy = $default['luminanceStrategy'];
return $this;
}
/**
* Create an Ascii image from an image file.
*
* @param string $filename of the image to use.
*
* @return string $ascii with the ASCII image.
*/
public function createFromFile($filename)
{
$img = imagecreatefromstring(file_get_contents($filename));
list($width, $height) = getimagesize($filename);
$ascii = null;
$incY = $this->scale;
$incX = $this->scale / 2;
for ($y = 0; $y < $height - 1; $y += $incY) {
for ($x = 0; $x < $width - 1; $x += $incX) {
$toX = min($x + $this->scale / 2, $width - 1);
$toY = min($y + $this->scale, $height - 1);
$luminance = $this->luminanceAreaAverage($img, $x, $y, $toX, $toY);
$ascii .= $this->luminance2character($luminance);
}
$ascii .= PHP_EOL;
}
return $ascii;
}
/**
* Get the luminance from a region of an image using average color value.
*
* @param string $img the image.
* @param integer $x1 the area to get pixels from.
* @param integer $y1 the area to get pixels from.
* @param integer $x2 the area to get pixels from.
* @param integer $y2 the area to get pixels from.
*
* @return integer $luminance with a value between 0 and 100.
*/
public function luminanceAreaAverage($img, $x1, $y1, $x2, $y2)
{
$numPixels = ($x2 - $x1 + 1) * ($y2 - $y1 + 1);
$luminance = 0;
for ($x = $x1; $x <= $x2; $x++) {
for ($y = $y1; $y <= $y2; $y++) {
$rgb = imagecolorat($img, $x, $y);
$red = (($rgb >> 16) & 0xFF);
$green = (($rgb >> 8) & 0xFF);
$blue = ($rgb & 0xFF);
$luminance += $this->getLuminance($red, $green, $blue);
}
}
return $luminance / $numPixels;
}
/**
* Calculate luminance value with different strategies.
*
* @param integer $red The color red.
* @param integer $green The color green.
* @param integer $blue The color blue.
*
* @return float $luminance with a value between 0 and 1.
*/
public function getLuminance($red, $green, $blue)
{
switch($this->luminanceStrategy) {
case 1:
$luminance = ($red * 0.2126 + $green * 0.7152 + $blue * 0.0722) / 255;
break;
case 2:
$luminance = ($red * 0.299 + $green * 0.587 + $blue * 0.114) / 255;
break;
case 3:
$luminance = sqrt(0.299 * pow($red, 2) + 0.587 * pow($green, 2) + 0.114 * pow($blue, 2)) / 255;
break;
case 0:
default:
$luminance = ($red + $green + $blue) / (255 * 3);
}
return $luminance;
}
/**
* Translate the luminance value to a character.
*
* @param string $position a value between 0-100 representing the
* luminance.
*
* @return string with the ascii character.
*/
public function luminance2character($luminance)
{
$position = (int) round($luminance * ($this->charCount - 1));
$char = $this->characters[$position];
return $char;
}
}
/**
* Resize and crop images on the fly, store generated images in a cache.
*
@@ -711,13 +1032,6 @@ class CImage
/**
* The root folder of images (only used in constructor to create $pathToImage?).
*/
private $imageFolder;
/**
* Image filename, may include subdirectory, relative from $imageFolder
*/
@@ -791,6 +1105,7 @@ class CImage
* Path to command for filter optimize, for example optipng or null.
*/
private $pngFilter;
private $pngFilterCmd;
@@ -798,13 +1113,16 @@ class CImage
* Path to command for deflate optimize, for example pngout or null.
*/
private $pngDeflate;
private $pngDeflateCmd;
/**
* Path to command to optimize jpeg images, for example jpegtran or null.
*/
private $jpegOptimize;
private $jpegOptimize;
private $jpegOptimizeCmd;
/**
@@ -880,6 +1198,56 @@ class CImage
private $fillToFit;
/**
* To store value for option scale.
*/
private $scale;
/**
* To store value for option.
*/
private $rotateBefore;
/**
* To store value for option.
*/
private $rotateAfter;
/**
* To store value for option.
*/
private $autoRotate;
/**
* To store value for option.
*/
private $sharpen;
/**
* To store value for option.
*/
private $emboss;
/**
* To store value for option.
*/
private $blur;
/**
* Used with option area to set which parts of the image to use.
*/
@@ -932,6 +1300,13 @@ class CImage
/*
* output to ascii can take som options as an array.
*/
private $asciiOptions = array();
/**
* Properties, the class is mutable and the method setOptions()
* decides (partly) what properties are created.
@@ -946,9 +1321,7 @@ class CImage
public $crop_x;
public $crop_y;
public $filters;
private $type; // Calculated from source image
private $attr; // Calculated from source image
private $useOriginal; // Use original image if possible
@@ -1005,9 +1378,7 @@ class CImage
/**
* Use cache or not.
*
* @todo clean up how $this->noCache is used in other methods.
*
* @param string $use true or false to use cache.
* @param boolean $use true or false to use cache.
*
* @return $this
*/
@@ -1019,6 +1390,27 @@ class CImage
/**
* Create and save a dummy image. Use dimensions as stated in
* $this->newWidth, or $width or default to 100 (same for height.
*
* @param integer $width use specified width for image dimension.
* @param integer $height use specified width for image dimension.
*
* @return $this
*/
public function createDummyImage($width = null, $height = null)
{
$this->newWidth = $this->newWidth ?: $width ?: 100;
$this->newHeight = $this->newHeight ?: $height ?: 100;
$this->image = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
return $this;
}
/**
* Allow or disallow remote image download.
*
@@ -1169,16 +1561,18 @@ class CImage
/**
* Set src file.
* Set source file to use as image source.
*
* @param string $src of image.
* @param string $dir as base directory where images are.
* @param string $dir as optional base directory where images are.
*
* @return $this
*/
public function setSource($src, $dir = null)
{
if (!isset($src)) {
$this->imageSrc = null;
$this->pathToImage = null;
return $this;
}
@@ -1192,9 +1586,9 @@ class CImage
$src = basename($src);
}
$this->imageSrc = ltrim($src, '/');
$this->imageFolder = rtrim($dir, '/');
$this->pathToImage = $this->imageFolder . '/' . $this->imageSrc;
$this->imageSrc = ltrim($src, '/');
$imageFolder = rtrim($dir, '/');
$this->pathToImage = $imageFolder . '/' . $this->imageSrc;
return $this;
}
@@ -1205,23 +1599,23 @@ class CImage
* Set target file.
*
* @param string $src of target image.
* @param string $dir as base directory where images are stored.
* @param string $dir as optional base directory where images are stored.
* Uses $this->saveFolder if null.
*
* @return $this
*/
public function setTarget($src = null, $dir = null)
{
if (!(isset($src) && isset($dir))) {
if (!isset($src)) {
$this->cacheFileName = null;
return $this;
}
$this->saveFolder = $dir;
$this->cacheFileName = $dir . '/' . $src;
if (isset($dir)) {
$this->saveFolder = rtrim($dir, '/');
}
/* Allow readonly cache
is_writable($this->saveFolder)
or $this->raiseError('Target directory is not writable.');
*/
$this->cacheFileName = $this->saveFolder . '/' . $src;
// Sanitize filename
$this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName);
@@ -1232,6 +1626,18 @@ class CImage
/**
* Get filename of target file.
*
* @return Boolean|String as filename of target or false if not set.
*/
public function getTarget()
{
return $this->cacheFileName;
}
/**
* Set options to use when processing image.
*
@@ -1279,11 +1685,6 @@ class CImage
// Output format
'outputFormat' => null,
'dpr' => 1,
// Options for saving
//'quality' => null,
//'compress' => null,
//'saveAs' => null,
);
// Convert crop settings from string to array
@@ -1777,13 +2178,15 @@ class CImage
/**
* Generate filename to save file in cache.
*
* @param string $base as basepath for storing file.
* @param string $base as optional basepath for storing file.
* @param boolean $useSubdir use or skip the subdir part when creating the
* filename.
*
* @return $this
*/
public function generateFilename($base)
public function generateFilename($base = null, $useSubdir = true)
{
$parts = pathinfo($this->pathToImage);
$filename = basename($this->pathToImage);
$cropToFit = $this->cropToFit ? '_cf' : null;
$fillToFit = $this->fillToFit ? '_ff' : null;
$crop_x = $this->crop_x ? "_x{$this->crop_x}" : null;
@@ -1812,7 +2215,7 @@ class CImage
if (is_array($filter)) {
$filters .= "_f{$filter['id']}";
for ($i=1; $i<=$filter['argc']; $i++) {
$filters .= ":".$filter["arg{$i}"];
$filters .= "-".$filter["arg{$i}"];
}
}
}
@@ -1825,16 +2228,6 @@ class CImage
$autoRotate = $this->autoRotate ? 'ar' : null;
$this->extension = isset($this->extension)
? $this->extension
: (isset($parts['extension'])
? $parts['extension']
: null);
$extension = empty($this->extension)
? null
: "." . $this->extension;
$optimize = $this->jpegOptimize ? 'o' : null;
$optimize .= $this->pngFilter ? 'f' : null;
$optimize .= $this->pngDeflate ? 'd' : null;
@@ -1849,14 +2242,20 @@ class CImage
$upscale = '_nu';
}
$subdir = str_replace('/', '-', dirname($this->imageSrc));
$subdir = ($subdir == '.') ? '_.' : $subdir;
$file = $subdir . '_' . $parts['filename'] . '_' . $width . '_'
$subdir = null;
if ($useSubdir === true) {
$subdir = str_replace('/', '-', dirname($this->imageSrc));
$subdir = ($subdir == '.') ? '_.' : $subdir;
$subdir .= '_';
}
$file = $subdir . $filename . '_' . $width . '_'
. $height . $offset . $crop . $cropToFit . $fillToFit
. $crop_x . $crop_y . $upscale
. $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize
. $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve
. $extension;
. $quality . $filters . $sharpen . $emboss . $blur . $palette
. $optimize . $compress
. $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor
. $convolve;
return $this->setTarget($file, $base);
}
@@ -2624,7 +3023,10 @@ class CImage
imagealphablending($img, false);
imagesavealpha($img, true);
$index = imagecolortransparent($this->image);
$index = $this->image
? imagecolortransparent($this->image)
: -1;
if ($index != -1) {
imagealphablending($img, true);
@@ -2697,17 +3099,23 @@ class CImage
/**
* Save image.
*
* @param string $src as target filename.
* @param string $base as base directory where to store images.
* @param string $src as target filename.
* @param string $base as base directory where to store images.
* @param boolean $overwrite or not, default to always overwrite file.
*
* @return $this or false if no folder is set.
*/
public function save($src = null, $base = null)
public function save($src = null, $base = null, $overwrite = true)
{
if (isset($src)) {
$this->setTarget($src, $base);
}
if ($overwrite === false && is_file($this->cacheFileName)) {
$this->Log("Not overwriting file since its already exists and \$overwrite if false.");
return;
}
is_writable($this->saveFolder)
or $this->raiseError('Target directory is not writable.');
@@ -2848,6 +3256,10 @@ class CImage
header('Content-type: application/json');
echo $this->json($file);
exit;
} elseif ($format == 'ascii') {
header('Content-type: text/plain');
echo $this->ascii($file);
exit;
}
$this->log("Outputting image: $file");
@@ -2939,6 +3351,38 @@ class CImage
/**
* Set options for creating ascii version of image.
*
* @param array $options empty to use default or set options to change.
*
* @return void.
*/
public function setAsciiOptions($options = array())
{
$this->asciiOptions = $options;
}
/**
* Create an ASCII version from the image details.
*
* @param string $file the file to output.
*
* @return string ASCII representation of the image.
*/
public function ascii($file = null)
{
$file = $file ? $file : $this->cacheFileName;
$asciiArt = new CAsciiArt();
$asciiArt->setOptions($this->asciiOptions);
return $asciiArt->createFromFile($file);
}
/**
* Log an event if verbose mode.
*
@@ -3028,70 +3472,6 @@ EOD;
/**
* Act as whitelist (or blacklist).
*
*/
class CWhitelist
{
/**
* Array to contain the whitelist options.
*/
private $whitelist = array();
/**
* Set the whitelist from an array of strings, each item in the
* whitelist should be a regexp without the surrounding / or #.
*
* @param array $whitelist with all valid options,
* default is to clear the whitelist.
*
* @return $this
*/
public function set($whitelist = array())
{
if (!is_array($whitelist)) {
throw new Exception("Whitelist is not of a supported format.");
}
$this->whitelist = $whitelist;
return $this;
}
/**
* Check if item exists in the whitelist.
*
* @param string $item string to check.
* @param array $whitelist optional with all valid options, default is null.
*
* @return boolean true if item is in whitelist, else false.
*/
public function check($item, $whitelist = null)
{
if ($whitelist !== null) {
$this->set($whitelist);
}
if (empty($item) or empty($this->whitelist)) {
return false;
}
foreach ($this->whitelist as $regexp) {
if (preg_match("#$regexp#", $item)) {
return true;
}
}
return false;
}
}
/**
* Resize and crop images on the fly, store generated images in a cache.
*
@@ -3101,12 +3481,12 @@ class CWhitelist
*
*/
$version = "v0.7.1 (2015-07-25)";
$version = "v0.7.6 (2015-10-18)";
/**
* Default configuration options, can be overridden in own config-file.
* Display error message.
*
* @param string $msg to display.
*
@@ -3466,10 +3846,20 @@ $imagePath = getConfig('image_path', __DIR__ . '/img/');
$imagePathConstraint = getConfig('image_path_constraint', true);
$validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#');
// Dummy image feature
$dummyEnabled = getConfig('dummy_enabled', true);
$dummyFilename = getConfig('dummy_filename', 'dummy');
$dummyImage = false;
preg_match($validFilename, $srcImage)
or errorPage('Filename contains invalid characters.');
if ($allowRemote && $img->isRemoteSource($srcImage)) {
if ($dummyEnabled && $srcImage === $dummyFilename) {
// Prepare to create a dummy image and use it as the source image.
$dummyImage = true;
} elseif ($allowRemote && $img->isRemoteSource($srcImage)) {
// If source is a remote file, ignore local file checks.
@@ -3847,11 +4237,51 @@ verbose("filters = " . print_r($filters, 1));
/**
* json - output the image as a JSON object with details on the image.
* json - output the image as a JSON object with details on the image.
* ascii - output the image as ASCII art.
*/
$outputFormat = getDefined('json', 'json', null);
$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
verbose("outputformat = $outputFormat");
if ($outputFormat == 'ascii') {
$defaultOptions = getConfig(
'ascii-options',
array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
)
);
$options = get('ascii');
$options = explode(',', $options);
if (isset($options[0]) && !empty($options[0])) {
$defaultOptions['characterSet'] = $options[0];
}
if (isset($options[1]) && !empty($options[1])) {
$defaultOptions['scale'] = $options[1];
}
if (isset($options[2]) && !empty($options[2])) {
$defaultOptions['luminanceStrategy'] = $options[2];
}
if (count($options) > 3) {
// Last option is custom character string
unset($options[0]);
unset($options[1]);
unset($options[2]);
$characterString = implode($options);
$defaultOptions['customCharacterSet'] = $characterString;
}
$img->setAsciiOptions($defaultOptions);
}
verbose("json = $outputFormat");
@@ -3986,9 +4416,47 @@ if ($verboseFile) {
/**
* Load, process and output the image
*/
/**
* Set basic options for image processing.
*/
/**
* Prepare a dummy image and use it as source image.
*/
if ($dummyImage === true) {
$dummyDir = getConfig('dummy_dir', $cachePath. "/" . $dummyFilename);
is_writable($dummyDir)
or verbose("dummy dir not writable = $dummyDir");
$img->setSaveFolder($dummyDir)
->setSource($dummyFilename, $dummyDir)
->setOptions(
array(
'newWidth' => $newWidth,
'newHeight' => $newHeight,
'bgColor' => $bgColor,
)
)
->setJpegQuality($quality)
->setPngCompression($compress)
->createDummyImage()
->generateFilename(null, false)
->save(null, null, false);
$srcImage = $img->getTarget();
$imagePath = null;
verbose("src (updated) = $srcImage");
}
/**
* Load, process and output the image
*/
$img->log("Incoming arguments: " . print_r(verbose(), 1))
->setSaveFolder($cachePath)
->useCache($useCache)

View File

@@ -58,6 +58,33 @@ class CHttpGet
/**
* Build an encoded url.
*
* @param string $baseUrl This is the original url which will be merged.
* @param string $merge Thse parts should be merged into the baseUrl,
* the format is as parse_url.
*
* @return string $url as the modified url.
*/
public function buildUrl($baseUrl, $merge)
{
$parts = parse_url($baseUrl);
$parts = array_merge($parts, $merge);
$url = $parts['scheme'];
$url .= "://";
$url .= $parts['host'];
$url .= isset($parts['port'])
? ":" . $parts['port']
: "" ;
$url .= $parts['path'];
return $url;
}
/**
* Set the url for the request.
*
@@ -67,6 +94,18 @@ class CHttpGet
*/
public function setUrl($url)
{
$parts = parse_url($url);
$path = "";
if (isset($parts['path'])) {
$pathParts = explode('/', $parts['path']);
unset($pathParts[0]);
foreach ($pathParts as $value) {
$path .= "/" . rawurlencode($value);
}
}
$url = $this->buildUrl($url, array("path" => $path));
$this->request['url'] = $url;
return $this;
}
@@ -130,6 +169,8 @@ class CHttpGet
*
* @param boolean $debug set to true to dump headers.
*
* @throws Exception when curl fails to retrieve url.
*
* @return boolean
*/
public function doGet($debug = false)
@@ -152,7 +193,7 @@ class CHttpGet
$response = curl_exec($ch);
if (!$response) {
return false;
throw new Exception("Failed retrieving url, details follows: " . curl_error($ch));
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
@@ -435,7 +476,7 @@ class CRemoteImage
*/
public function setHeaderFields()
{
$this->http->setHeader("User-Agent", "CImage/0.7.0 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("User-Agent", "CImage/0.7.2 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("Accept", "image/jpeg,image/png,image/gif");
if ($this->useCache) {
@@ -514,6 +555,8 @@ class CRemoteImage
*
* @param string $url a remote url.
*
* @throws Exception when status code does not match 200 or 304.
*
* @return string as path to downloaded file or false if failed.
*/
public function download($url)
@@ -546,7 +589,7 @@ class CRemoteImage
return $this->updateCacheDetails();
}
return false;
throw new Exception("Unknown statuscode when downloading remote image: " . $this->status);
}
@@ -597,6 +640,284 @@ class CRemoteImage
/**
* Act as whitelist (or blacklist).
*
*/
class CWhitelist
{
/**
* Array to contain the whitelist options.
*/
private $whitelist = array();
/**
* Set the whitelist from an array of strings, each item in the
* whitelist should be a regexp without the surrounding / or #.
*
* @param array $whitelist with all valid options,
* default is to clear the whitelist.
*
* @return $this
*/
public function set($whitelist = array())
{
if (!is_array($whitelist)) {
throw new Exception("Whitelist is not of a supported format.");
}
$this->whitelist = $whitelist;
return $this;
}
/**
* Check if item exists in the whitelist.
*
* @param string $item string to check.
* @param array $whitelist optional with all valid options, default is null.
*
* @return boolean true if item is in whitelist, else false.
*/
public function check($item, $whitelist = null)
{
if ($whitelist !== null) {
$this->set($whitelist);
}
if (empty($item) or empty($this->whitelist)) {
return false;
}
foreach ($this->whitelist as $regexp) {
if (preg_match("#$regexp#", $item)) {
return true;
}
}
return false;
}
}
/**
* Create an ASCII version of an image.
*
*/
class CAsciiArt
{
/**
* Character set to use.
*/
private $characterSet = array(
'one' => "#0XT|:,.' ",
'two' => "@%#*+=-:. ",
'three' => "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
);
/**
* Current character set.
*/
private $characters = null;
/**
* Length of current character set.
*/
private $charCount = null;
/**
* Scale of the area to swap to a character.
*/
private $scale = null;
/**
* Strategy to calculate luminance.
*/
private $luminanceStrategy = null;
/**
* Constructor which sets default options.
*/
public function __construct()
{
$this->setOptions();
}
/**
* Add a custom character set.
*
* @param string $key for the character set.
* @param string $value for the character set.
*
* @return $this
*/
public function addCharacterSet($key, $value)
{
$this->characterSet[$key] = $value;
return $this;
}
/**
* Set options for processing, defaults are available.
*
* @param array $options to use as default settings.
*
* @return $this
*/
public function setOptions($options = array())
{
$default = array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
);
$default = array_merge($default, $options);
if (!is_null($default['customCharacterSet'])) {
$this->addCharacterSet('custom', $default['customCharacterSet']);
$default['characterSet'] = 'custom';
}
$this->scale = $default['scale'];
$this->characters = $this->characterSet[$default['characterSet']];
$this->charCount = strlen($this->characters);
$this->luminanceStrategy = $default['luminanceStrategy'];
return $this;
}
/**
* Create an Ascii image from an image file.
*
* @param string $filename of the image to use.
*
* @return string $ascii with the ASCII image.
*/
public function createFromFile($filename)
{
$img = imagecreatefromstring(file_get_contents($filename));
list($width, $height) = getimagesize($filename);
$ascii = null;
$incY = $this->scale;
$incX = $this->scale / 2;
for ($y = 0; $y < $height - 1; $y += $incY) {
for ($x = 0; $x < $width - 1; $x += $incX) {
$toX = min($x + $this->scale / 2, $width - 1);
$toY = min($y + $this->scale, $height - 1);
$luminance = $this->luminanceAreaAverage($img, $x, $y, $toX, $toY);
$ascii .= $this->luminance2character($luminance);
}
$ascii .= PHP_EOL;
}
return $ascii;
}
/**
* Get the luminance from a region of an image using average color value.
*
* @param string $img the image.
* @param integer $x1 the area to get pixels from.
* @param integer $y1 the area to get pixels from.
* @param integer $x2 the area to get pixels from.
* @param integer $y2 the area to get pixels from.
*
* @return integer $luminance with a value between 0 and 100.
*/
public function luminanceAreaAverage($img, $x1, $y1, $x2, $y2)
{
$numPixels = ($x2 - $x1 + 1) * ($y2 - $y1 + 1);
$luminance = 0;
for ($x = $x1; $x <= $x2; $x++) {
for ($y = $y1; $y <= $y2; $y++) {
$rgb = imagecolorat($img, $x, $y);
$red = (($rgb >> 16) & 0xFF);
$green = (($rgb >> 8) & 0xFF);
$blue = ($rgb & 0xFF);
$luminance += $this->getLuminance($red, $green, $blue);
}
}
return $luminance / $numPixels;
}
/**
* Calculate luminance value with different strategies.
*
* @param integer $red The color red.
* @param integer $green The color green.
* @param integer $blue The color blue.
*
* @return float $luminance with a value between 0 and 1.
*/
public function getLuminance($red, $green, $blue)
{
switch($this->luminanceStrategy) {
case 1:
$luminance = ($red * 0.2126 + $green * 0.7152 + $blue * 0.0722) / 255;
break;
case 2:
$luminance = ($red * 0.299 + $green * 0.587 + $blue * 0.114) / 255;
break;
case 3:
$luminance = sqrt(0.299 * pow($red, 2) + 0.587 * pow($green, 2) + 0.114 * pow($blue, 2)) / 255;
break;
case 0:
default:
$luminance = ($red + $green + $blue) / (255 * 3);
}
return $luminance;
}
/**
* Translate the luminance value to a character.
*
* @param string $position a value between 0-100 representing the
* luminance.
*
* @return string with the ascii character.
*/
public function luminance2character($luminance)
{
$position = (int) round($luminance * ($this->charCount - 1));
$char = $this->characters[$position];
return $char;
}
}
/**
* Resize and crop images on the fly, store generated images in a cache.
*
@@ -711,13 +1032,6 @@ class CImage
/**
* The root folder of images (only used in constructor to create $pathToImage?).
*/
private $imageFolder;
/**
* Image filename, may include subdirectory, relative from $imageFolder
*/
@@ -791,6 +1105,7 @@ class CImage
* Path to command for filter optimize, for example optipng or null.
*/
private $pngFilter;
private $pngFilterCmd;
@@ -798,13 +1113,16 @@ class CImage
* Path to command for deflate optimize, for example pngout or null.
*/
private $pngDeflate;
private $pngDeflateCmd;
/**
* Path to command to optimize jpeg images, for example jpegtran or null.
*/
private $jpegOptimize;
private $jpegOptimize;
private $jpegOptimizeCmd;
/**
@@ -880,6 +1198,56 @@ class CImage
private $fillToFit;
/**
* To store value for option scale.
*/
private $scale;
/**
* To store value for option.
*/
private $rotateBefore;
/**
* To store value for option.
*/
private $rotateAfter;
/**
* To store value for option.
*/
private $autoRotate;
/**
* To store value for option.
*/
private $sharpen;
/**
* To store value for option.
*/
private $emboss;
/**
* To store value for option.
*/
private $blur;
/**
* Used with option area to set which parts of the image to use.
*/
@@ -932,6 +1300,13 @@ class CImage
/*
* output to ascii can take som options as an array.
*/
private $asciiOptions = array();
/**
* Properties, the class is mutable and the method setOptions()
* decides (partly) what properties are created.
@@ -946,9 +1321,7 @@ class CImage
public $crop_x;
public $crop_y;
public $filters;
private $type; // Calculated from source image
private $attr; // Calculated from source image
private $useOriginal; // Use original image if possible
@@ -1005,9 +1378,7 @@ class CImage
/**
* Use cache or not.
*
* @todo clean up how $this->noCache is used in other methods.
*
* @param string $use true or false to use cache.
* @param boolean $use true or false to use cache.
*
* @return $this
*/
@@ -1019,6 +1390,27 @@ class CImage
/**
* Create and save a dummy image. Use dimensions as stated in
* $this->newWidth, or $width or default to 100 (same for height.
*
* @param integer $width use specified width for image dimension.
* @param integer $height use specified width for image dimension.
*
* @return $this
*/
public function createDummyImage($width = null, $height = null)
{
$this->newWidth = $this->newWidth ?: $width ?: 100;
$this->newHeight = $this->newHeight ?: $height ?: 100;
$this->image = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
return $this;
}
/**
* Allow or disallow remote image download.
*
@@ -1169,16 +1561,18 @@ class CImage
/**
* Set src file.
* Set source file to use as image source.
*
* @param string $src of image.
* @param string $dir as base directory where images are.
* @param string $dir as optional base directory where images are.
*
* @return $this
*/
public function setSource($src, $dir = null)
{
if (!isset($src)) {
$this->imageSrc = null;
$this->pathToImage = null;
return $this;
}
@@ -1192,9 +1586,9 @@ class CImage
$src = basename($src);
}
$this->imageSrc = ltrim($src, '/');
$this->imageFolder = rtrim($dir, '/');
$this->pathToImage = $this->imageFolder . '/' . $this->imageSrc;
$this->imageSrc = ltrim($src, '/');
$imageFolder = rtrim($dir, '/');
$this->pathToImage = $imageFolder . '/' . $this->imageSrc;
return $this;
}
@@ -1205,23 +1599,23 @@ class CImage
* Set target file.
*
* @param string $src of target image.
* @param string $dir as base directory where images are stored.
* @param string $dir as optional base directory where images are stored.
* Uses $this->saveFolder if null.
*
* @return $this
*/
public function setTarget($src = null, $dir = null)
{
if (!(isset($src) && isset($dir))) {
if (!isset($src)) {
$this->cacheFileName = null;
return $this;
}
$this->saveFolder = $dir;
$this->cacheFileName = $dir . '/' . $src;
if (isset($dir)) {
$this->saveFolder = rtrim($dir, '/');
}
/* Allow readonly cache
is_writable($this->saveFolder)
or $this->raiseError('Target directory is not writable.');
*/
$this->cacheFileName = $this->saveFolder . '/' . $src;
// Sanitize filename
$this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName);
@@ -1232,6 +1626,18 @@ class CImage
/**
* Get filename of target file.
*
* @return Boolean|String as filename of target or false if not set.
*/
public function getTarget()
{
return $this->cacheFileName;
}
/**
* Set options to use when processing image.
*
@@ -1279,11 +1685,6 @@ class CImage
// Output format
'outputFormat' => null,
'dpr' => 1,
// Options for saving
//'quality' => null,
//'compress' => null,
//'saveAs' => null,
);
// Convert crop settings from string to array
@@ -1777,13 +2178,15 @@ class CImage
/**
* Generate filename to save file in cache.
*
* @param string $base as basepath for storing file.
* @param string $base as optional basepath for storing file.
* @param boolean $useSubdir use or skip the subdir part when creating the
* filename.
*
* @return $this
*/
public function generateFilename($base)
public function generateFilename($base = null, $useSubdir = true)
{
$parts = pathinfo($this->pathToImage);
$filename = basename($this->pathToImage);
$cropToFit = $this->cropToFit ? '_cf' : null;
$fillToFit = $this->fillToFit ? '_ff' : null;
$crop_x = $this->crop_x ? "_x{$this->crop_x}" : null;
@@ -1812,7 +2215,7 @@ class CImage
if (is_array($filter)) {
$filters .= "_f{$filter['id']}";
for ($i=1; $i<=$filter['argc']; $i++) {
$filters .= ":".$filter["arg{$i}"];
$filters .= "-".$filter["arg{$i}"];
}
}
}
@@ -1825,16 +2228,6 @@ class CImage
$autoRotate = $this->autoRotate ? 'ar' : null;
$this->extension = isset($this->extension)
? $this->extension
: (isset($parts['extension'])
? $parts['extension']
: null);
$extension = empty($this->extension)
? null
: "." . $this->extension;
$optimize = $this->jpegOptimize ? 'o' : null;
$optimize .= $this->pngFilter ? 'f' : null;
$optimize .= $this->pngDeflate ? 'd' : null;
@@ -1849,14 +2242,20 @@ class CImage
$upscale = '_nu';
}
$subdir = str_replace('/', '-', dirname($this->imageSrc));
$subdir = ($subdir == '.') ? '_.' : $subdir;
$file = $subdir . '_' . $parts['filename'] . '_' . $width . '_'
$subdir = null;
if ($useSubdir === true) {
$subdir = str_replace('/', '-', dirname($this->imageSrc));
$subdir = ($subdir == '.') ? '_.' : $subdir;
$subdir .= '_';
}
$file = $subdir . $filename . '_' . $width . '_'
. $height . $offset . $crop . $cropToFit . $fillToFit
. $crop_x . $crop_y . $upscale
. $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize
. $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve
. $extension;
. $quality . $filters . $sharpen . $emboss . $blur . $palette
. $optimize . $compress
. $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor
. $convolve;
return $this->setTarget($file, $base);
}
@@ -2624,7 +3023,10 @@ class CImage
imagealphablending($img, false);
imagesavealpha($img, true);
$index = imagecolortransparent($this->image);
$index = $this->image
? imagecolortransparent($this->image)
: -1;
if ($index != -1) {
imagealphablending($img, true);
@@ -2697,17 +3099,23 @@ class CImage
/**
* Save image.
*
* @param string $src as target filename.
* @param string $base as base directory where to store images.
* @param string $src as target filename.
* @param string $base as base directory where to store images.
* @param boolean $overwrite or not, default to always overwrite file.
*
* @return $this or false if no folder is set.
*/
public function save($src = null, $base = null)
public function save($src = null, $base = null, $overwrite = true)
{
if (isset($src)) {
$this->setTarget($src, $base);
}
if ($overwrite === false && is_file($this->cacheFileName)) {
$this->Log("Not overwriting file since its already exists and \$overwrite if false.");
return;
}
is_writable($this->saveFolder)
or $this->raiseError('Target directory is not writable.');
@@ -2848,6 +3256,10 @@ class CImage
header('Content-type: application/json');
echo $this->json($file);
exit;
} elseif ($format == 'ascii') {
header('Content-type: text/plain');
echo $this->ascii($file);
exit;
}
$this->log("Outputting image: $file");
@@ -2939,6 +3351,38 @@ class CImage
/**
* Set options for creating ascii version of image.
*
* @param array $options empty to use default or set options to change.
*
* @return void.
*/
public function setAsciiOptions($options = array())
{
$this->asciiOptions = $options;
}
/**
* Create an ASCII version from the image details.
*
* @param string $file the file to output.
*
* @return string ASCII representation of the image.
*/
public function ascii($file = null)
{
$file = $file ? $file : $this->cacheFileName;
$asciiArt = new CAsciiArt();
$asciiArt->setOptions($this->asciiOptions);
return $asciiArt->createFromFile($file);
}
/**
* Log an event if verbose mode.
*
@@ -3028,70 +3472,6 @@ EOD;
/**
* Act as whitelist (or blacklist).
*
*/
class CWhitelist
{
/**
* Array to contain the whitelist options.
*/
private $whitelist = array();
/**
* Set the whitelist from an array of strings, each item in the
* whitelist should be a regexp without the surrounding / or #.
*
* @param array $whitelist with all valid options,
* default is to clear the whitelist.
*
* @return $this
*/
public function set($whitelist = array())
{
if (!is_array($whitelist)) {
throw new Exception("Whitelist is not of a supported format.");
}
$this->whitelist = $whitelist;
return $this;
}
/**
* Check if item exists in the whitelist.
*
* @param string $item string to check.
* @param array $whitelist optional with all valid options, default is null.
*
* @return boolean true if item is in whitelist, else false.
*/
public function check($item, $whitelist = null)
{
if ($whitelist !== null) {
$this->set($whitelist);
}
if (empty($item) or empty($this->whitelist)) {
return false;
}
foreach ($this->whitelist as $regexp) {
if (preg_match("#$regexp#", $item)) {
return true;
}
}
return false;
}
}
/**
* Resize and crop images on the fly, store generated images in a cache.
*
@@ -3101,12 +3481,12 @@ class CWhitelist
*
*/
$version = "v0.7.1 (2015-07-25)";
$version = "v0.7.6 (2015-10-18)";
/**
* Default configuration options, can be overridden in own config-file.
* Display error message.
*
* @param string $msg to display.
*
@@ -3466,10 +3846,20 @@ $imagePath = getConfig('image_path', __DIR__ . '/img/');
$imagePathConstraint = getConfig('image_path_constraint', true);
$validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#');
// Dummy image feature
$dummyEnabled = getConfig('dummy_enabled', true);
$dummyFilename = getConfig('dummy_filename', 'dummy');
$dummyImage = false;
preg_match($validFilename, $srcImage)
or errorPage('Filename contains invalid characters.');
if ($allowRemote && $img->isRemoteSource($srcImage)) {
if ($dummyEnabled && $srcImage === $dummyFilename) {
// Prepare to create a dummy image and use it as the source image.
$dummyImage = true;
} elseif ($allowRemote && $img->isRemoteSource($srcImage)) {
// If source is a remote file, ignore local file checks.
@@ -3847,11 +4237,51 @@ verbose("filters = " . print_r($filters, 1));
/**
* json - output the image as a JSON object with details on the image.
* json - output the image as a JSON object with details on the image.
* ascii - output the image as ASCII art.
*/
$outputFormat = getDefined('json', 'json', null);
$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
verbose("outputformat = $outputFormat");
if ($outputFormat == 'ascii') {
$defaultOptions = getConfig(
'ascii-options',
array(
"characterSet" => 'two',
"scale" => 14,
"luminanceStrategy" => 3,
"customCharacterSet" => null,
)
);
$options = get('ascii');
$options = explode(',', $options);
if (isset($options[0]) && !empty($options[0])) {
$defaultOptions['characterSet'] = $options[0];
}
if (isset($options[1]) && !empty($options[1])) {
$defaultOptions['scale'] = $options[1];
}
if (isset($options[2]) && !empty($options[2])) {
$defaultOptions['luminanceStrategy'] = $options[2];
}
if (count($options) > 3) {
// Last option is custom character string
unset($options[0]);
unset($options[1]);
unset($options[2]);
$characterString = implode($options);
$defaultOptions['customCharacterSet'] = $characterString;
}
$img->setAsciiOptions($defaultOptions);
}
verbose("json = $outputFormat");
@@ -3986,9 +4416,47 @@ if ($verboseFile) {
/**
* Load, process and output the image
*/
/**
* Set basic options for image processing.
*/
/**
* Prepare a dummy image and use it as source image.
*/
if ($dummyImage === true) {
$dummyDir = getConfig('dummy_dir', $cachePath. "/" . $dummyFilename);
is_writable($dummyDir)
or verbose("dummy dir not writable = $dummyDir");
$img->setSaveFolder($dummyDir)
->setSource($dummyFilename, $dummyDir)
->setOptions(
array(
'newWidth' => $newWidth,
'newHeight' => $newHeight,
'bgColor' => $bgColor,
)
)
->setJpegQuality($quality)
->setPngCompression($compress)
->createDummyImage()
->generateFilename(null, false)
->save(null, null, false);
$srcImage = $img->getTarget();
$imagePath = null;
verbose("src (updated) = $srcImage");
}
/**
* Load, process and output the image
*/
$img->log("Incoming arguments: " . print_r(verbose(), 1))
->setSaveFolder($cachePath)
->useCache($useCache)

View File

@@ -0,0 +1,36 @@
<?php
// Include config for all testcases
include __DIR__ . "/config.php";
// The title of the test case
$title = "Testing issue 100 - Dummy images";
// Provide a short description of the testcase.
$description = "Create dummy images.";
// Use these images in the test
$images = array(
'dummy',
);
// For each image, apply these testcases
$testcase = array(
'&nc&so',
'&nc&width=300',
'&nc&height=300',
'&nc&width=300&height=300',
'&nc&bgc=006600',
);
// Apply testcases and present results
include __DIR__ . "/template.php";