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