View source
<?php
namespace Drupal\Tests\imagemagick\Functional;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Image\ImageInterface;
use Drupal\imagemagick\EventSubscriber\ImagemagickEventSubscriber;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\imagemagick\Kernel\ToolkitSetupTrait;
class ToolkitImagemagickTest extends BrowserTestBase {
use ToolkitSetupTrait;
protected $black = [
0,
0,
0,
0,
];
protected $red = [
255,
0,
0,
0,
];
protected $green = [
0,
255,
0,
0,
];
protected $blue = [
0,
0,
255,
0,
];
protected $yellow = [
255,
255,
0,
0,
];
protected $fuchsia = [
255,
0,
255,
0,
];
protected $cyan = [
0,
255,
255,
0,
];
protected $white = [
255,
255,
255,
0,
];
protected $grey = [
128,
128,
128,
0,
];
protected $transparent = [
0,
0,
0,
127,
];
protected $rotateTransparent = [
255,
255,
255,
127,
];
protected $width = 40;
protected $height = 20;
protected static $modules = [
'system',
'imagemagick',
'file_mdm',
'file_test',
];
protected $defaultTheme = 'stark';
public function setUp() : void {
parent::setUp();
$admin_user = $this
->drupalCreateUser([
'administer site configuration',
]);
$this
->drupalLogin($admin_user);
}
public function testTemporaryRemoteCopiesDeletion(string $toolkit_id, string $toolkit_config, array $toolkit_settings) : void {
$this
->setUpToolkit($toolkit_id, $toolkit_config, $toolkit_settings);
$this
->prepareImageFileHandling();
$image = $this->imageFactory
->get('dummy-remote://image-test.png');
$image
->getToolkit()
->getExifOrientation();
$this
->assertCount(1, $this->fileSystem
->scanDirectory('temporary://', '/ima.*/'), 'A temporary file has been created for getting metadata from a remote file.');
$callbacks = drupal_register_shutdown_function();
foreach ($callbacks as $callback) {
if ($callback['callback'] === [
ImagemagickEventSubscriber::class,
'removeTemporaryRemoteCopy',
]) {
call_user_func_array($callback['callback'], $callback['arguments']);
}
}
$this
->assertCount(0, $this->fileSystem
->scanDirectory('temporary://', '/ima.*/'), 'No files left in the temporary directory after the Drupal shutdown.');
}
public function testDoubleCropping(string $toolkit_id, string $toolkit_config, array $toolkit_settings) : void {
$this
->setUpToolkit($toolkit_id, $toolkit_config, $toolkit_settings);
$this
->prepareImageFileHandling();
$image = $this->imageFactory
->get('public://image-test.png');
$image
->resize(5833, 3889);
$image
->save('public://3204498-test.png');
$test_image = $this->imageFactory
->get('public://3204498-test.png');
$this
->assertSame(5833, $test_image
->getWidth());
$this
->assertSame(3889, $test_image
->getHeight());
$test_image
->crop(0, 1601, 5826, 1456);
$test_image
->resize(1601, 400);
$test_image
->crop(100, 0, 1400, 400);
$test_image
->save('public://3204498-test-derived.png');
$derived_test_image = $this->imageFactory
->get('public://3204498-test-derived.png');
$this
->assertSame(1400, $derived_test_image
->getWidth());
$this
->assertSame(400, $derived_test_image
->getHeight());
}
public function testManipulations(string $toolkit_id, string $toolkit_config, array $toolkit_settings) : void {
$this
->setUpToolkit($toolkit_id, $toolkit_config, $toolkit_settings);
$this
->prepareImageFileHandling();
$default_corners = [
$this->red,
$this->green,
$this->blue,
$this->transparent,
];
$files = [
'image-test.png',
'image-test.gif',
'image-test-no-transparency.gif',
'image-test.jpg',
'img-test.webp',
];
$operations = [
'resize' => [
'function' => 'resize',
'arguments' => [
'width' => 20,
'height' => 10,
],
'width' => 20,
'height' => 10,
'corners' => $default_corners,
'tolerance' => 0,
],
'scale_x' => [
'function' => 'scale',
'arguments' => [
'width' => 20,
],
'width' => 20,
'height' => 10,
'corners' => $default_corners,
'tolerance' => 0,
],
'scale_y' => [
'function' => 'scale',
'arguments' => [
'height' => 10,
],
'width' => 20,
'height' => 10,
'corners' => $default_corners,
'tolerance' => 0,
],
'upscale_x' => [
'function' => 'scale',
'arguments' => [
'width' => 80,
'upscale' => TRUE,
],
'width' => 80,
'height' => 40,
'corners' => $default_corners,
'tolerance' => 0,
],
'upscale_y' => [
'function' => 'scale',
'arguments' => [
'height' => 40,
'upscale' => TRUE,
],
'width' => 80,
'height' => 40,
'corners' => $default_corners,
'tolerance' => 0,
],
'crop' => [
'function' => 'crop',
'arguments' => [
'x' => 12,
'y' => 4,
'width' => 16,
'height' => 12,
],
'width' => 16,
'height' => 12,
'corners' => array_fill(0, 4, $this->white),
'tolerance' => 0,
],
'scale_and_crop' => [
'function' => 'scale_and_crop',
'arguments' => [
'width' => 10,
'height' => 8,
],
'width' => 10,
'height' => 8,
'corners' => array_fill(0, 4, $this->black),
'tolerance' => 100,
],
'convert_jpg' => [
'function' => 'convert',
'width' => 40,
'height' => 20,
'arguments' => [
'extension' => 'jpeg',
],
'mimetype' => 'image/jpeg',
'corners' => $default_corners,
'tolerance' => 0,
],
'convert_gif' => [
'function' => 'convert',
'width' => 40,
'height' => 20,
'arguments' => [
'extension' => 'gif',
],
'mimetype' => 'image/gif',
'corners' => $default_corners,
'tolerance' => 15,
],
'convert_png' => [
'function' => 'convert',
'width' => 40,
'height' => 20,
'arguments' => [
'extension' => 'png',
],
'mimetype' => 'image/png',
'corners' => $default_corners,
'tolerance' => 5,
],
'convert_webp' => [
'function' => 'convert',
'width' => 40,
'height' => 20,
'arguments' => [
'extension' => 'webp',
],
'mimetype' => 'image/webp',
'corners' => $default_corners,
'tolerance' => 27,
],
'rotate_5' => [
'function' => 'rotate',
'arguments' => [
'degrees' => 5,
'background' => '#FF00FF',
'resize_filter' => 'Box',
],
'width' => 41,
'height' => 23,
'corners' => array_fill(0, 4, $this->fuchsia),
'tolerance' => 5,
],
'rotate_minus_10' => [
'function' => 'rotate',
'arguments' => [
'degrees' => -10,
'background' => '#FF00FF',
'resize_filter' => 'Box',
],
'width' => 41,
'height' => 26,
'corners' => array_fill(0, 4, $this->fuchsia),
'tolerance' => 15,
],
'rotate_90' => [
'function' => 'rotate',
'arguments' => [
'degrees' => 90,
'background' => '#FF00FF',
],
'width' => 20,
'height' => 40,
'corners' => [
$this->transparent,
$this->red,
$this->green,
$this->blue,
],
'tolerance' => 0,
],
'rotate_transparent_5' => [
'function' => 'rotate',
'arguments' => [
'degrees' => 5,
'resize_filter' => 'Box',
],
'width' => 41,
'height' => 23,
'corners' => array_fill(0, 4, $this->transparent),
'tolerance' => 0,
],
'rotate_transparent_90' => [
'function' => 'rotate',
'arguments' => [
'degrees' => 90,
],
'width' => 20,
'height' => 40,
'corners' => [
$this->transparent,
$this->red,
$this->green,
$this->blue,
],
'tolerance' => 0,
],
'desaturate' => [
'function' => 'desaturate',
'arguments' => [],
'height' => 20,
'width' => 40,
'corners' => [
array_fill(0, 3, 76) + [
3 => 0,
],
array_fill(0, 3, 149) + [
3 => 0,
],
array_fill(0, 3, 29) + [
3 => 0,
],
array_fill(0, 3, 225) + [
3 => 127,
],
],
'tolerance' => 17000,
],
];
foreach ($files as $file) {
$image_uri = 'public://' . $file;
foreach ($operations as $op => $values) {
$image = $this->imageFactory
->get($image_uri);
if (!$image
->isValid()) {
if ($file === 'img-test.webp') {
continue 2;
}
$this
->fail("Could not load image {$file}.");
}
$this
->assertSame(1, $image
->getToolkit()
->getFrames());
$image
->apply($values['function'], $values['arguments']);
$file_path = $this->testDirectory . '/' . $op . substr($file, -4);
$save_result = $image
->save($file_path);
if (!$save_result && $op === 'convert_webp') {
continue 2;
}
$this
->assertTrue($save_result);
$image = $this->imageFactory
->get($file_path);
$this
->assertTrue($image
->isValid());
$package = $image
->getToolkit()
->getExecManager()
->getPackage();
if ($package === 'graphicsmagick') {
if (in_array($file, [
'image-test.gif',
'image-test-no-transparency.gif',
]) && in_array($op, [
'crop',
'scale_and_crop',
'convert_png',
])) {
continue;
}
}
if ($package === 'imagemagick') {
if (in_array($file, [
'image-test.gif',
'image-test-no-transparency.gif',
]) && in_array($op, [
'crop',
'scale_and_crop',
])) {
continue;
}
}
$image = $this->imageFactory
->get($file_path, 'gd');
$toolkit = $image
->getToolkit();
$toolkit
->getResource();
$this
->assertTrue($image
->isValid());
if (isset($values['mimetype'])) {
$this
->assertEquals($values['mimetype'], $toolkit
->getMimeType(), "Image '{$file}' after '{$op}' action has proper MIME type ({$values['mimetype']}).");
}
$correct_dimensions_real = TRUE;
$correct_dimensions_object = TRUE;
$actual_toolkit_width = imagesx($toolkit
->getResource());
$actual_toolkit_height = imagesy($toolkit
->getResource());
if ($actual_toolkit_height != $values['height'] || $actual_toolkit_width != $values['width']) {
$correct_dimensions_real = FALSE;
}
$actual_image_width = $image
->getWidth();
$actual_image_height = $image
->getHeight();
if ($actual_image_width != $values['width'] || $actual_image_height != $values['height']) {
$correct_dimensions_object = FALSE;
}
$this
->assertTrue($correct_dimensions_real, "Image '{$file}' after '{$op}' action has proper dimensions. Expected {$values['width']}x{$values['height']}, actual {$actual_toolkit_width}x{$actual_toolkit_height}.");
$this
->assertTrue($correct_dimensions_object, "Image '{$file}' object after '{$op}' action is reporting the proper height and width values. Expected {$values['width']}x{$values['height']}, actual {$actual_image_width}x{$actual_image_height}.");
if ($file === 'img-test.webp' && $package === 'graphicsmagick') {
$values['tolerance'] += 4800;
}
if ($image
->getToolkit()
->getType() != IMAGETYPE_JPEG) {
foreach ($values['corners'] as $key => $corner) {
if ($file === 'image-test-no-transparency.gif' && $corner === $this->transparent && $op != 'rotate_transparent_5') {
$corner = $this->yellow;
}
if ($file === 'image-test.jpg' && $corner === $this->transparent && in_array($op, [
'convert_gif',
'convert_png',
'convert_webp',
])) {
$corner = $this->yellow;
}
switch ($key) {
case 0:
$x = 0;
$y = 0;
break;
case 1:
$x = $image
->getWidth() - 1;
$y = 0;
break;
case 2:
$x = $image
->getWidth() - 1;
$y = $image
->getHeight() - 1;
break;
case 3:
$x = 0;
$y = $image
->getHeight() - 1;
break;
}
$this
->assertColorsAreClose($corner, $this
->getPixelColor($image, $x, $y), $values['tolerance'], $file, $op);
}
}
}
}
$this
->assertFalse($this->imageFactory
->get('/generic.png')
->isValid());
$this
->assertFalse($this->imageFactory
->get('public://generic.png')
->isValid());
foreach ([
IMAGETYPE_PNG,
IMAGETYPE_GIF,
IMAGETYPE_JPEG,
] as $type) {
$image = $this->imageFactory
->get();
$image
->createNew(50, 20, image_type_to_extension($type, FALSE), '#ffff00');
$file = 'from_null' . image_type_to_extension($type);
$file_path = $this->testDirectory . '/' . $file;
$this
->assertEquals(50, $image
->getWidth(), "Image file '{$file}' has the correct width.");
$this
->assertEquals(20, $image
->getHeight(), "Image file '{$file}' has the correct height.");
$this
->assertEquals(image_type_to_mime_type($type), $image
->getMimeType(), "Image file '{$file}' has the correct MIME type.");
$this
->assertTrue($image
->save($file_path), "Image '{$file}' created anew from a null image was saved.");
$image_reloaded = $this->imageFactory
->get($file_path, 'gd');
if (!$image_reloaded
->isValid()) {
$this
->fail("Could not load image '{$file}'.");
continue;
}
$this
->assertEquals(50, $image_reloaded
->getWidth(), "Image file '{$file}' has the correct width.");
$this
->assertEquals(20, $image_reloaded
->getHeight(), "Image file '{$file}' has the correct height.");
$this
->assertEquals(image_type_to_mime_type($type), $image_reloaded
->getMimeType(), "Image file '{$file}' has the correct MIME type.");
if ($image_reloaded
->getToolkit()
->getType() == IMAGETYPE_GIF) {
$this
->assertEquals('#ffff00', $image_reloaded
->getToolkit()
->getTransparentColor(), "Image file '{$file}' has the correct transparent color channel set.");
}
else {
$this
->assertEquals(NULL, $image_reloaded
->getToolkit()
->getTransparentColor(), "Image file '{$file}' has no color channel set.");
}
}
$file_names = [
'greek εικόνα δοκιμής.png',
'russian Тестовое изображение.png',
'simplified chinese 测试图片.png',
'japanese 試験画像.png',
'arabic صورة الاختبار.png',
'armenian փորձարկման պատկերը.png',
'bengali পরীক্ষা ইমেজ.png',
'hebraic תמונת בדיקה.png',
'hindi परीक्षण छवि.png',
'viet hình ảnh thử nghiệm.png',
'viet \'with quotes\' hình ảnh thử nghiệm.png',
'viet "with double quotes" hình ảnh thử nghiệm.png',
];
foreach ($file_names as $file) {
if (substr(PHP_OS, 0, 3) === 'WIN' && $toolkit_settings['binaries'] === 'graphicsmagick') {
continue;
}
if (substr(PHP_OS, 0, 3) === 'WIN' && preg_match('/[:*?"<>|]/', $file)) {
continue;
}
$image = $this->imageFactory
->get();
$this
->assertTrue($image
->createNew(50, 20, 'png'));
$file_path = $this->testDirectory . '/' . $file;
$this
->assertTrue($image
->save($file_path), $file);
$image_reloaded = $this->imageFactory
->get($file_path, 'gd');
$this
->assertTrue($image_reloaded
->isValid(), "Image file '{$file}' loaded successfully.");
}
$image = $this->imageFactory
->get('dummy-remote://image-test.png');
$this
->assertEquals(filesize('dummy-remote://image-test.png'), filesize($image
->getToolkit()
->arguments()
->getSourceLocalPath()));
$image
->desaturate();
$this
->assertTrue($image
->save('dummy-remote://remote-image-test.png'));
$this
->assertTrue(file_exists($image
->getToolkit()
->arguments()
->getDestination()));
$this
->assertEquals('dummy-remote://remote-image-test.png', $image
->getToolkit()
->arguments()
->getDestination());
$this
->assertSame('', $image
->getToolkit()
->arguments()
->getDestinationLocalPath());
$this->fileSystem
->copy(drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg', 'public://', FileSystemInterface::EXISTS_REPLACE);
$image_files = [
[
'path' => drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg',
'orientation' => 8,
],
[
'path' => 'public://test-exif.jpeg',
'orientation' => 8,
],
[
'path' => 'dummy-remote://test-exif.jpeg',
'orientation' => 8,
],
[
'path' => 'public://image-test.jpg',
'orientation' => NULL,
],
[
'path' => 'public://image-test.png',
'orientation' => NULL,
],
[
'path' => 'public://image-test.gif',
'orientation' => NULL,
],
[
'path' => NULL,
'orientation' => NULL,
],
];
foreach ($image_files as $image_file) {
$image = $this->imageFactory
->get($image_file['path']);
$this
->assertSame($image_file['orientation'], $image
->getToolkit()
->getExifOrientation());
}
$image_files = [
[
'source' => drupal_get_path('module', 'imagemagick') . '/misc/test-multi-frame.gif',
'destination' => $this->testDirectory . '/test-multi-frame.gif',
'width' => 60,
'height' => 29,
'frames' => 13,
'scaled_width' => 30,
'scaled_height' => 15,
'rotated_width' => 33,
'rotated_height' => 26,
],
];
foreach ($image_files as $image_file) {
$image = $this->imageFactory
->get($image_file['source']);
$this
->assertSame($image_file['width'], $image
->getWidth());
$this
->assertSame($image_file['height'], $image
->getHeight());
$this
->assertSame($image_file['frames'], $image
->getToolkit()
->getFrames());
$image
->scale(30);
$this
->assertTrue($image
->save($image_file['destination']));
$image = $this->imageFactory
->get($image_file['destination']);
$this
->assertSame($image_file['scaled_width'], $image
->getWidth());
$this
->assertSame($image_file['scaled_height'], $image
->getHeight());
$this
->assertSame($image_file['frames'], $image
->getToolkit()
->getFrames());
$image
->rotate(24);
$this
->assertTrue($image
->save($image_file['destination']));
$image = $this->imageFactory
->get($image_file['destination']);
$this
->assertSame($image_file['rotated_width'], $image
->getWidth());
$this
->assertSame($image_file['rotated_height'], $image
->getHeight());
$this
->assertSame($image_file['frames'], $image
->getToolkit()
->getFrames());
$image
->convert('png');
$this
->assertTrue($image
->save($image_file['destination']));
$image = $this->imageFactory
->get($image_file['destination']);
$this
->assertSame(1, $image
->getToolkit()
->getFrames());
$this
->assertSame($image_file['rotated_width'], $image
->getWidth());
$this
->assertSame($image_file['rotated_height'], $image
->getHeight());
$this
->assertSame(1, $image
->getToolkit()
->getFrames());
}
}
protected function getPixelColor(ImageInterface $image, int $x, int $y) : array {
$toolkit = $image
->getToolkit();
$color_index = imagecolorat($toolkit
->getResource(), $x, $y);
$transparent_index = imagecolortransparent($toolkit
->getResource());
if ($color_index == $transparent_index) {
return [
0,
0,
0,
127,
];
}
return array_values(imagecolorsforindex($toolkit
->getResource(), $color_index));
}
protected function assertColorsAreClose(array $expected, array $actual, int $tolerance, string $file, string $op) : void {
if ($actual[3] == 127 && $expected[3] == 127) {
return;
}
$distance = pow($actual[0] - $expected[0], 2) + pow($actual[1] - $expected[1], 2) + pow($actual[2] - $expected[2], 2) + pow($actual[3] - $expected[3], 2);
$this
->assertLessThanOrEqual($tolerance, $distance, "Actual: {" . implode(',', $actual) . "}, Expected: {" . implode(',', $expected) . "}, Distance: " . $distance . ", Tolerance: " . $tolerance . ", File: " . $file . ", Operation: " . $op);
}
}