Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable25] feat(theming): Only convert a background image if it benefits from it #42943

Merged
merged 3 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 64 additions & 17 deletions apps/theming/lib/ImageManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,32 +233,79 @@
throw new \Exception('Unsupported image type');
}

if ($key === 'background' && strpos($detectedMimeType, 'image/svg') === false && strpos($detectedMimeType, 'image/gif') === false) {
// Optimize the image since some people may upload images that will be
// either to big or are not progressive rendering.
$newImage = @imagecreatefromstring(file_get_contents($tmpFile));
if ($key === 'background' && $this->shouldOptimizeBackgroundImage($detectedMimeType, filesize($tmpFile))) {
try {
// Optimize the image since some people may upload images that will be
// either to big or are not progressive rendering.
$newImage = @imagecreatefromstring(file_get_contents($tmpFile));
if ($newImage === false) {
throw new \Exception('Could not read background image, possibly corrupted.');
}

// Preserve transparency
imagesavealpha($newImage, true);
imagealphablending($newImage, true);
// Preserve transparency
imagesavealpha($newImage, true);
imagealphablending($newImage, true);

$tmpFile = $this->tempManager->getTemporaryFile();
$newWidth = (int)(imagesx($newImage) < 4096 ? imagesx($newImage) : 4096);
$newHeight = (int)(imagesy($newImage) / (imagesx($newImage) / $newWidth));
$outputImage = imagescale($newImage, $newWidth, $newHeight);
$newWidth = (int)(imagesx($newImage) < 4096 ? imagesx($newImage) : 4096);
$newHeight = (int)(imagesy($newImage) / (imagesx($newImage) / $newWidth));

Check notice

Code scanning / Psalm

PossiblyFalseOperand Note

Left operand cannot be falsable, got false|int

Check notice

Code scanning / Psalm

PossiblyFalseOperand Note

Left operand cannot be falsable, got false|int
$outputImage = imagescale($newImage, $newWidth, $newHeight);
if ($outputImage === false) {
throw new \Exception('Could not scale uploaded background image.');
}

imageinterlace($outputImage, 1);
imagepng($outputImage, $tmpFile, 8);
imagedestroy($outputImage);
$newTmpFile = $this->tempManager->getTemporaryFile();
imageinterlace($outputImage, 1);

Check notice

Code scanning / Psalm

PossiblyInvalidArgument Note

Argument 1 of imageinterlace expects resource, possibly different type resource|true provided
// Keep jpeg images encoded as jpeg
if (strpos($detectedMimeType, 'image/jpeg') !== false) {
if (!imagejpeg($outputImage, $newTmpFile, 90)) {

Check notice

Code scanning / Psalm

PossiblyInvalidArgument Note

Argument 1 of imagejpeg expects resource, possibly different type resource|true provided
throw new \Exception('Could not recompress background image as JPEG');
}
} else {
if (!imagepng($outputImage, $newTmpFile, 8)) {

Check notice

Code scanning / Psalm

PossiblyInvalidArgument Note

Argument 1 of imagepng expects resource, possibly different type resource|true provided
throw new \Exception('Could not recompress background image as PNG');
}
}
$tmpFile = $newTmpFile;
imagedestroy($outputImage);

Check notice

Code scanning / Psalm

PossiblyInvalidArgument Note

Argument 1 of imagedestroy expects resource, possibly different type resource|true provided
} catch (\Exception $e) {
if (is_resource($outputImage) || $outputImage instanceof \GdImage) {

Check notice

Code scanning / Psalm

DocblockTypeContradiction Note

Cannot resolve types for $outputImage - docblock-defined type resource|true does not contain GdImage

Check notice

Code scanning / Psalm

PossiblyUndefinedVariable Note

Possibly undefined variable $outputImage defined in try block

Check notice

Code scanning / Psalm

PossiblyUndefinedVariable Note

Possibly undefined variable $outputImage defined in try block

Check notice

Code scanning / Psalm

DocblockTypeContradiction Note

Cannot resolve types for $outputImage - docblock-defined type true does not contain GdImage
imagedestroy($outputImage);

Check notice

Code scanning / Psalm

PossiblyInvalidArgument Note

Argument 1 of imagedestroy expects resource, possibly different type GdImage|resource provided
}

$target->putContent(file_get_contents($tmpFile));
} else {
$target->putContent(file_get_contents($tmpFile));
$this->logger->debug($e->getMessage());

Check notice

Code scanning / Psalm

DeprecatedMethod Note

The method OCP\ILogger::debug has been marked as deprecated
}
}

$target->putContent(file_get_contents($tmpFile));

return $detectedMimeType;
}

/**
* Decide whether an image benefits from shrinking and reconverting
*
* @param string $mimeType the mime type of the image
* @param int $contentSize size of the image file
* @return bool
*/
private function shouldOptimizeBackgroundImage(string $mimeType, int $contentSize): bool {
// Do not touch SVGs
if (strpos($mimeType, 'image/svg') !== false) {
return false;
}
// GIF does not benefit from converting
if (strpos($mimeType, 'image/gif') !== false) {
return false;
}
// WebP also does not benefit from converting
// We could possibly try to convert to progressive image, but normally webP images are quite small
if (strpos($mimeType, 'image/webp') !== false) {
return false;
}
// As a rule of thumb background images should be max. 150-300 KiB, small images do not benefit from converting
return $contentSize > 150000;
}

/**
* Returns a list of supported mime types for image uploads.
* "favicon" images are only allowed to be SVG when imagemagick with SVG support is available.
Expand Down
9 changes: 6 additions & 3 deletions apps/theming/tests/ImageManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,12 @@ public function testCleanup() {

public function dataUpdateImage() {
return [
['background', __DIR__ . '/../../../tests/data/testimage.png', true, true],
['background', __DIR__ . '/../../../tests/data/testimage.png', false, true],
['background', __DIR__ . '/../../../tests/data/testimage.jpg', true, true],
['background', __DIR__ . '/../../../tests/data/testimage.png', true, false],
['background', __DIR__ . '/../../../tests/data/testimage.png', false, false],
['background', __DIR__ . '/../../../tests/data/testimage.jpg', true, false],
['background', __DIR__ . '/../../../tests/data/testimage.webp', true, false],
['background', __DIR__ . '/../../../tests/data/testimage-large.jpg', true, true],
['background', __DIR__ . '/../../../tests/data/testimage-wide.png', true, true],
['logo', __DIR__ . '/../../../tests/data/testimagelarge.svg', true, false],
];
}
Expand Down
Binary file added tests/data/testimage-large.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/testimage.webp
Binary file not shown.
Loading