<?php
/**
- * @copyright Copyright (C) 2010-2022, the Friendica project
+ * @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
use Friendica\DI;
use Friendica\Util\Images;
use Imagick;
+use ImagickDraw;
use ImagickPixel;
+use GDImage;
+use kornrunner\Blurhash\Blurhash;
/**
* Class to handle images
*/
class Image
{
- /** @var Imagick|resource */
+ /** @var GDImage|Imagick|resource */
private $image;
/*
}
$this->type = $type;
- if ($this->isImagick() && $this->loadData($data)) {
+ if ($this->isImagick() && (empty($data) || $this->loadData($data))) {
+ $this->valid = !empty($data);
return;
} else {
// Failed to load with Imagick, fallback
$this->image->setCompressionQuality($quality);
}
- // The 'width' and 'height' properties are only used by non-Imagick routines.
$this->width = $this->image->getImageWidth();
$this->height = $this->image->getImageHeight();
- $this->valid = true;
+ $this->valid = !empty($this->image);
- return true;
+ return $this->valid;
}
$this->valid = false;
public function isValid(): bool
{
if ($this->isImagick()) {
- return ($this->image !== false);
+ return !empty($this->image);
}
return $this->valid;
}
return false;
}
- if ($this->isImagick()) {
- return $this->image->getImageWidth();
- }
return $this->width;
}
return false;
}
- if ($this->isImagick()) {
- return $this->image->getImageHeight();
- }
return $this->height;
}
$width = $this->getWidth();
$height = $this->getHeight();
- if ((! $width)|| (! $height)) {
- return false;
- }
-
- if ($width > $max && $height > $max) {
- // very tall image (greater than 16:9)
- // constrain the width - let the height float.
-
- if ((($height * 9) / 16) > $width) {
- $dest_width = $max;
- $dest_height = intval(($height * $max) / $width);
- } elseif ($width > $height) {
- // else constrain both dimensions
- $dest_width = $max;
- $dest_height = intval(($height * $max) / $width);
- } else {
- $dest_width = intval(($width * $max) / $height);
- $dest_height = $max;
- }
+ $scale = Images::getScalingDimensions($width, $height, $max);
+ if ($scale) {
+ return $this->scale($scale['width'], $scale['height']);
} else {
- if ($width > $max) {
- $dest_width = $max;
- $dest_height = intval(($height * $max) / $width);
- } else {
- if ($height > $max) {
- // very tall image (greater than 16:9)
- // but width is OK - don't do anything
-
- if ((($height * 9) / 16) > $width) {
- $dest_width = $width;
- $dest_height = $height;
- } else {
- $dest_width = intval(($width * $max) / $height);
- $dest_height = $max;
- }
- } else {
- $dest_width = $width;
- $dest_height = $height;
- }
- }
+ return false;
}
- return $this->scale($dest_width, $dest_height);
}
/**
do {
$this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate()
} while ($this->image->nextImage());
+
+ $this->width = $this->image->getImageWidth();
+ $this->height = $this->image->getImageHeight();
return;
}
$orientation = $this->image->getImageOrientation();
switch ($orientation) {
case Imagick::ORIENTATION_BOTTOMRIGHT:
- $this->image->rotateimage("#000", 180);
+ $this->rotate(180);
break;
case Imagick::ORIENTATION_RIGHTTOP:
- $this->image->rotateimage("#000", 90);
+ $this->rotate(-90);
break;
case Imagick::ORIENTATION_LEFTBOTTOM:
- $this->image->rotateimage("#000", -90);
+ $this->rotate(90);
break;
}
}
} while ($this->image->nextImage());
- // These may not be necessary anymore
$this->width = $this->image->getImageWidth();
$this->height = $this->image->getImageHeight();
} else {
if ($this->image) {
imagedestroy($this->image);
}
- $this->image = $dest;
+ $this->image = $dest;
$this->width = imagesx($this->image);
$this->height = imagesy($this->image);
try {
/* Clean it */
$this->image = $this->image->deconstructImages();
- $string = $this->image->getImagesBlob();
- return $string;
+ return $this->image->getImagesBlob();
} catch (Exception $e) {
return false;
}
}
- ob_start();
+ $stream = fopen('php://memory','r+');
// Enable interlacing
imageinterlace($this->image, true);
switch ($this->getType()) {
case 'image/png':
$quality = DI::config()->get('system', 'png_quality');
- imagepng($this->image, null, $quality);
+ imagepng($this->image, $stream, $quality);
break;
case 'image/jpeg':
case 'image/jpg':
$quality = DI::config()->get('system', 'jpeg_quality');
- imagejpeg($this->image, null, $quality);
+ imagejpeg($this->image, $stream, $quality);
break;
}
- $string = ob_get_contents();
- ob_end_clean();
+ rewind($stream);
+ return stream_get_contents($stream);
+ }
+
+ /**
+ * Create a blurhash out of a given image string
+ *
+ * @param string $img_str
+ * @return string
+ */
+ public function getBlurHash(): string
+ {
+ $image = New Image($this->asString());
+ if (empty($image) || !$this->isValid()) {
+ return '';
+ }
+
+ $width = $image->getWidth();
+ $height = $image->getHeight();
+
+ if (max($width, $height) > 90) {
+ $image->scaleDown(90);
+ $width = $image->getWidth();
+ $height = $image->getHeight();
+ }
+
+ if (empty($width) || empty($height)) {
+ return '';
+ }
+
+ $pixels = [];
+ for ($y = 0; $y < $height; ++$y) {
+ $row = [];
+ for ($x = 0; $x < $width; ++$x) {
+ if ($image->isImagick()) {
+ try {
+ $colors = $image->image->getImagePixelColor($x, $y)->getColor();
+ } catch (\Throwable $th) {
+ return '';
+ }
+ $row[] = [$colors['r'], $colors['g'], $colors['b']];
+ } else {
+ $index = imagecolorat($image->image, $x, $y);
+ $colors = @imagecolorsforindex($image->image, $index);
+ $row[] = [$colors['red'], $colors['green'], $colors['blue']];
+ }
+ }
+ $pixels[] = $row;
+ }
+
+ // The components define the amount of details (1 to 9).
+ $components_x = 9;
+ $components_y = 9;
+
+ return Blurhash::encode($pixels, $components_x, $components_y);
+ }
+
+ /**
+ * Create an image out of a blurhash
+ *
+ * @param string $blurhash
+ * @param integer $width
+ * @param integer $height
+ * @return void
+ */
+ public function getFromBlurHash(string $blurhash, int $width, int $height)
+ {
+ $scaled = Images::getScalingDimensions($width, $height, 90);
+ $pixels = Blurhash::decode($blurhash, $scaled['width'], $scaled['height']);
+
+ if ($this->isImagick()) {
+ $this->image = new Imagick();
+ $draw = new ImagickDraw();
+ $this->image->newImage($scaled['width'], $scaled['height'], '', 'png');
+ } else {
+ $this->image = imagecreatetruecolor($scaled['width'], $scaled['height']);
+ }
+
+ for ($y = 0; $y < $scaled['height']; ++$y) {
+ for ($x = 0; $x < $scaled['width']; ++$x) {
+ [$r, $g, $b] = $pixels[$y][$x];
+ if ($this->isImagick()) {
+ $draw->setFillColor("rgb($r, $g, $b)");
+ $draw->point($x, $y);
+ } else {
+ imagesetpixel($this->image, $x, $y, imagecolorallocate($this->image, $r, $g, $b));
+ }
+ }
+ }
+
+ if ($this->isImagick()) {
+ $this->image->drawImage($draw);
+ $this->width = $this->image->getImageWidth();
+ $this->height = $this->image->getImageHeight();
+ } else {
+ $this->width = imagesx($this->image);
+ $this->height = imagesy($this->image);
+ }
+
+ $this->valid = !empty($this->image);
- return $string;
+ $this->scaleUp(min($width, $height));
}
}