diff --git a/README.md b/README.md index 17d6952..9f52202 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,11 @@ Deleting or not setting a config will use a built-in default list of values for #### `occ config:app:set --value="64 256" previewgenerator squareSizes` Cropped square previews which are mostly used in the list and tile views of the files app. -#### `occ config:app:set --value="256 4096" previewgenerator squareUncroppedSizes` -Will retain the aspect ratio and try to maximize **either** width **or** height. +#### `occ config:app:set --value="256 4096" previewgenerator fillWidthHeightSizes` +Will retain the aspect ratio and try to use the given size for the longer edge. + +#### `occ config:app:set --value="256 4096" previewgenerator coverWidthHeightSizes` +Will retain the aspect ratio and try to use the given size for the shorter edge. #### `occ config:app:set --value="64 256 1024" previewgenerator widthSizes` Will retain the aspect ratio and use the specified width. The height will be scaled according to @@ -115,7 +118,7 @@ This should include all previews requested by the files, photos and activity app ``` ./occ config:app:set --value="64 256" previewgenerator squareSizes -./occ config:app:set --value="256 4096" previewgenerator squareUncroppedSizes +./occ config:app:set --value="256 4096" previewgenerator fillWidthHeightSizes ./occ config:app:set --value="" previewgenerator widthSizes ./occ config:app:set --value="" previewgenerator heightSizes ``` @@ -124,6 +127,14 @@ This will only generate: * Cropped square previews of: 64x64 and 256x256 * Aspect ratio previews with a max width **or** max height of: 256 and 4096 +### I'm using the Memories app + +You can generate an additional preview for the timeline view of the Memories app. + +``` +./occ config:app:set --value="256" previewgenerator coverWidthHeightSizes +``` + ### I get "PHP Fatal error: Allowed memory size of X bytes exhausted" You need to increase the memory allowance of PHP, by default it is 128 MB. You do that by changing the memory_limit in the php.ini file. diff --git a/lib/SizeHelper.php b/lib/SizeHelper.php index 7fadc82..ab6e2b2 100644 --- a/lib/SizeHelper.php +++ b/lib/SizeHelper.php @@ -11,6 +11,7 @@ use OCA\PreviewGenerator\AppInfo\Application; use OCP\IConfig; +use OCP\IPreview; class SizeHelper { public function __construct( @@ -28,7 +29,8 @@ public function generateSpecifications(): array { $sizes = [ 'square' => [], - 'squareUncropped' => [], + 'fillWidthHeight' => [], + 'coverWidthHeight' => [], 'height' => [], 'width' => [], ]; @@ -39,7 +41,7 @@ public function generateSpecifications(): array { $s = 64; while ($s <= $maxW || $s <= $maxH) { $sizes['square'][] = $s; - $sizes['squareUncropped'][] = $s; + $sizes['fillWidthHeight'][] = $s; $s *= 4; } @@ -60,8 +62,8 @@ public function generateSpecifications(): array { * Note that only powers of 4 matter but if users supply different * stuff it is their own fault and we just ignore it */ - $getCustomSizes = function (IConfig $config, $key) { - $raw = $config->getAppValue(Application::APP_ID, $key, null); + $getCustomSizes = function ($key) { + $raw = $this->config->getAppValue(Application::APP_ID, $key, null); if ($raw === null) { return null; } @@ -81,19 +83,28 @@ public function generateSpecifications(): array { return $values; }; - $squares = $getCustomSizes($this->config, 'squareSizes'); - $squaresUncropped = $getCustomSizes($this->config, 'squareUncroppedSizes'); - $widths = $getCustomSizes($this->config, 'widthSizes'); - $heights = $getCustomSizes($this->config, 'heightSizes'); + $squares = $getCustomSizes('squareSizes'); + $fillWidthHeight = $getCustomSizes('fillWidthHeightSizes') + ?? $getCustomSizes('squareUncroppedSizes'); + $coverWidthHeight = $getCustomSizes('coverWidthHeightSizes'); + $widths = $getCustomSizes('widthSizes'); + $heights = $getCustomSizes('heightSizes'); if ($squares !== null) { $sizes['square'] = array_intersect($sizes['square'], $squares); } - if ($squaresUncropped !== null) { - $sizes['squareUncropped'] = array_intersect( - $sizes['squareUncropped'], - $squaresUncropped, + if ($fillWidthHeight !== null) { + $sizes['fillWidthHeight'] = array_intersect( + $sizes['fillWidthHeight'], + $fillWidthHeight, + ); + } + + if ($coverWidthHeight !== null) { + $sizes['coverWidthHeight'] = array_filter( + $coverWidthHeight, + static fn ($size) => $size <= $maxW && $size <= $maxH && self::isPowerOfTwo($size), ); } @@ -117,9 +128,22 @@ private function mergeSpecifications(array $sizes): array { array_map(static function ($squareSize) { return ['width' => $squareSize, 'height' => $squareSize, 'crop' => true]; }, $sizes['square']), - array_map(static function ($squareSize) { - return ['width' => $squareSize, 'height' => $squareSize, 'crop' => false]; - }, $sizes['squareUncropped']), + array_map(static function ($size) { + return [ + 'width' => $size, + 'height' => $size, + 'crop' => false, + 'mode' => IPreview::MODE_COVER, + ]; + }, $sizes['coverWidthHeight']), + array_map(static function ($size) { + return [ + 'width' => $size, + 'height' => $size, + 'crop' => false, + 'mode' => IPreview::MODE_FILL, + ]; + }, $sizes['fillWidthHeight']), array_map(static function ($heightSize) { return ['width' => -1, 'height' => $heightSize, 'crop' => false]; }, $sizes['height']), @@ -128,4 +152,8 @@ private function mergeSpecifications(array $sizes): array { }, $sizes['width']) ); } + + private static function isPowerOfTwo(int $n): bool { + return ($n & ($n - 1)) === 0; + } } diff --git a/tests/SizeHelperTest.php b/tests/SizeHelperTest.php new file mode 100644 index 0000000..8c41093 --- /dev/null +++ b/tests/SizeHelperTest.php @@ -0,0 +1,114 @@ +config = $this->createMock(IConfig::class); + + $this->sizeHelper = new SizeHelper($this->config); + } + + private function mockMaxDimensions(int $maxW = 4096, int $maxH = 4096): void { + $this->config->method('getSystemValue') + ->willReturnMap([ + ['preview_max_x', 4096, $maxW], + ['preview_max_y', 4096, $maxH], + ]); + } + + public static function provideGenerateSpecificationsData(): array { + return [ + // Fallback and precedence for squareUncroppedSizes + [ + [ + 'squareSizes' => '', + 'squareUncroppedSizes' => '256', + 'fillWidthHeightSizes' => null, + 'coverWidthHeightSizes' => '', + 'widthSizes' => '', + 'heightSizes' => '', + ], + [['width' => 256, 'height' => 256, 'crop' => false, 'mode' => 'fill']], + ], + [ + [ + 'squareSizes' => '', + 'squareUncroppedSizes' => '256', + 'fillWidthHeightSizes' => '', + 'coverWidthHeightSizes' => '', + 'widthSizes' => '', + 'heightSizes' => '', + ], + [], + ], + [ + [ + 'squareSizes' => '', + 'squareUncroppedSizes' => '', + 'fillWidthHeightSizes' => '256', + 'coverWidthHeightSizes' => '', + 'widthSizes' => '', + 'heightSizes' => '', + ], + [['width' => 256, 'height' => 256, 'crop' => false, 'mode' => 'fill']], + ], + [ + [ + 'squareSizes' => '', + 'squareUncroppedSizes' => null, + 'fillWidthHeightSizes' => '256', + 'coverWidthHeightSizes' => '', + 'widthSizes' => '', + 'heightSizes' => '', + ], + [['width' => 256, 'height' => 256, 'crop' => false, 'mode' => 'fill']], + ], + // No default value for coverWidthHeightSizes + [ + [ + 'squareSizes' => '', + 'squareUncroppedSizes' => '', + 'fillWidthHeightSizes' => '', + 'coverWidthHeightSizes' => null, + 'widthSizes' => '', + 'heightSizes' => '', + ], + [], + ], + ]; + } + + /** @dataProvider provideGenerateSpecificationsData */ + public function testGenerateSpecifications(array $config, array $expectedSpecs): void { + $this->mockMaxDimensions(); + + $this->config->method('getAppValue') + ->willReturnCallback(function (string $appName, string $key, ?string $default) use ($config) { + $this->assertEquals($appName, 'previewgenerator'); + $this->assertNull($default); + return $config[$key] ?? $default; + }); + + $actualSpecs = $this->sizeHelper->generateSpecifications(); + $this->assertEqualsCanonicalizing($expectedSpecs, $actualSpecs); + } +}