"npm-asset/moment": "^2.24",
"npm-asset/perfect-scrollbar": "0.6.16",
"npm-asset/textcomplete": "^0.18.2",
- "npm-asset/typeahead.js": "^0.11.1"
+ "npm-asset/typeahead.js": "^0.11.1",
+ "kornrunner/blurhash": "^1.2"
},
"repositories": [
{
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "2e082bac083ca61cc0c22f7055d690bf",
+ "content-hash": "f8e7baec685d20e6aee56978c275d64c",
"packages": [
{
"name": "asika/simple-console",
],
"time": "2022-06-20T21:43:03+00:00"
},
+ {
+ "name": "kornrunner/blurhash",
+ "version": "v1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/kornrunner/php-blurhash.git",
+ "reference": "bc8a4596cb0a49874f0158696a382ab3933fefe4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/kornrunner/php-blurhash/zipball/bc8a4596cb0a49874f0158696a382ab3933fefe4",
+ "reference": "bc8a4596cb0a49874f0158696a382ab3933fefe4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3|^8.0"
+ },
+ "require-dev": {
+ "ext-gd": "*",
+ "ocramius/package-versions": "^1.4|^2.0",
+ "phpstan/phpstan": "^0.12",
+ "phpunit/phpunit": "^9",
+ "vimeo/psalm": "^4.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "kornrunner\\Blurhash\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Boris Momčilović",
+ "email": "boris.momcilovic@gmail.com"
+ }
+ ],
+ "description": "Pure PHP implementation of Blurhash",
+ "homepage": "https://github.com/kornrunner/php-blurhash",
+ "time": "2022-07-13T19:38:39+00:00"
+ },
{
"name": "league/html-to-markdown",
"version": "4.10.0",
-- ------------------------------------------
-- Friendica 2022.12-dev (Giant Rhubarb)
--- DB_UPDATE_VERSION 1496
+-- DB_UPDATE_VERSION 1497
-- ------------------------------------------
`height` smallint unsigned NOT NULL DEFAULT 0 COMMENT '',
`width` smallint unsigned NOT NULL DEFAULT 0 COMMENT '',
`datasize` int unsigned NOT NULL DEFAULT 0 COMMENT '',
+ `blurhash` varbinary(255) COMMENT 'BlurHash representation of the photo',
`data` mediumblob NOT NULL COMMENT '',
`scale` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`profile` boolean NOT NULL DEFAULT '0' COMMENT '',
`height` smallint unsigned COMMENT 'Height of the media',
`width` smallint unsigned COMMENT 'Width of the media',
`size` bigint unsigned COMMENT 'Media size',
+ `blurhash` varbinary(255) COMMENT 'BlurHash representation of the image',
`preview` varbinary(512) COMMENT 'Preview URL',
`preview-height` smallint unsigned COMMENT 'Height of the preview picture',
`preview-width` smallint unsigned COMMENT 'Width of the preview picture',
| height | | smallint unsigned | NO | | 0 | |
| width | | smallint unsigned | NO | | 0 | |
| datasize | | int unsigned | NO | | 0 | |
+| blurhash | BlurHash representation of the photo | varbinary(255) | YES | | NULL | |
| data | | mediumblob | NO | | NULL | |
| scale | | tinyint unsigned | NO | | 0 | |
| profile | | boolean | NO | | 0 | |
| height | Height of the media | smallint unsigned | YES | | NULL | |
| width | Width of the media | smallint unsigned | YES | | NULL | |
| size | Media size | bigint unsigned | YES | | NULL | |
+| blurhash | BlurHash representation of the image | varbinary(255) | YES | | NULL | |
| preview | Preview URL | varbinary(512) | YES | | NULL | |
| preview-height | Height of the preview picture | smallint unsigned | YES | | NULL | |
| preview-width | Width of the preview picture | smallint unsigned | YES | | NULL | |
*/
public function createFromPhoto(int $id): array
{
- $photo = Photo::selectFirst(['resource-id', 'uid', 'id', 'title', 'type', 'width', 'height'], ['id' => $id]);
+ $photo = Photo::selectFirst(['resource-id', 'uid', 'id', 'title', 'type', 'width', 'height', 'blurhash'], ['id' => $id]);
if (empty($photo)) {
return [];
}
'description' => $photo['title'],
'width' => $photo['width'],
'height' => $photo['height'],
+ 'blurhash' => $photo['blurhash'],
];
$photoTypes = Images::supportedTypes();
$data['image'] = $attached['preview'];
$data['width'] = $attached['preview-width'];
$data['height'] = $attached['preview-height'];
+ $data['blurhash'] = $attached['blurhash'];
}
}
*/
public static function discoverByUser(int $uid)
{
- $contact = Contact::selectFirst(['id', 'url'], ['uid' => $uid, 'self' => true]);
+ $contact = Contact::selectFirst(['id', 'url', 'network'], ['uid' => $uid, 'self' => true]);
if (empty($contact)) {
Logger::warning('Self contact for user not found', ['uid' => $uid]);
return;
'height' => $image->getHeight(),
'width' => $image->getWidth(),
'datasize' => strlen($image->asString()),
+ 'blurhash' => $image->getBlurHash(),
'data' => $data,
'scale' => $scale,
'photo-type' => $type,
*/
private static function unsetEmptyFields(array $media): array
{
- $fields = ['mimetype', 'height', 'width', 'size', 'preview', 'preview-height', 'preview-width', 'description'];
+ $fields = ['mimetype', 'height', 'width', 'size', 'preview', 'preview-height', 'preview-width', 'blurhash', 'description'];
foreach ($fields as $field) {
if (empty($media[$field])) {
unset($media[$field]);
$media['size'] = $imagedata['size'];
$media['width'] = $imagedata[0];
$media['height'] = $imagedata[1];
+ $media['blurhash'] = $imagedata['blurhash'] ?? null;
} else {
Logger::notice('No image data', ['media' => $media]);
}
$media['preview'] = $data['images'][0]['src'] ?? null;
$media['preview-height'] = $data['images'][0]['height'] ?? null;
$media['preview-width'] = $data['images'][0]['width'] ?? null;
+ $media['blurhash'] = $data['images'][0]['blurhash'] ?? null;
$media['description'] = $data['text'] ?? null;
$media['name'] = $data['title'] ?? null;
$media['author-url'] = $data['author_url'] ?? null;
$media['preview'] = null;
$media['preview-height'] = null;
$media['preview-width'] = null;
+ $media['blurhash'] = null;
$media['description'] = $item['body'];
$media['name'] = $item['title'];
$media['author-url'] = $item['author-link'];
$media['preview'] = null;
$media['preview-height'] = null;
$media['preview-width'] = null;
+ $media['blurhash'] = null;
$media['description'] = $contact['about'];
$media['name'] = $contact['name'];
$media['author-url'] = $contact['url'];
$media['size'] = $photo['datasize'];
$media['width'] = $photo['width'];
$media['height'] = $photo['height'];
+ $media['blurhash'] = $photo['blurhash'];
}
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['preview'] ?? '', $matches)) {
protected $text_url;
/** @var string */
protected $description;
+ /** @var string */
+ protected $blurhash;
/** @var array */
protected $meta;
$this->remote_url = $remote;
$this->text_url = $this->remote_url ?? $this->url;
$this->description = $attachment['description'];
+ $this->blurhash = $attachment['blurhash'];
if ($type === 'image') {
if ((int) $attachment['width'] > 0 && (int) $attachment['height'] > 0) {
$this->meta['original']['width'] = (int) $attachment['width'];
protected $height;
/** @var string */
protected $image;
+ /** @var string */
+ protected $blurhash;
/**
* Creates a card record from an attachment array.
$this->width = $attachment['width'] ?? 0;
$this->height = $attachment['height'] ?? 0;
$this->image = $attachment['image'] ?? '';
+ $this->blurhash = $attachment['blurhash'] ?? '';
$this->history = $history;
}
use Friendica\Util\Images;
use Imagick;
use ImagickPixel;
+use GDImage;
+use kornrunner\Blurhash\Blurhash;
/**
* Class to handle images
*/
class Image
{
- /** @var Imagick|resource */
+ /** @var GDImage|Imagick|resource */
private $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
+ {
+ if ($this->isImagick()) {
+ // Imagick is not supported
+ return '';
+ }
+
+ $width = $this->getWidth();
+ $height = $this->getHeight();
+
+ if (max($width, $height) > 90) {
+ $this->scaleDown(90);
+ $width = $this->getWidth();
+ $height = $this->getHeight();
+ }
+
+ $pixels = [];
+ for ($y = 0; $y < $height; ++$y) {
+ $row = [];
+ for ($x = 0; $x < $width; ++$x) {
+ $index = imagecolorat($this->image, $x, $y);
+ $colors = imagecolorsforindex($this->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)
+ {
+ if ($this->isImagick()) {
+ // Imagick is not supported
+ return;
+ }
- return $string;
+ $pixels = Blurhash::decode($blurhash, $width, $height);
+ $this->image = imagecreatetruecolor($width, $height);
+ for ($y = 0; $y < $height; ++$y) {
+ for ($x = 0; $x < $width; ++$x) {
+ [$r, $g, $b] = $pixels[$y][$x];
+ imagesetpixel($this->image, $x, $y, imagecolorallocate($this->image, $r, $g, $b));
+ }
+ }
}
}
use Friendica\DI;
use Friendica\Model\Photo;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
+use Friendica\Object\Image;
/**
* Image utilities
}
if ($data) {
- $data['size'] = $filesize;
+ $image = new Image($img_str);
+
+ $data['blurhash'] = $image->getBlurHash();
+ $data['size'] = $filesize;
}
return is_array($data) ? $data : [];
$image['width'] = $photodata[0];
$image['height'] = $photodata[1];
$image['contenttype'] = $photodata['mime'];
+ $image['blurhash'] = $photodata['blurhash'] ?? null;
unset($image['url']);
ksort($image);
} else {
use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) {
- define('DB_UPDATE_VERSION', 1496);
+ define('DB_UPDATE_VERSION', 1497);
}
return [
"height" => ["type" => "smallint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
"width" => ["type" => "smallint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
"datasize" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => ""],
+ "blurhash" => ["type" => "varbinary(255)", "comment" => "BlurHash representation of the photo"],
"data" => ["type" => "mediumblob", "not null" => "1", "comment" => ""],
"scale" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
"profile" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"height" => ["type" => "smallint unsigned", "comment" => "Height of the media"],
"width" => ["type" => "smallint unsigned", "comment" => "Width of the media"],
"size" => ["type" => "bigint unsigned", "comment" => "Media size"],
+ "blurhash" => ["type" => "varbinary(255)", "comment" => "BlurHash representation of the image"],
"preview" => ["type" => "varbinary(512)", "comment" => "Preview URL"],
"preview-height" => ["type" => "smallint unsigned", "comment" => "Height of the preview picture"],
"preview-width" => ["type" => "smallint unsigned", "comment" => "Width of the preview picture"],