<?php
/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/
namespace Pimcore\Image\Adapter;
use Pimcore\Image\Adapter;
class GD extends Adapter
{
/**
* @var string
*/
protected $path;
/**
* @var resource|\GdImage|false
*/
protected $resource;
/**
* {@inheritdoc}
*/
public function load($imagePath, $options = [])
{
$this->path = $imagePath;
if (!$this->resource = @imagecreatefromstring(file_get_contents($this->path))) {
return false;
}
// set dimensions
list($width, $height) = getimagesize($this->path);
$this->setWidth($width);
$this->setHeight($height);
if (!$this->sourceImageFormat) {
$this->sourceImageFormat = \Pimcore\File::getFileExtension($imagePath);
}
if (in_array(\Pimcore\File::getFileExtension($imagePath), ['png', 'gif'])) {
// in GD only gif and PNG can have an alphachannel
$this->setIsAlphaPossible(true);
}
$this->setModified(false);
return $this;
}
/**
* {@inheritdoc}
*/
public function getContentOptimizedFormat()
{
$format = 'pjpeg';
if ($this->hasAlphaChannel()) {
$format = 'png';
}
return $format;
}
/**
* {@inheritdoc}
*/
public function save($path, $format = null, $quality = null)
{
if (!$format || $format == 'png32') {
$format = 'png';
}
if ($format == 'original') {
$format = $this->sourceImageFormat;
}
$format = strtolower($format);
// progressive jpeg
if ($format == 'pjpeg') {
imageinterlace($this->resource, true);
$format = 'jpeg';
}
if ($format == 'jpg') {
$format = 'jpeg';
}
$functionName = 'image' . $format;
if (!function_exists($functionName)) {
$functionName = 'imagepng';
}
// always create a PNG24
if ($format == 'png') {
imagesavealpha($this->resource, true);
}
if ($functionName === 'imagejpeg' || $functionName === 'imagewebp') {
$functionName($this->resource, $path, $quality);
} else {
$functionName($this->resource, $path);
}
return $this;
}
/**
* @return bool
*/
private function hasAlphaChannel()
{
if ($this->isAlphaPossible) {
$width = imagesx($this->resource); // Get the width of the image
$height = imagesy($this->resource); // Get the height of the image
// We run the image pixel by pixel and as soon as we find a transparent pixel we stop and return true.
for ($i = 0; $i < $width; $i++) {
for ($j = 0; $j < $height; $j++) {
$rgba = imagecolorat($this->resource, $i, $j);
if (($rgba & 0x7F000000) >> 24) {
return true;
}
}
}
}
// If we dont find any pixel the function will return false.
return false;
}
/**
* {@inheritdoc}
*/
protected function destroy()
{
if ($this->resource) {
imagedestroy($this->resource);
}
}
/**
* @param int $width
* @param int $height
*
* @return \GdImage|false
*/
private function createImage($width, $height)
{
$newImg = imagecreatetruecolor($width, $height);
imagesavealpha($newImg, true);
imagealphablending($newImg, false);
$trans_colour = imagecolorallocatealpha($newImg, 0, 0, 0, 127);
imagefill($newImg, 0, 0, $trans_colour);
return $newImg;
}
/**
* {@inheritdoc}
*/
public function resize($width, $height)
{
$this->preModify();
$newImg = $this->createImage($width, $height);
imagecopyresampled($newImg, $this->resource, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
$this->resource = $newImg;
$this->setWidth($width);
$this->setHeight($height);
$this->postModify();
return $this;
}
/**
* {@inheritdoc}
*/
public function crop($x, $y, $width, $height)
{
$this->preModify();
$x = min($this->getWidth(), max(0, $x));
$y = min($this->getHeight(), max(0, $y));
$width = min($width, $this->getWidth() - $x);
$height = min($height, $this->getHeight() - $y);
$new_img = $this->createImage($width, $height);
imagecopy($new_img, $this->resource, 0, 0, $x, $y, $width, $height);
$this->resource = $new_img;
$this->setWidth($width);
$this->setHeight($height);
$this->postModify();
return $this;
}
/**
* {@inheritdoc}
*/
public function frame($width, $height, $forceResize = false)
{
$this->preModify();
$this->contain($width, $height, $forceResize);
$x = ($width - $this->getWidth()) / 2;
$y = ($height - $this->getHeight()) / 2;
$newImage = $this->createImage($width, $height);
imagecopy($newImage, $this->resource, $x, $y, 0, 0, $this->getWidth(), $this->getHeight());
$this->resource = $newImage;
$this->setWidth($width);
$this->setHeight($height);
$this->postModify();
$this->setIsAlphaPossible(true);
return $this;
}
/**
* {@inheritdoc}
*/
public function setBackgroundColor($color)
{
$this->preModify();
list($r, $g, $b) = $this->colorhex2colorarray($color);
// just imagefill() on the existing image doesn't work, so we have to create a new image, fill it and then merge
// the source image with the background-image together
$newImg = imagecreatetruecolor($this->getWidth(), $this->getHeight());
$color = imagecolorallocate($newImg, $r, $g, $b);
imagefill($newImg, 0, 0, $color);
imagecopy($newImg, $this->resource, 0, 0, 0, 0, $this->getWidth(), $this->getHeight());
$this->resource = $newImg;
$this->postModify();
$this->setIsAlphaPossible(false);
return $this;
}
/**
* {@inheritdoc}
*/
public function setBackgroundImage($image, $mode = null)
{
$this->preModify();
$image = ltrim($image, '/');
$image = PIMCORE_WEB_ROOT . '/' . $image;
if (is_file($image)) {
$backgroundImage = imagecreatefromstring(file_get_contents($image));
list($backgroundImageWidth, $backgroundImageHeight) = getimagesize($image);
$newImg = $this->createImage($this->getWidth(), $this->getHeight());
if ($mode == 'cropTopLeft') {
imagecopyresampled($newImg, $backgroundImage, 0, 0, 0, 0, $this->getWidth(), $this->getHeight(), $this->getWidth(), $this->getHeight());
} elseif ($mode == 'asTexture') {
imagesettile($newImg, $backgroundImage);
imagefilledrectangle($newImg, 0, 0, $this->getWidth(), $this->getHeight(), IMG_COLOR_TILED);
} else {
// default behavior (fit)
imagecopyresampled($newImg, $backgroundImage, 0, 0, 0, 0, $this->getWidth(), $this->getHeight(), $backgroundImageWidth, $backgroundImageHeight);
}
imagealphablending($newImg, true);
imagecopyresampled($newImg, $this->resource, 0, 0, 0, 0, $this->getWidth(), $this->getHeight(), $this->getWidth(), $this->getHeight());
$this->resource = $newImg;
}
$this->postModify();
return $this;
}
/**
* {@inheritdoc}
*/
public function grayscale()
{
$this->preModify();
imagefilter($this->resource, IMG_FILTER_GRAYSCALE);
$this->postModify();
return $this;
}
/**
* {@inheritdoc}
*/
public function sepia()
{
$this->preModify();
imagefilter($this->resource, IMG_FILTER_GRAYSCALE);
imagefilter($this->resource, IMG_FILTER_COLORIZE, 100, 50, 0);
$this->postModify();
return $this;
}
/**
* {@inheritdoc}
*/
public function addOverlay($image, $x = 0, $y = 0, $alpha = 100, $composite = 'COMPOSITE_DEFAULT', $origin = 'top-left')
{
$this->preModify();
$image = ltrim($image, '/');
$image = PIMCORE_PROJECT_ROOT . '/' . $image;
if (is_file($image)) {
list($oWidth, $oHeight) = getimagesize($image);
if ($origin === 'top-right') {
$x = $this->getWidth() - $oWidth - $x;
} elseif ($origin === 'bottom-left') {
$y = $this->getHeight() - $oHeight - $y;
} elseif ($origin === 'bottom-right') {
$x = $this->getWidth() - $oWidth - $x;
$y = $this->getHeight() - $oHeight - $y;
} elseif ($origin === 'center') {
$x = round($this->getWidth() / 2) - round($oWidth / 2) + $x;
$y = round($this->getHeight() / 2) - round($oHeight / 2) + $y;
}
$overlay = imagecreatefromstring(file_get_contents($image));
imagealphablending($this->resource, true);
imagecopyresampled($this->resource, $overlay, $x, $y, 0, 0, $oWidth, $oHeight, $oWidth, $oHeight);
}
$this->postModify();
return $this;
}
/**
* {@inheritdoc}
*/
public function mirror($mode)
{
$this->preModify();
if ($mode == 'vertical') {
imageflip($this->resource, IMG_FLIP_VERTICAL);
} elseif ($mode == 'horizontal') {
imageflip($this->resource, IMG_FLIP_HORIZONTAL);
}
$this->postModify();
return $this;
}
/**
* {@inheritdoc}
*/
public function rotate($angle)
{
$this->preModify();
$angle = 360 - $angle;
$this->resource = imagerotate($this->resource, $angle, imagecolorallocatealpha($this->resource, 0, 0, 0, 127));
$this->setWidth(imagesx($this->resource));
$this->setHeight(imagesy($this->resource));
$this->postModify();
$this->setIsAlphaPossible(true);
return $this;
}
protected static $supportedFormatsCache = [];
/**
* {@inheritdoc}
*/
public function supportsFormat(string $format, bool $force = false)
{
if (!isset(self::$supportedFormatsCache[$format]) || $force) {
$info = gd_info();
$mappings = [
'jpg' => 'JPEG Support',
'jpeg' => 'JPEG Support',
'pjpeg' => 'JPEG Support',
'webp' => 'WebP Support',
'gif' => 'GIF Create Support',
'png' => 'PNG Support',
];
if (isset($mappings[$format]) && isset($info[$mappings[$format]]) && $info[$mappings[$format]]) {
self::$supportedFormatsCache[$format] = true;
} else {
self::$supportedFormatsCache[$format] = false;
}
}
return self::$supportedFormatsCache[$format];
}
}