Skip to content

Commit 2c838a7

Browse files
committed
feat: implement config for covering width and height sizes
Signed-off-by: Richard Steinmetz <[email protected]>
1 parent 2a92d67 commit 2c838a7

File tree

3 files changed

+171
-18
lines changed

3 files changed

+171
-18
lines changed

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,11 @@ Deleting or not setting a config will use a built-in default list of values for
7979
#### `occ config:app:set --value="64 256" previewgenerator squareSizes`
8080
Cropped square previews which are mostly used in the list and tile views of the files app.
8181

82-
#### `occ config:app:set --value="256 4096" previewgenerator squareUncroppedSizes`
83-
Will retain the aspect ratio and try to maximize **either** width **or** height.
82+
#### `occ config:app:set --value="256 4096" previewgenerator fillWidthHeightSizes`
83+
Will retain the aspect ratio and try to use the given size for the longer edge.
84+
85+
#### `occ config:app:set --value="256 4096" previewgenerator coverWidthHeightSizes`
86+
Will retain the aspect ratio and try to use the given size for the shorter edge.
8487

8588
#### `occ config:app:set --value="64 256 1024" previewgenerator widthSizes`
8689
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
115118

116119
```
117120
./occ config:app:set --value="64 256" previewgenerator squareSizes
118-
./occ config:app:set --value="256 4096" previewgenerator squareUncroppedSizes
121+
./occ config:app:set --value="256 4096" previewgenerator fillWidthHeightSizes
119122
./occ config:app:set --value="" previewgenerator widthSizes
120123
./occ config:app:set --value="" previewgenerator heightSizes
121124
```
@@ -124,6 +127,14 @@ This will only generate:
124127
* Cropped square previews of: 64x64 and 256x256
125128
* Aspect ratio previews with a max width **or** max height of: 256 and 4096
126129

130+
### I'm using the Memories app
131+
132+
You can generate an additional preview for the timeline view of the Memories app.
133+
134+
```
135+
./occ config:app:set --value="256" previewgenerator coverWidthHeightSizes
136+
```
137+
127138
### I get "PHP Fatal error: Allowed memory size of X bytes exhausted"
128139
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.
129140

lib/SizeHelper.php

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use OCA\PreviewGenerator\AppInfo\Application;
1313
use OCP\IConfig;
14+
use OCP\IPreview;
1415

1516
class SizeHelper {
1617
public function __construct(
@@ -28,7 +29,8 @@ public function generateSpecifications(): array {
2829

2930
$sizes = [
3031
'square' => [],
31-
'squareUncropped' => [],
32+
'fillWidthHeight' => [],
33+
'coverWidthHeight' => [],
3234
'height' => [],
3335
'width' => [],
3436
];
@@ -39,7 +41,7 @@ public function generateSpecifications(): array {
3941
$s = 64;
4042
while ($s <= $maxW || $s <= $maxH) {
4143
$sizes['square'][] = $s;
42-
$sizes['squareUncropped'][] = $s;
44+
$sizes['fillWidthHeight'][] = $s;
4345
$s *= 4;
4446
}
4547

@@ -60,8 +62,8 @@ public function generateSpecifications(): array {
6062
* Note that only powers of 4 matter but if users supply different
6163
* stuff it is their own fault and we just ignore it
6264
*/
63-
$getCustomSizes = function (IConfig $config, $key) {
64-
$raw = $config->getAppValue(Application::APP_ID, $key, null);
65+
$getCustomSizes = function ($key) {
66+
$raw = $this->config->getAppValue(Application::APP_ID, $key, null);
6567
if ($raw === null) {
6668
return null;
6769
}
@@ -81,19 +83,28 @@ public function generateSpecifications(): array {
8183
return $values;
8284
};
8385

84-
$squares = $getCustomSizes($this->config, 'squareSizes');
85-
$squaresUncropped = $getCustomSizes($this->config, 'squareUncroppedSizes');
86-
$widths = $getCustomSizes($this->config, 'widthSizes');
87-
$heights = $getCustomSizes($this->config, 'heightSizes');
86+
$squares = $getCustomSizes('squareSizes');
87+
$fillWidthHeight = $getCustomSizes('fillWidthHeightSizes')
88+
?? $getCustomSizes('squareUncroppedSizes');
89+
$coverWidthHeight = $getCustomSizes('coverWidthHeightSizes');
90+
$widths = $getCustomSizes('widthSizes');
91+
$heights = $getCustomSizes('heightSizes');
8892

8993
if ($squares !== null) {
9094
$sizes['square'] = array_intersect($sizes['square'], $squares);
9195
}
9296

93-
if ($squaresUncropped !== null) {
94-
$sizes['squareUncropped'] = array_intersect(
95-
$sizes['squareUncropped'],
96-
$squaresUncropped,
97+
if ($fillWidthHeight !== null) {
98+
$sizes['fillWidthHeight'] = array_intersect(
99+
$sizes['fillWidthHeight'],
100+
$fillWidthHeight,
101+
);
102+
}
103+
104+
if ($coverWidthHeight !== null) {
105+
$sizes['coverWidthHeight'] = array_filter(
106+
$coverWidthHeight,
107+
static fn ($size) => $size <= $maxW && $size <= $maxH && self::isPowerOfTwo($size),
97108
);
98109
}
99110

@@ -117,9 +128,22 @@ private function mergeSpecifications(array $sizes): array {
117128
array_map(static function ($squareSize) {
118129
return ['width' => $squareSize, 'height' => $squareSize, 'crop' => true];
119130
}, $sizes['square']),
120-
array_map(static function ($squareSize) {
121-
return ['width' => $squareSize, 'height' => $squareSize, 'crop' => false];
122-
}, $sizes['squareUncropped']),
131+
array_map(static function ($size) {
132+
return [
133+
'width' => $size,
134+
'height' => $size,
135+
'crop' => false,
136+
'mode' => IPreview::MODE_COVER,
137+
];
138+
}, $sizes['coverWidthHeight']),
139+
array_map(static function ($size) {
140+
return [
141+
'width' => $size,
142+
'height' => $size,
143+
'crop' => false,
144+
'mode' => IPreview::MODE_FILL,
145+
];
146+
}, $sizes['fillWidthHeight']),
123147
array_map(static function ($heightSize) {
124148
return ['width' => -1, 'height' => $heightSize, 'crop' => false];
125149
}, $sizes['height']),
@@ -128,4 +152,8 @@ private function mergeSpecifications(array $sizes): array {
128152
}, $sizes['width'])
129153
);
130154
}
155+
156+
private static function isPowerOfTwo(int $n): bool {
157+
return ($n & ($n - 1)) === 0;
158+
}
131159
}

tests/SizeHelperTest.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\PreviewGenerator\Tests;
11+
12+
use OCA\PreviewGenerator\SizeHelper;
13+
use OCP\IConfig;
14+
use PHPUnit\Framework\MockObject\MockObject;
15+
use PHPUnit\Framework\TestCase;
16+
17+
class SizeHelperTest extends TestCase {
18+
private SizeHelper $sizeHelper;
19+
20+
private IConfig|MockObject $config;
21+
22+
public function setUp(): void {
23+
parent::setUp();
24+
25+
$this->config = $this->createMock(IConfig::class);
26+
27+
$this->sizeHelper = new SizeHelper($this->config);
28+
}
29+
30+
private function mockMaxDimensions(int $maxW = 4096, int $maxH = 4096): void {
31+
$this->config->method('getSystemValue')
32+
->willReturnMap([
33+
['preview_max_x', 4096, $maxW],
34+
['preview_max_y', 4096, $maxH],
35+
]);
36+
}
37+
38+
public static function provideGenerateSpecificationsData(): array {
39+
return [
40+
// Fallback and precedence for squareUncroppedSizes
41+
[
42+
[
43+
'squareSizes' => '',
44+
'squareUncroppedSizes' => '256',
45+
'fillWidthHeightSizes' => null,
46+
'coverWidthHeightSizes' => '',
47+
'widthSizes' => '',
48+
'heightSizes' => '',
49+
],
50+
[['width' => 256, 'height' => 256, 'crop' => false, 'mode' => 'fill']],
51+
],
52+
[
53+
[
54+
'squareSizes' => '',
55+
'squareUncroppedSizes' => '256',
56+
'fillWidthHeightSizes' => '',
57+
'coverWidthHeightSizes' => '',
58+
'widthSizes' => '',
59+
'heightSizes' => '',
60+
],
61+
[],
62+
],
63+
[
64+
[
65+
'squareSizes' => '',
66+
'squareUncroppedSizes' => '',
67+
'fillWidthHeightSizes' => '256',
68+
'coverWidthHeightSizes' => '',
69+
'widthSizes' => '',
70+
'heightSizes' => '',
71+
],
72+
[['width' => 256, 'height' => 256, 'crop' => false, 'mode' => 'fill']],
73+
],
74+
[
75+
[
76+
'squareSizes' => '',
77+
'squareUncroppedSizes' => null,
78+
'fillWidthHeightSizes' => '256',
79+
'coverWidthHeightSizes' => '',
80+
'widthSizes' => '',
81+
'heightSizes' => '',
82+
],
83+
[['width' => 256, 'height' => 256, 'crop' => false, 'mode' => 'fill']],
84+
],
85+
// No default value for coverWidthHeightSizes
86+
[
87+
[
88+
'squareSizes' => '',
89+
'squareUncroppedSizes' => '',
90+
'fillWidthHeightSizes' => '',
91+
'coverWidthHeightSizes' => null,
92+
'widthSizes' => '',
93+
'heightSizes' => '',
94+
],
95+
[],
96+
],
97+
];
98+
}
99+
100+
/** @dataProvider provideGenerateSpecificationsData */
101+
public function testGenerateSpecifications(array $config, array $expectedSpecs): void {
102+
$this->mockMaxDimensions();
103+
104+
$this->config->method('getAppValue')
105+
->willReturnCallback(function(string $appName, string $key, ?string $default) use ($config) {
106+
$this->assertEquals($appName, 'previewgenerator');
107+
$this->assertNull($default);
108+
return $config[$key] ?? $default;
109+
});
110+
111+
$actualSpecs = $this->sizeHelper->generateSpecifications();
112+
$this->assertEqualsCanonicalizing($expectedSpecs, $actualSpecs);
113+
}
114+
}

0 commit comments

Comments
 (0)