dokuwiki/vendor/splitbrain/slika/src/GdAdapter.php

397 lines
12 KiB
PHP

<?php /** @noinspection PhpComposerExtensionStubsInspection */
namespace splitbrain\slika;
class GdAdapter extends Adapter
{
/** @var resource libGD image */
protected $image;
/** @var int width of the current image */
protected $width = 0;
/** @var int height of the current image */
protected $height = 0;
/** @var string the extension of the file we're working with */
protected $extension;
/** @inheritDoc */
public function __construct($imagepath, $options = [])
{
parent::__construct($imagepath, $options);
$this->image = $this->loadImage($imagepath);
}
/**
* Clean up
*/
public function __destruct()
{
if (is_resource($this->image)) {
imagedestroy($this->image);
}
}
/** @inheritDoc
* @throws Exception
* @link https://gist.github.com/EionRobb/8e0c76178522bc963c75caa6a77d3d37#file-imagecreatefromstring_autorotate-php-L15
*/
public function autorotate()
{
if ($this->extension !== 'jpeg') {
return $this;
}
$orientation = 1;
if (function_exists('exif_read_data')) {
// use PHP's exif capablities
$exif = exif_read_data($this->imagepath);
if (!empty($exif['Orientation'])) {
$orientation = $exif['Orientation'];
}
} else {
// grep the exif info from the raw contents
// we read only the first 70k bytes
$data = file_get_contents($this->imagepath, false, null, 0, 70000);
if (preg_match('@\x12\x01\x03\x00\x01\x00\x00\x00(.)\x00\x00\x00@', $data, $matches)) {
// Little endian EXIF
$orientation = ord($matches[1]);
} else if (preg_match('@\x01\x12\x00\x03\x00\x00\x00\x01\x00(.)\x00\x00@', $data, $matches)) {
// Big endian EXIF
$orientation = ord($matches[1]);
}
}
return $this->rotate($orientation);
}
/**
* @inheritDoc
* @throws Exception
*/
public function rotate($orientation)
{
$orientation = (int)$orientation;
if ($orientation < 0 || $orientation > 8) {
throw new Exception('Unknown rotation given');
}
if ($orientation <= 1) {
// no rotation wanted
return $this;
}
// fill color
$transparency = imagecolorallocatealpha($this->image, 0, 0, 0, 127);
// rotate
if (in_array($orientation, [3, 4])) {
$image = imagerotate($this->image, 180, $transparency, 1);
}
if (in_array($orientation, [5, 6])) {
$image = imagerotate($this->image, -90, $transparency, 1);
list($this->width, $this->height) = [$this->height, $this->width];
} elseif (in_array($orientation, [7, 8])) {
$image = imagerotate($this->image, 90, $transparency, 1);
list($this->width, $this->height) = [$this->height, $this->width];
}
/** @var resource $image is now defined */
// additionally flip
if (in_array($orientation, [2, 5, 7, 4])) {
imageflip($image, IMG_FLIP_HORIZONTAL);
}
imagedestroy($this->image);
$this->image = $image;
//keep png alpha channel if possible
if ($this->extension == 'png' && function_exists('imagesavealpha')) {
imagealphablending($this->image, false);
imagesavealpha($this->image, true);
}
return $this;
}
/**
* @inheritDoc
* @throws Exception
*/
public function resize($width, $height)
{
list($width, $height) = $this->boundingBox($width, $height);
$this->resizeOperation($width, $height);
return $this;
}
/**
* @inheritDoc
* @throws Exception
*/
public function crop($width, $height)
{
list($this->width, $this->height, $offsetX, $offsetY) = $this->cropPosition($width, $height);
$this->resizeOperation($width, $height, $offsetX, $offsetY);
return $this;
}
/**
* @inheritDoc
* @throws Exception
*/
public function save($path, $extension = '')
{
if ($extension === 'jpg') {
$extension = 'jpeg';
}
if ($extension === '') {
$extension = $this->extension;
}
$saver = 'image' . $extension;
if (!function_exists($saver)) {
throw new Exception('Can not save image format ' . $extension);
}
if ($extension == 'jpeg') {
imagejpeg($this->image, $path, $this->options['quality']);
} else {
$saver($this->image, $path);
}
imagedestroy($this->image);
}
/**
* Initialize libGD on the given image
*
* @param string $path
* @return resource
* @throws Exception
*/
protected function loadImage($path)
{
// Figure out the file info
$info = getimagesize($path);
if ($info === false) {
throw new Exception('Failed to read image information');
}
$this->width = $info[0];
$this->height = $info[1];
// what type of image is it?
$this->extension = image_type_to_extension($info[2], false);
$creator = 'imagecreatefrom' . $this->extension;
if (!function_exists($creator)) {
throw new Exception('Can not work with image format ' . $this->extension);
}
// create the GD instance
$image = @$creator($path);
if ($image === false) {
throw new Exception('Failed to load image wiht libGD');
}
return $image;
}
/**
* Creates a new blank image to which we can copy
*
* Tries to set up alpha/transparency stuff correctly
*
* @param int $width
* @param int $height
* @return resource
* @throws Exception
*/
protected function createImage($width, $height)
{
// create a canvas to copy to, use truecolor if possible (except for gif)
$canvas = false;
if (function_exists('imagecreatetruecolor') && $this->extension != 'gif') {
$canvas = @imagecreatetruecolor($width, $height);
}
if (!$canvas) {
$canvas = @imagecreate($width, $height);
}
if (!$canvas) {
throw new Exception('Failed to create new canvas');
}
//keep png alpha channel if possible
if ($this->extension == 'png' && function_exists('imagesavealpha')) {
imagealphablending($canvas, false);
imagesavealpha($canvas, true);
}
//keep gif transparent color if possible
if ($this->extension == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) {
if (function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) {
$transcolorindex = @imagecolortransparent($this->image);
if ($transcolorindex >= 0) { //transparent color exists
$transcolor = @imagecolorsforindex($this->image, $transcolorindex);
$transcolorindex = @imagecolorallocate(
$canvas,
$transcolor['red'],
$transcolor['green'],
$transcolor['blue']
);
@imagefill($canvas, 0, 0, $transcolorindex);
@imagecolortransparent($canvas, $transcolorindex);
} else { //filling with white
$whitecolorindex = @imagecolorallocate($canvas, 255, 255, 255);
@imagefill($canvas, 0, 0, $whitecolorindex);
}
} else { //filling with white
$whitecolorindex = @imagecolorallocate($canvas, 255, 255, 255);
@imagefill($canvas, 0, 0, $whitecolorindex);
}
}
return $canvas;
}
/**
* Calculate new size
*
* If widht and height are given, the new size will be fit within this bounding box.
* If only one value is given the other is adjusted to match according to the aspect ratio
*
* @param int $width width of the bounding box
* @param int $height height of the bounding box
* @return array (width, height)
* @throws Exception
*/
protected function boundingBox($width, $height)
{
if ($width == 0 && $height == 0) {
throw new Exception('You can not resize to 0x0');
}
if (!$height) {
// adjust to match width
$height = round(($width * $this->height) / $this->width);
} else if (!$width) {
// adjust to match height
$width = round(($height * $this->width) / $this->height);
} else {
// fit into bounding box
$scale = min($width / $this->width, $height / $this->height);
$width = $this->width * $scale;
$height = $this->height * $scale;
}
return [$width, $height];
}
/**
* Calculates crop position
*
* Given the wanted final size, this calculates which exact area needs to be cut
* from the original image to be then resized to the wanted dimensions.
*
* @param int $width
* @param int $height
* @return array (cropWidth, cropHeight, offsetX, offsetY)
* @throws Exception
*/
protected function cropPosition($width, $height)
{
if ($width == 0 && $height == 0) {
throw new Exception('You can not crop to 0x0');
}
if (!$height) {
$height = $width;
}
if (!$width) {
$width = $height;
}
// calculate ratios
$oldRatio = $this->width / $this->height;
$newRatio = $width / $height;
// calulate new size
if ($newRatio >= 1) {
if ($newRatio > $oldRatio) {
$cropWidth = $this->width;
$cropHeight = (int)($this->width / $newRatio);
} else {
$cropWidth = (int)($this->height * $newRatio);
$cropHeight = $this->height;
}
} else {
if ($newRatio < $oldRatio) {
$cropWidth = (int)($this->height * $newRatio);
$cropHeight = $this->height;
} else {
$cropWidth = $this->width;
$cropHeight = (int)($this->width / $newRatio);
}
}
// calculate crop offset
$offsetX = (int)(($this->width - $cropWidth) / 2);
$offsetY = (int)(($this->height - $cropHeight) / 2);
return [$cropWidth, $cropHeight, $offsetX, $offsetY];
}
/**
* resize or crop images using PHP's libGD support
*
* @param int $toWidth desired width
* @param int $toHeight desired height
* @param int $offsetX offset of crop centre
* @param int $offsetY offset of crop centre
* @throws Exception
*/
protected function resizeOperation($toWidth, $toHeight, $offsetX = 0, $offsetY = 0)
{
$newimg = $this->createImage($toWidth, $toHeight);
//try resampling first, fall back to resizing
if (
!function_exists('imagecopyresampled') ||
!@imagecopyresampled(
$newimg,
$this->image,
0,
0,
$offsetX,
$offsetY,
$toWidth,
$toHeight,
$this->width,
$this->height
)
) {
imagecopyresized(
$newimg,
$this->image,
0,
0,
$offsetX,
$offsetY,
$toWidth,
$toHeight,
$this->width,
$this->height
);
}
// destroy original GD image ressource and replace with new one
imagedestroy($this->image);
$this->image = $newimg;
$this->width = $toWidth;
$this->height = $toHeight;
}
}