[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/splitbrain/slika/src/ -> GdAdapter.php (source)

   1  <?php /** @noinspection PhpComposerExtensionStubsInspection */
   2  
   3  
   4  namespace splitbrain\slika;
   5  
   6  /**
   7   * Image processing adapter for PHP's libGD
   8   */
   9  class GdAdapter extends Adapter
  10  {
  11      /** @var resource libGD image */
  12      protected $image;
  13      /** @var int width of the current image */
  14      protected $width = 0;
  15      /** @var int height of the current image */
  16      protected $height = 0;
  17      /** @var string the extension of the file we're working with */
  18      protected $extension;
  19  
  20  
  21      /** @inheritDoc */
  22      public function __construct($imagepath, $options = [])
  23      {
  24          parent::__construct($imagepath, $options);
  25          $this->image = $this->loadImage($imagepath);
  26      }
  27  
  28      /**
  29       * Clean up
  30       */
  31      public function __destruct()
  32      {
  33          if (is_resource($this->image)) {
  34              imagedestroy($this->image);
  35          }
  36      }
  37  
  38      /** @inheritDoc
  39       * @throws Exception
  40       * @link https://gist.github.com/EionRobb/8e0c76178522bc963c75caa6a77d3d37#file-imagecreatefromstring_autorotate-php-L15
  41       */
  42      public function autorotate()
  43      {
  44          if ($this->extension !== 'jpeg') {
  45              return $this;
  46          }
  47  
  48          $orientation = 1;
  49  
  50          if (function_exists('exif_read_data')) {
  51              // use PHP's exif capablities
  52              $exif = exif_read_data($this->imagepath);
  53              if (!empty($exif['Orientation'])) {
  54                  $orientation = $exif['Orientation'];
  55              }
  56          } else {
  57              // grep the exif info from the raw contents
  58              // we read only the first 70k bytes
  59              $data = file_get_contents($this->imagepath, false, null, 0, 70000);
  60              if (preg_match('@\x12\x01\x03\x00\x01\x00\x00\x00(.)\x00\x00\x00@', $data, $matches)) {
  61                  // Little endian EXIF
  62                  $orientation = ord($matches[1]);
  63              } else if (preg_match('@\x01\x12\x00\x03\x00\x00\x00\x01\x00(.)\x00\x00@', $data, $matches)) {
  64                  // Big endian EXIF
  65                  $orientation = ord($matches[1]);
  66              }
  67          }
  68  
  69          return $this->rotate($orientation);
  70      }
  71  
  72      /**
  73       * @inheritDoc
  74       * @throws Exception
  75       */
  76      public function rotate($orientation)
  77      {
  78          $orientation = (int)$orientation;
  79          if ($orientation < 0 || $orientation > 8) {
  80              throw new Exception('Unknown rotation given');
  81          }
  82  
  83          if ($orientation <= 1) {
  84              // no rotation wanted
  85              return $this;
  86          }
  87  
  88          // fill color
  89          $transparency = imagecolorallocatealpha($this->image, 0, 0, 0, 127);
  90  
  91          // rotate
  92          if (in_array($orientation, [3, 4])) {
  93              $image = imagerotate($this->image, 180, $transparency, 1);
  94          }
  95          if (in_array($orientation, [5, 6])) {
  96              $image = imagerotate($this->image, -90, $transparency, 1);
  97              list($this->width, $this->height) = [$this->height, $this->width];
  98          } elseif (in_array($orientation, [7, 8])) {
  99              $image = imagerotate($this->image, 90, $transparency, 1);
 100              list($this->width, $this->height) = [$this->height, $this->width];
 101          }
 102          /** @var resource $image is now defined */
 103  
 104          // additionally flip
 105          if (in_array($orientation, [2, 5, 7, 4])) {
 106              imageflip($image, IMG_FLIP_HORIZONTAL);
 107          }
 108  
 109          imagedestroy($this->image);
 110          $this->image = $image;
 111  
 112          //keep png alpha channel if possible
 113          if ($this->extension == 'png' && function_exists('imagesavealpha')) {
 114              imagealphablending($this->image, false);
 115              imagesavealpha($this->image, true);
 116          }
 117  
 118          return $this;
 119      }
 120  
 121      /**
 122       * @inheritDoc
 123       * @throws Exception
 124       */
 125      public function resize($width, $height)
 126      {
 127          list($width, $height) = $this->boundingBox($width, $height);
 128          $this->resizeOperation($width, $height);
 129          return $this;
 130      }
 131  
 132      /**
 133       * @inheritDoc
 134       * @throws Exception
 135       */
 136      public function crop($width, $height)
 137      {
 138          list($this->width, $this->height, $offsetX, $offsetY) = $this->cropPosition($width, $height);
 139          $this->resizeOperation($width, $height, $offsetX, $offsetY);
 140          return $this;
 141      }
 142  
 143      /**
 144       * @inheritDoc
 145       * @throws Exception
 146       */
 147      public function save($path, $extension = '')
 148      {
 149          if ($extension === 'jpg') {
 150              $extension = 'jpeg';
 151          }
 152          if ($extension === '') {
 153              $extension = $this->extension;
 154          }
 155          $saver = 'image' . $extension;
 156          if (!function_exists($saver)) {
 157              throw new Exception('Can not save image format ' . $extension);
 158          }
 159  
 160          if ($extension == 'jpeg') {
 161              imagejpeg($this->image, $path, $this->options['quality']);
 162          } else {
 163              $saver($this->image, $path);
 164          }
 165  
 166          imagedestroy($this->image);
 167      }
 168  
 169      /**
 170       * Initialize libGD on the given image
 171       *
 172       * @param string $path
 173       * @return resource
 174       * @throws Exception
 175       */
 176      protected function loadImage($path)
 177      {
 178          // Figure out the file info
 179          $info = getimagesize($path);
 180          if ($info === false) {
 181              throw new Exception('Failed to read image information');
 182          }
 183          $this->width = $info[0];
 184          $this->height = $info[1];
 185  
 186          // what type of image is it?
 187          $this->extension = image_type_to_extension($info[2], false);
 188          $creator = 'imagecreatefrom' . $this->extension;
 189          if (!function_exists($creator)) {
 190              throw new Exception('Can not work with image format ' . $this->extension);
 191          }
 192  
 193          // create the GD instance
 194          $image = @$creator($path);
 195  
 196          if ($image === false) {
 197              throw new Exception('Failed to load image wiht libGD');
 198          }
 199  
 200          return $image;
 201      }
 202  
 203      /**
 204       * Creates a new blank image to which we can copy
 205       *
 206       * Tries to set up alpha/transparency stuff correctly
 207       *
 208       * @param int $width
 209       * @param int $height
 210       * @return resource
 211       * @throws Exception
 212       */
 213      protected function createImage($width, $height)
 214      {
 215          // create a canvas to copy to, use truecolor if possible (except for gif)
 216          $canvas = false;
 217          if (function_exists('imagecreatetruecolor') && $this->extension != 'gif') {
 218              $canvas = @imagecreatetruecolor($width, $height);
 219          }
 220          if (!$canvas) {
 221              $canvas = @imagecreate($width, $height);
 222          }
 223          if (!$canvas) {
 224              throw new Exception('Failed to create new canvas');
 225          }
 226  
 227          //keep png alpha channel if possible
 228          if ($this->extension == 'png' && function_exists('imagesavealpha')) {
 229              imagealphablending($canvas, false);
 230              imagesavealpha($canvas, true);
 231          }
 232  
 233          //keep gif transparent color if possible
 234          if ($this->extension == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) {
 235              if (function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) {
 236                  $transcolorindex = @imagecolortransparent($this->image);
 237                  if ($transcolorindex >= 0) { //transparent color exists
 238                      $transcolor = @imagecolorsforindex($this->image, $transcolorindex);
 239                      $transcolorindex = @imagecolorallocate(
 240                          $canvas,
 241                          $transcolor['red'],
 242                          $transcolor['green'],
 243                          $transcolor['blue']
 244                      );
 245                      @imagefill($canvas, 0, 0, $transcolorindex);
 246                      @imagecolortransparent($canvas, $transcolorindex);
 247                  } else { //filling with white
 248                      $whitecolorindex = @imagecolorallocate($canvas, 255, 255, 255);
 249                      @imagefill($canvas, 0, 0, $whitecolorindex);
 250                  }
 251              } else { //filling with white
 252                  $whitecolorindex = @imagecolorallocate($canvas, 255, 255, 255);
 253                  @imagefill($canvas, 0, 0, $whitecolorindex);
 254              }
 255          }
 256  
 257          return $canvas;
 258      }
 259  
 260      /**
 261       * Calculate new size
 262       *
 263       * If widht and height are given, the new size will be fit within this bounding box.
 264       * If only one value is given the other is adjusted to match according to the aspect ratio
 265       *
 266       * @param int $width width of the bounding box
 267       * @param int $height height of the bounding box
 268       * @return array (width, height)
 269       * @throws Exception
 270       */
 271      protected function boundingBox($width, $height)
 272      {
 273          $width = $this->cleanDimension($width, $this->width);
 274          $height = $this->cleanDimension($height, $this->height);
 275  
 276          if ($width == 0 && $height == 0) {
 277              throw new Exception('You can not resize to 0x0');
 278          }
 279  
 280          if (!$height) {
 281              // adjust to match width
 282              $height = round(($width * $this->height) / $this->width);
 283          } else if (!$width) {
 284              // adjust to match height
 285              $width = round(($height * $this->width) / $this->height);
 286          } else {
 287              // fit into bounding box
 288              $scale = min($width / $this->width, $height / $this->height);
 289              $width = $this->width * $scale;
 290              $height = $this->height * $scale;
 291          }
 292  
 293          return [$width, $height];
 294      }
 295  
 296      /**
 297       * Ensure the given Dimension is a proper pixel value
 298       *
 299       * When a percentage is given, the value is calculated based on the given original dimension
 300       *
 301       * @param int|string $dim New Dimension
 302       * @param int $orig Original dimension
 303       * @return int
 304       */
 305      protected function cleanDimension($dim, $orig)
 306      {
 307          if ($dim && substr($dim, -1) == '%') {
 308              $dim = round($orig * ((float)$dim / 100));
 309          } else {
 310              $dim = (int)$dim;
 311          }
 312  
 313          return $dim;
 314      }
 315  
 316      /**
 317       * Calculates crop position
 318       *
 319       * Given the wanted final size, this calculates which exact area needs to be cut
 320       * from the original image to be then resized to the wanted dimensions.
 321       *
 322       * @param int $width
 323       * @param int $height
 324       * @return array (cropWidth, cropHeight, offsetX, offsetY)
 325       * @throws Exception
 326       */
 327      protected function cropPosition($width, $height)
 328      {
 329          if ($width == 0 && $height == 0) {
 330              throw new Exception('You can not crop to 0x0');
 331          }
 332  
 333          if (!$height) {
 334              $height = $width;
 335          }
 336  
 337          if (!$width) {
 338              $width = $height;
 339          }
 340  
 341          // calculate ratios
 342          $oldRatio = $this->width / $this->height;
 343          $newRatio = $width / $height;
 344  
 345          // calulate new size
 346          if ($newRatio >= 1) {
 347              if ($newRatio > $oldRatio) {
 348                  $cropWidth = $this->width;
 349                  $cropHeight = (int)($this->width / $newRatio);
 350              } else {
 351                  $cropWidth = (int)($this->height * $newRatio);
 352                  $cropHeight = $this->height;
 353              }
 354          } else {
 355              if ($newRatio < $oldRatio) {
 356                  $cropWidth = (int)($this->height * $newRatio);
 357                  $cropHeight = $this->height;
 358              } else {
 359                  $cropWidth = $this->width;
 360                  $cropHeight = (int)($this->width / $newRatio);
 361              }
 362          }
 363  
 364          // calculate crop offset
 365          $offsetX = (int)(($this->width - $cropWidth) / 2);
 366          $offsetY = (int)(($this->height - $cropHeight) / 2);
 367  
 368          return [$cropWidth, $cropHeight, $offsetX, $offsetY];
 369      }
 370  
 371      /**
 372       * resize or crop images using PHP's libGD support
 373       *
 374       * @param int $toWidth desired width
 375       * @param int $toHeight desired height
 376       * @param int $offsetX offset of crop centre
 377       * @param int $offsetY offset of crop centre
 378       * @throws Exception
 379       */
 380      protected function resizeOperation($toWidth, $toHeight, $offsetX = 0, $offsetY = 0)
 381      {
 382          $newimg = $this->createImage($toWidth, $toHeight);
 383  
 384          //try resampling first, fall back to resizing
 385          if (
 386              !function_exists('imagecopyresampled') ||
 387              !@imagecopyresampled(
 388                  $newimg,
 389                  $this->image,
 390                  0,
 391                  0,
 392                  $offsetX,
 393                  $offsetY,
 394                  $toWidth,
 395                  $toHeight,
 396                  $this->width,
 397                  $this->height
 398              )
 399          ) {
 400              imagecopyresized(
 401                  $newimg,
 402                  $this->image,
 403                  0,
 404                  0,
 405                  $offsetX,
 406                  $offsetY,
 407                  $toWidth,
 408                  $toHeight,
 409                  $this->width,
 410                  $this->height
 411              );
 412          }
 413  
 414          // destroy original GD image ressource and replace with new one
 415          imagedestroy($this->image);
 416          $this->image = $newimg;
 417          $this->width = $toWidth;
 418          $this->height = $toHeight;
 419      }
 420  
 421  }