]> source.dussan.org Git - nextcloud-server.git/commitdiff
blurhash generation 41044/head
authorMaxence Lange <maxence@artificial-owl.com>
Thu, 4 Jan 2024 22:36:03 +0000 (21:36 -0100)
committerJohn Molakvoæ <skjnldsv@protonmail.com>
Fri, 5 Jan 2024 10:50:51 +0000 (11:50 +0100)
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
3rdparty
build/psalm-baseline.xml
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php [new file with mode: 0644]
lib/private/Server.php

index 5b8a5fc015968956a000d269561cb5ec9d931870..a71bd8af76fdcfad78c865d1c60f6dde6e24f1dd 160000 (submodule)
--- a/3rdparty
+++ b/3rdparty
@@ -1 +1 @@
-Subproject commit 5b8a5fc015968956a000d269561cb5ec9d931870
+Subproject commit a71bd8af76fdcfad78c865d1c60f6dde6e24f1dd
index 273c7ef47094601eb1b4a32a7313e8f5232f654e..ba529091482996802d09144026f7ee5ce8bda0cb 100644 (file)
       <code>$jobList</code>
     </MoreSpecificImplementedParamType>
   </file>
+  <file src="lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php">
+    <InvalidArgument>
+      <code>$image</code>
+      <code>$image</code>
+      <code>$image</code>
+      <code>$image</code>
+    </InvalidArgument>
+    <InvalidReturnStatement>
+      <code>$image</code>
+    </InvalidReturnStatement>
+    <InvalidReturnType>
+      <code>GdImage|false</code>
+    </InvalidReturnType>
+  </file>
   <file src="lib/private/Cache/CappedMemoryCache.php">
     <MissingTemplateParam>
       <code>\ArrayAccess</code>
index e9a7d9d5705aef7bb2d6abd7d95daeffab5563d5..201b49f3fae37735a55248107362656312925d55 100644 (file)
@@ -953,6 +953,7 @@ return array(
     'OC\\BackgroundJob\\QueuedJob' => $baseDir . '/lib/private/BackgroundJob/QueuedJob.php',
     'OC\\BackgroundJob\\TimedJob' => $baseDir . '/lib/private/BackgroundJob/TimedJob.php',
     'OC\\BinaryFinder' => $baseDir . '/lib/private/BinaryFinder.php',
+    'OC\\Blurhash\\Listener\\GenerateBlurhashMetadata' => $baseDir . '/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php',
     'OC\\Broadcast\\Events\\BroadcastEvent' => $baseDir . '/lib/private/Broadcast/Events/BroadcastEvent.php',
     'OC\\Cache\\CappedMemoryCache' => $baseDir . '/lib/private/Cache/CappedMemoryCache.php',
     'OC\\Cache\\File' => $baseDir . '/lib/private/Cache/File.php',
index 922db7b36acf9d98e39e8a3b8c6ec729267ae468..ebcccb328c24a0ce3ba907c1170869d50c1aa3aa 100644 (file)
@@ -986,6 +986,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
         'OC\\BackgroundJob\\QueuedJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/QueuedJob.php',
         'OC\\BackgroundJob\\TimedJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/TimedJob.php',
         'OC\\BinaryFinder' => __DIR__ . '/../../..' . '/lib/private/BinaryFinder.php',
+        'OC\\Blurhash\\Listener\\GenerateBlurhashMetadata' => __DIR__ . '/../../..' . '/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php',
         'OC\\Broadcast\\Events\\BroadcastEvent' => __DIR__ . '/../../..' . '/lib/private/Broadcast/Events/BroadcastEvent.php',
         'OC\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/private/Cache/CappedMemoryCache.php',
         'OC\\Cache\\File' => __DIR__ . '/../../..' . '/lib/private/Cache/File.php',
diff --git a/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php b/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php
new file mode 100644 (file)
index 0000000..7bfe36e
--- /dev/null
@@ -0,0 +1,164 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2024 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Blurhash\Listener;
+
+use GdImage;
+use kornrunner\Blurhash\Blurhash;
+use OC\Files\Node\File;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\GenericFileException;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\FilesMetadata\AMetadataEvent;
+use OCP\FilesMetadata\Event\MetadataBackgroundEvent;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use OCP\IPreview;
+use OCP\Lock\LockedException;
+
+/**
+ * Generate a Blurhash string as metadata when image file is uploaded/edited.
+ *
+ * @template-implements IEventListener<AMetadataEvent>
+ */
+class GenerateBlurhashMetadata implements IEventListener {
+       private const RESIZE_BOXSIZE = 300;
+
+       private const COMPONENTS_X = 4;
+       private const COMPONENTS_Y = 3;
+
+       public function __construct(
+               private IPreview $preview,
+       ) {
+       }
+
+       /**
+        * @throws NotPermittedException
+        * @throws GenericFileException
+        * @throws LockedException
+        */
+       public function handle(Event $event): void {
+               if (!($event instanceof MetadataLiveEvent)
+                       && !($event instanceof MetadataBackgroundEvent)) {
+                       return;
+               }
+
+               $file = $event->getNode();
+               if (!($file instanceof File)) {
+                       return;
+               }
+
+               // too heavy to run on the live thread, request a rerun as a background job
+               if ($event instanceof MetadataLiveEvent) {
+                       $event->requestBackgroundJob();
+                       return;
+               }
+
+               $image = false;
+               try {
+                       // using preview image to generate the blurhash
+                       $preview = $this->preview->getPreview($file, 256, 256);
+                       $image = imagecreatefromstring($preview->getContent());
+               } catch (NotFoundException $e) {
+                       // https://github.com/nextcloud/server/blob/9d70fd3e64b60a316a03fb2b237891380c310c58/lib/private/legacy/OC_Image.php#L668
+                       // The preview system can fail on huge picture, in that case we use our own image resizer.
+                       if (str_starts_with($file->getMimetype(), 'image/')) {
+                               $image = $this->resizedImageFromFile($file);
+                       }
+               }
+
+               if ($image === false) {
+                       return;
+               }
+
+               $metadata = $event->getMetadata();
+               $metadata->setString('blurhash', $this->generateBlurHash($image));
+       }
+
+       /**
+        * @param File $file
+        *
+        * @return GdImage|false
+        * @throws GenericFileException
+        * @throws NotPermittedException
+        * @throws LockedException
+        */
+       private function resizedImageFromFile(File $file): GdImage|false {
+               $image = imagecreatefromstring($file->getContent());
+               if ($image === false) {
+                       return false;
+               }
+
+               $currX = imagesx($image);
+               $currY = imagesy($image);
+
+               if ($currX > $currY) {
+                       $newX = self::RESIZE_BOXSIZE;
+                       $newY = intval($currY * $newX / $currX);
+               } else {
+                       $newY = self::RESIZE_BOXSIZE;
+                       $newX = intval($currX * $newY / $currY);
+               }
+
+               $newImage = imagescale($image, $newX, $newY);
+               return ($newImage !== false) ? $newImage : $image;
+       }
+
+       /**
+        * @param GdImage $image
+        *
+        * @return string
+        */
+       public function generateBlurHash(GdImage $image): string {
+               $width = imagesx($image);
+               $height = imagesy($image);
+
+               $pixels = [];
+               for ($y = 0; $y < $height; ++$y) {
+                       $row = [];
+                       for ($x = 0; $x < $width; ++$x) {
+                               $index = imagecolorat($image, $x, $y);
+                               $colors = imagecolorsforindex($image, $index);
+                               $row[] = [$colors['red'], $colors['green'], $colors['blue']];
+                       }
+
+                       $pixels[] = $row;
+               }
+
+               return Blurhash::encode($pixels, self::COMPONENTS_X, self::COMPONENTS_Y);
+       }
+
+       /**
+        * @param IEventDispatcher $eventDispatcher
+        *
+        * @return void
+        */
+       public static function loadListeners(IEventDispatcher $eventDispatcher): void {
+               $eventDispatcher->addServiceListener(MetadataLiveEvent::class, self::class);
+               $eventDispatcher->addServiceListener(MetadataBackgroundEvent::class, self::class);
+       }
+}
index acc66b9cb0adb3e598532fed11f1c6fe142cf258..d026ad4286de56b3baa02dc4a874d6c12d4c8900 100644 (file)
@@ -68,6 +68,7 @@ use OC\Authentication\Listeners\UserLoggedInListener;
 use OC\Authentication\LoginCredentials\Store;
 use OC\Authentication\Token\IProvider;
 use OC\Avatar\AvatarManager;
+use OC\Blurhash\Listener\GenerateBlurhashMetadata;
 use OC\Collaboration\Collaborators\GroupPlugin;
 use OC\Collaboration\Collaborators\MailPlugin;
 use OC\Collaboration\Collaborators\RemoteGroupPlugin;
@@ -1482,6 +1483,7 @@ class Server extends ServerContainer implements IServerContainer {
                $eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class);
 
                FilesMetadataManager::loadListeners($eventDispatcher);
+               GenerateBlurhashMetadata::loadListeners($eventDispatcher);
        }
 
        /**