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

[RFC] Refactor image handling #28279

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
507 changes: 507 additions & 0 deletions lib/private/Image/Common.php

Large diffs are not rendered by default.

649 changes: 649 additions & 0 deletions lib/private/Image/Gd.php

Large diffs are not rendered by default.

474 changes: 474 additions & 0 deletions lib/private/Image/Gmagick.php

Large diffs are not rendered by default.

474 changes: 474 additions & 0 deletions lib/private/Image/Imagick.php

Large diffs are not rendered by default.

447 changes: 447 additions & 0 deletions lib/private/Image/Vips.php

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions lib/private/legacy/OC_Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public function valid(): bool {
/**
* Returns the MIME type of the image or null if no image is loaded.
*
* @return string
* @return ?string
*/
public function mimeType(): ?string {
return $this->valid() ? $this->mimeType : null;
Expand Down Expand Up @@ -716,7 +716,7 @@ public function loadFromFile($imagePath = false) {
}
break;
case IMAGETYPE_BMP:
$this->resource = imagecreatefrombmp($imagePath);
$this->resource = @imagecreatefrombmp($imagePath);
break;
case IMAGETYPE_WEBP:
if (imagetypes() & IMG_WEBP) {
Expand Down
2 changes: 1 addition & 1 deletion lib/public/IImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public function show(?string $mimeType = null): bool;
public function save(?string $filePath = null, ?string $mimeType = null): bool;

/**
* @return false|resource|\GdImage Returns the image resource if any
* @return bool|object|resource Returns the raw image resource if any, false otherwise
* @since 8.1.0
*/
public function resource();
Expand Down
367 changes: 367 additions & 0 deletions tests/lib/Image/Gd.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
<?php
/**
* Copyright (c) 2013 Christopher Schäpers <[email protected]>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/

namespace Test;

use OC;
use OCP\IConfig;
use OCP\Image\Gd;

class ImageGdTest extends \Test\TestCase {
public static function tearDownAfterClass(): void {
@unlink(OC::$SERVERROOT.'/tests/data/testimage2.png');
@unlink(OC::$SERVERROOT.'/tests/data/testimage2.jpg');

parent::tearDownAfterClass();
}

public function testConstructDestruct() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$this->assertInstanceOf('Gd', $img);
$this->assertInstanceOf('\OCP\IImage', $img);
unset($img);

$imgcreate = imagecreatefromjpeg(OC::$SERVERROOT.'/tests/data/testimage.jpg');
$img = new Gd();
$img->setResource($imgcreate);
$this->assertInstanceOf('Gd', $img);
$this->assertInstanceOf('\OCP\IImage', $img);
unset($img);

$base64 = base64_encode(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif'));
$img = new Gd();
$img->loadFromBase64($base64);
$this->assertInstanceOf('Gd', $img);
$this->assertInstanceOf('\OCP\IImage', $img);
unset($img);

$img = new Gd();
$this->assertInstanceOf('Gd', $img);
$this->assertInstanceOf('\OCP\IImage', $img);
unset($img);
}

public function testValid() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$this->assertTrue($img->valid());

$text = base64_encode("Lorem ipsum dolor sir amet …");
$img = new Gd();
$img->loadFromBase64($text);
$this->assertFalse($img->valid());

$img = new Gd();
$this->assertFalse($img->valid());
}

public function testMimeType() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$this->assertEquals('image/png', $img->mimeType());

$img = new Gd();
$this->assertEquals('', $img->mimeType());

$img = new Gd();
$img->loadFromData(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
$this->assertEquals('image/jpeg', $img->mimeType());

$img = new Gd();
$img->loadFromBase64(base64_encode(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif')));
$this->assertEquals('image/gif', $img->mimeType());
}

public function testWidth() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$this->assertEquals(128, $img->width());

$img = new Gd();
$img->loadFromData(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
$this->assertEquals(1680, $img->width());

$img = new Gd();
$img->loadFromBase64(base64_encode(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif')));
$this->assertEquals(64, $img->width());

$img = new Gd();
$this->assertEquals(-1, $img->width());
}

public function testHeight() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$this->assertEquals(128, $img->height());

$img = new Gd();
$img->loadFromData(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
$this->assertEquals(1050, $img->height());

$img = new Gd();
$img->loadFromBase64(base64_encode(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif')));
$this->assertEquals(64, $img->height());

$img = new Gd();
$this->assertEquals(-1, $img->height());
}

public function testSave() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$img->resize(16);
$img->save(OC::$SERVERROOT.'/tests/data/testimage2.png');
$this->assertEquals(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage2.png'), $img->data());

$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.jpg');
$img->resize(128);
$img->save(OC::$SERVERROOT.'/tests/data/testimage2.jpg');
$this->assertEquals(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage2.jpg'), $img->data());
}

public function testData() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$raw = imagecreatefromstring(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.png'));
// Preserve transparency
imagealphablending($raw, true);
imagesavealpha($raw, true);
ob_start();
imagepng($raw);
$expected = ob_get_clean();
$this->assertEquals($expected, $img->data());

$config = $this->createMock(IConfig::class);
$config->expects($this->once())
->method('getAppValue')
->with('preview', 'jpeg_quality', 90)
->willReturn(null);
$img = new Gd(null, null, $config);
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.jpg');
$raw = imagecreatefromstring(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
ob_start();
imagejpeg($raw);
$expected = ob_get_clean();
$this->assertEquals($expected, $img->data());

$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.gif');
$raw = imagecreatefromstring(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif'));
ob_start();
imagegif($raw);
$expected = ob_get_clean();
$this->assertEquals($expected, $img->data());
}

public function testDataNoResource() {
$img = new Gd();
$this->assertNull($img->data());
}

/**
* @depends testData
*/
public function testToString() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$expected = base64_encode($img->data());
$this->assertEquals($expected, (string)$img);

$img = new Gd();
$img->loadFromData(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
$expected = base64_encode($img->data());
$this->assertEquals($expected, (string)$img);

$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.gif');
$expected = base64_encode($img->data());
$this->assertEquals($expected, (string)$img);
}

public function testResize() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$this->assertTrue($img->resize(32));
$this->assertEquals(32, $img->width());
$this->assertEquals(32, $img->height());

$img = new Gd();
$img->loadFromData(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
$this->assertTrue($img->resize(840));
$this->assertEquals(840, $img->width());
$this->assertEquals(525, $img->height());

$img = new Gd();
$img->loadFromBase64(base64_encode(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif')));
$this->assertTrue($img->resize(100));
$this->assertEquals(100, $img->width());
$this->assertEquals(100, $img->height());
}

public function testPreciseResize() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$this->assertTrue($img->preciseResize(128, 512));
$this->assertEquals(128, $img->width());
$this->assertEquals(512, $img->height());

$img = new Gd();
$img->loadFromData(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
$this->assertTrue($img->preciseResize(64, 840));
$this->assertEquals(64, $img->width());
$this->assertEquals(840, $img->height());

$img = new Gd();
$img->loadFromBase64(base64_encode(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif')));
$this->assertTrue($img->preciseResize(1000, 1337));
$this->assertEquals(1000, $img->width());
$this->assertEquals(1337, $img->height());
}

public function testCenterCrop() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$img->centerCrop();
$this->assertEquals(128, $img->width());
$this->assertEquals(128, $img->height());

$img = new Gd();
$img->loadFromData(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
$img->centerCrop();
$this->assertEquals(1050, $img->width());
$this->assertEquals(1050, $img->height());

$img = new Gd();
$img->loadFromBase64(base64_encode(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif')));
$img->centerCrop(512);
$this->assertEquals(512, $img->width());
$this->assertEquals(512, $img->height());
}

public function testCrop() {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$this->assertTrue($img->crop(0, 0, 50, 20));
$this->assertEquals(50, $img->width());
$this->assertEquals(20, $img->height());

$img = new Gd();
$img->loadFromData(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
$this->assertTrue($img->crop(500, 700, 550, 300));
$this->assertEquals(550, $img->width());
$this->assertEquals(300, $img->height());

$img = new Gd();
$img->loadFromBase64(base64_encode(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif')));
$this->assertTrue($img->crop(10, 10, 15, 15));
$this->assertEquals(15, $img->width());
$this->assertEquals(15, $img->height());
}

public static function sampleProvider() {
return [
['testimage.png', [200, 100], [100, 100]],
['testimage.jpg', [840, 840], [840, 525]],
['testimage.gif', [200, 250], [200, 200]]
];
}

/**
* @dataProvider sampleProvider
*
* @param string $filename
* @param int[] $asked
* @param int[] $expected
*/
public function testFitIn($filename, $asked, $expected) {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT . '/tests/data/' . $filename);
$this->assertTrue($img->fitIn($asked[0], $asked[1]));
$this->assertEquals($expected[0], $img->width());
$this->assertEquals($expected[1], $img->height());
}

public static function sampleFilenamesProvider() {
return [
['testimage.png'],
['testimage.jpg'],
['testimage.gif']
];
}

/**
* Image should not be resized if it's already smaller than what is required
*
* @dataProvider sampleFilenamesProvider
*
* @param string $filename
*/
public function testScaleDownToFitWhenSmallerAlready($filename) {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/' . $filename);
$currentWidth = $img->width();
$currentHeight = $img->height();
// We pick something larger than the image we want to scale down
$this->assertFalse($img->scaleDownToFit(4000, 4000));
// The dimensions of the image should not have changed since it's smaller already
$resizedWidth = $img->width();
$resizedHeight = $img->height();
$this->assertEquals(
$currentWidth, $img->width(), "currentWidth $currentWidth resizedWidth $resizedWidth \n"
);
$this->assertEquals(
$currentHeight, $img->height(),
"currentHeight $currentHeight resizedHeight $resizedHeight \n"
);
}

public static function largeSampleProvider() {
return [
['testimage.png', [200, 100], [100, 100]],
['testimage.jpg', [840, 840], [840, 525]],
];
}

/**
* @dataProvider largeSampleProvider
*
* @param string $filename
* @param int[] $asked
* @param int[] $expected
*/
public function testScaleDownWhenBigger($filename, $asked, $expected) {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/' . $filename);
//$this->assertTrue($img->scaleDownToFit($asked[0], $asked[1]));
$img->scaleDownToFit($asked[0], $asked[1]);
$this->assertEquals($expected[0], $img->width());
$this->assertEquals($expected[1], $img->height());
}

public function convertDataProvider() {
return [
[ 'image/gif'],
[ 'image/jpeg'],
[ 'image/png'],
];
}

/**
* @dataProvider convertDataProvider
*/
public function testConvert($mimeType) {
$img = new Gd();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.png');
$tempFile = tempnam(sys_get_temp_dir(), 'img-test');

$img->save($tempFile, $mimeType);
$this->assertEquals($mimeType, image_type_to_mime_type(exif_imagetype($tempFile)));
}
}
Loading
Loading