MDL-84128 core: Flip the image according to its orientation in PHP GD
This commit is contained in:
@@ -63,6 +63,30 @@ class exifremover_service extends service implements file_redactor_service_inter
|
||||
/** @var bool $useexiftool Flag indicating whether to use ExifTool. */
|
||||
private bool $useexiftool = false;
|
||||
|
||||
/** @var int Normal orientation (no rotation). */
|
||||
private const TOP_LEFT = 1;
|
||||
|
||||
/** @var int Mirrored horizontally. */
|
||||
private const TOP_RIGHT = 2;
|
||||
|
||||
/** @var int Rotated 180° (upside down). */
|
||||
private const BOTTOM_RIGHT = 3;
|
||||
|
||||
/** @var int Mirrored vertically. */
|
||||
private const BOTTOM_LEFT = 4;
|
||||
|
||||
/** @var int Mirrored horizontally and rotated 270° clockwise. */
|
||||
private const LEFT_TOP = 5;
|
||||
|
||||
/** @var int Rotated 90° clockwise. */
|
||||
private const RIGHT_TOP = 6;
|
||||
|
||||
/** @var int Mirrored horizontally and rotated 90° clockwise. */
|
||||
private const RIGHT_BOTTOM = 7;
|
||||
|
||||
/** @var int Rotated 270° clockwise. */
|
||||
private const LEFT_BOTTOM = 8;
|
||||
|
||||
/**
|
||||
* Initialise the EXIF remover service.
|
||||
*/
|
||||
@@ -161,8 +185,12 @@ class exifremover_service extends service implements file_redactor_service_inter
|
||||
* @throws moodle_exception If the image data is not successfully recreated.
|
||||
*/
|
||||
private function execute_gd(string $sourcefile): string {
|
||||
// Read EXIF data from the temporary file.
|
||||
$exifdata = @exif_read_data($sourcefile);
|
||||
$orientation = isset($exifdata['Orientation']) ? $exifdata['Orientation'] : self::TOP_LEFT;
|
||||
|
||||
$filecontent = file_get_contents($sourcefile);
|
||||
$destinationfile = $this->recreate_image_gd($filecontent);
|
||||
$destinationfile = $this->recreate_image_gd($filecontent, $orientation);
|
||||
if (!$destinationfile) {
|
||||
throw new moodle_exception(
|
||||
errorcode: 'redactor:exifremover:failedprocessgd',
|
||||
@@ -242,11 +270,13 @@ class exifremover_service extends service implements file_redactor_service_inter
|
||||
/**
|
||||
* Recreate the image using PHP GD library to strip all EXIF data.
|
||||
*
|
||||
* @param string $content The source file content
|
||||
* @param string $content The source file content.
|
||||
* @param int $orientation The orientation value. The default is 1, which means no rotation.
|
||||
* @return null|string The path to the recreated image, or null on failure.
|
||||
*/
|
||||
private function recreate_image_gd(
|
||||
string $content,
|
||||
int $orientation = self::TOP_LEFT,
|
||||
): ?string {
|
||||
// Fetch the image information for this image.
|
||||
$imageinfo = @getimagesizefromstring($content);
|
||||
@@ -256,6 +286,8 @@ class exifremover_service extends service implements file_redactor_service_inter
|
||||
// Create a new image from the file.
|
||||
$image = @imagecreatefromstring($content);
|
||||
|
||||
$this->flip_gd($image, $orientation);
|
||||
|
||||
$destinationfile = make_request_directory() . '/output';
|
||||
|
||||
// Capture the image as a string object, rather than straight to file.
|
||||
@@ -274,6 +306,44 @@ class exifremover_service extends service implements file_redactor_service_inter
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips the given GD image resource based on the specified orientation.
|
||||
*
|
||||
* @param \GDImage $image The GD image resource to be flipped.
|
||||
* @param int $orientation The orientation value indicating how the image should be flipped.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function flip_gd(\GDImage &$image, int $orientation): void {
|
||||
switch ($orientation) {
|
||||
case self::TOP_LEFT:
|
||||
break;
|
||||
case self::TOP_RIGHT:
|
||||
imageflip($image, IMG_FLIP_HORIZONTAL);
|
||||
break;
|
||||
case self::BOTTOM_RIGHT:
|
||||
$image = imagerotate($image, 180, 0);
|
||||
break;
|
||||
case self::BOTTOM_LEFT:
|
||||
imageflip($image, IMG_FLIP_VERTICAL);
|
||||
break;
|
||||
case self::LEFT_TOP:
|
||||
$image = imagerotate($image, -90, 0);
|
||||
imageflip($image, IMG_FLIP_HORIZONTAL);
|
||||
break;
|
||||
case self::RIGHT_TOP:
|
||||
$image = imagerotate($image, -90, 0);
|
||||
break;
|
||||
case self::RIGHT_BOTTOM:
|
||||
$image = imagerotate($image, 90, 0);
|
||||
imageflip($image, IMG_FLIP_HORIZONTAL);
|
||||
break;
|
||||
case self::LEFT_BOTTOM:
|
||||
$image = imagerotate($image, 90, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the service is enabled, and false if it is not.
|
||||
*
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 434 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 841 B |
@@ -67,6 +67,87 @@ final class exifremover_service_test extends \advanced_testcase {
|
||||
$this->assertStringNotContainsString('Orientation', $newexif);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the `exifremover_service` functionality to flip orientation.
|
||||
*
|
||||
* @dataProvider exifremover_service_flip_orientation_provider
|
||||
* @param string $sourcepath the path to the source image.
|
||||
* @param string $expectedpath the path to the expected image.
|
||||
* @param bool $expectedresult the expected result of the comparison.
|
||||
*/
|
||||
public function test_exifremover_service_flip_orientation_with_gd(
|
||||
string $sourcepath,
|
||||
string $expectedpath,
|
||||
bool $expectedresult
|
||||
): void {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Ensure that the exif remover tool path is not set.
|
||||
set_config('exifremovertoolpath', null, 'core_files');
|
||||
|
||||
// Flip the orientation.
|
||||
$service = new exifremover_service();
|
||||
$newfile = $service->redact_file_by_path('image/jpeg', $sourcepath);
|
||||
|
||||
// Compare the actual and expected images.
|
||||
$this->assertEquals($expectedresult, $this->compare_images($newfile, $expectedpath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_exifremover_service_flip_orientation().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function exifremover_service_flip_orientation_provider(): array {
|
||||
return [
|
||||
'Flip right-top' => [
|
||||
'sourcepath' => self::get_fixture_path('core_files', 'redactor/righttop.jpg'),
|
||||
'expectedpath' => self::get_fixture_path('core_files', 'redactor/topleft.jpg'),
|
||||
'expectedresult' => true,
|
||||
],
|
||||
'The image will not be the same after the flip process' => [
|
||||
'sourcepath' => self::get_fixture_path('core_files', 'redactor/righttop.jpg'),
|
||||
'expectedpath' => self::get_fixture_path('core_files', 'redactor/righttop.jpg'),
|
||||
'expectedresult' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two images pixel by pixel.
|
||||
*
|
||||
* @param string $image1path the path to the first image.
|
||||
* @param string $image2path the path to the second image.
|
||||
* @return bool True if the images are identical, false otherwise.
|
||||
*/
|
||||
private function compare_images(string $image1path, string $image2path): bool {
|
||||
$image1 = imagecreatefromjpeg($image1path);
|
||||
$image2 = imagecreatefromjpeg($image2path);
|
||||
|
||||
if (!$image1 || !$image2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$width1 = imagesx($image1);
|
||||
$height1 = imagesy($image1);
|
||||
$width2 = imagesx($image2);
|
||||
$height2 = imagesy($image2);
|
||||
|
||||
if ($width1 !== $width2 || $height1 !== $height2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ($x = 0; $x < $width1; $x++) {
|
||||
for ($y = 0; $y < $height1; $y++) {
|
||||
if (imagecolorat($image1, $x, $y) !== imagecolorat($image2, $x, $y)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the `exifremover_service` functionality using ExifTool.
|
||||
*
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ $string['redactor'] = 'File redaction';
|
||||
$string['redactor:exifremover'] = 'EXIF remover';
|
||||
$string['redactor:exifremover:emptyremovetags'] = 'Remove tags can not be empty!';
|
||||
$string['redactor:exifremover:enabled'] = 'Enable EXIF remover';
|
||||
$string['redactor:exifremover:enabled_desc'] = 'By default, EXIF Remover only supports JPG files using PHP GD, or ExifTool if it is configured.
|
||||
This degrades the quality of the image and removes the orientation tag.
|
||||
$string['redactor:exifremover:enabled_desc'] = 'By default, EXIF Remover only supports JPG files using PHP GD or ExifTool if configured.
|
||||
Using PHP GD for this purpose can degrade the quality of the image.
|
||||
|
||||
To enhance the performance of EXIF Remover, please configure the ExifTool settings below.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user