From a428e3f067ee5b7eececc2a30a7ecb516425db4b Mon Sep 17 00:00:00 2001 From: Matthias Mullie Date: Tue, 30 Aug 2016 16:36:09 +0200 Subject: [PATCH] Embed TinyRGB color profile when JPG EXIF Color Space = sRGB but no profile embedded Existing srgb.jpg & tinyrgb.jpg have been replaced to be able to easily compare a "fixed" missingprofile.jpg to tinyrgb.jpg. With the existing files, when the tinyrgb profile was added to missingprofile.jpg, it would end up basically the same as tinyrgb.jpg, except that not all the exif data would be in the exact same order. I've rebuilt srgb.jpg & tinyrgb.jpg by first removing their profile (which is what missingprofile.jpg is), and then copying it over again: exiftool -tagsfromfile srgb.jpg -ICC_Profile new_srgb.jpg Meanwhile also moved the profile-swapping code to JpegHandler, as it was jpeg-specific. Bug: T134498 Change-Id: I722dd6f66f6007182ad9a215e5eb382776983c05 --- includes/media/ExifBitmap.php | 72 ----------- includes/media/Jpeg.php | 116 ++++++++++++++++++ tests/phpunit/data/media/adobergb.jpg | Bin 0 -> 5308 bytes tests/phpunit/data/media/missingprofile.jpg | Bin 0 -> 4576 bytes tests/phpunit/data/media/srgb.jpg | Bin 7738 -> 7738 bytes tests/phpunit/data/media/tinyrgb.jpg | Bin 5118 -> 5118 bytes .../phpunit/includes/media/ExifBitmapTest.php | 57 --------- tests/phpunit/includes/media/JpegTest.php | 69 +++++++++++ 8 files changed, 185 insertions(+), 129 deletions(-) create mode 100644 tests/phpunit/data/media/adobergb.jpg create mode 100644 tests/phpunit/data/media/missingprofile.jpg diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php index 7aeefa02e4..0e10abb9f9 100644 --- a/includes/media/ExifBitmap.php +++ b/includes/media/ExifBitmap.php @@ -30,7 +30,6 @@ class ExifBitmapHandler extends BitmapHandler { const BROKEN_FILE = '-1'; // error extracting metadata const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata. - const SRGB_ICC_PROFILE_NAME = 'IEC 61966-2.1 Default RGB colour space - sRGB'; function convertMetadataVersion( $metadata, $version = 1 ) { // basically flattens arrays. @@ -243,75 +242,4 @@ class ExifBitmapHandler extends BitmapHandler { return 0; } - - protected function transformImageMagick( $image, $params ) { - global $wgUseTinyRGBForJPGThumbnails; - - $ret = parent::transformImageMagick( $image, $params ); - - if ( $ret ) { - return $ret; - } - - if ( $params['mimeType'] === 'image/jpeg' && $wgUseTinyRGBForJPGThumbnails ) { - // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller - // (and free) TinyRGB - - $this->swapICCProfile( - $params['dstPath'], - self::SRGB_ICC_PROFILE_NAME, - realpath( __DIR__ ) . '/tinyrgb.icc' - ); - } - - return false; - } - - /** - * Swaps an embedded ICC profile for another, if found. - * Depends on exiftool, no-op if not installed. - * @param string $filepath File to be manipulated (will be overwritten) - * @param string $oldProfileString Exact name of color profile to look for - * (the one that will be replaced) - * @param string $profileFilepath ICC profile file to apply to the file - * @since 1.26 - * @return bool - */ - public function swapICCProfile( $filepath, $oldProfileString, $profileFilepath ) { - global $wgExiftool; - - if ( !$wgExiftool || !is_executable( $wgExiftool ) ) { - return false; - } - - $cmd = wfEscapeShellArg( $wgExiftool, - '-DeviceModelDesc', - '-S', - '-T', - $filepath - ); - - $output = wfShellExecWithStderr( $cmd, $retval ); - - if ( $retval !== 0 || strcasecmp( trim( $output ), $oldProfileString ) !== 0 ) { - // We can't establish that this file has the expected ICC profile, don't process it - return false; - } - - $cmd = wfEscapeShellArg( $wgExiftool, - '-overwrite_original', - '-icc_profile<=' . $profileFilepath, - $filepath - ); - - $output = wfShellExecWithStderr( $cmd, $retval ); - - if ( $retval !== 0 ) { - $this->logErrorForExternalProcess( $retval, $output, $cmd ); - - return false; - } - - return true; - } } diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php index b8b6f6c987..8631553346 100644 --- a/includes/media/Jpeg.php +++ b/includes/media/Jpeg.php @@ -31,6 +31,8 @@ * @ingroup Media */ class JpegHandler extends ExifBitmapHandler { + const SRGB_EXIF_COLOR_SPACE = 'sRGB'; + const SRGB_ICC_PROFILE_DESCRIPTION = 'sRGB IEC61966-2.1'; function normaliseParams( $image, &$params ) { if ( !parent::normaliseParams( $image, $params ) ) { @@ -171,4 +173,118 @@ class JpegHandler extends ExifBitmapHandler { return $params; } + + /** + * {@inheritdoc} + */ + protected function transformImageMagick( $image, $params ) { + global $wgUseTinyRGBForJPGThumbnails; + + $ret = parent::transformImageMagick( $image, $params ); + + if ( $ret ) { + return $ret; + } + + if ( $wgUseTinyRGBForJPGThumbnails ) { + // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller + // (and free) TinyRGB + + /** + * We'll want to replace the color profile for JPGs: + * * in the sRGB color space, or with the sRGB profile + * (other profiles will be left untouched) + * * without color space or profile, in which case browsers + * should assume sRGB, but don't always do (e.g. on wide-gamut + * monitors (unless it's meant for low bandwith) + * @see https://phabricator.wikimedia.org/T134498 + */ + $colorSpaces = [ self::SRGB_EXIF_COLOR_SPACE, '-' ]; + $profiles = [ self::SRGB_ICC_PROFILE_DESCRIPTION ]; + + // we'll also add TinyRGB profile to images lacking a profile, but + // only if they're not low quality (which are meant to save bandwith + // and we don't want to increase the filesize by adding a profile) + if ( $params['quality'] > 30 ) { + $profiles[] = '-'; + } + + $this->swapICCProfile( + $params['dstPath'], + $colorSpaces, + $profiles, + realpath( __DIR__ ) . '/tinyrgb.icc' + ); + } + + return false; + } + + /** + * Swaps an embedded ICC profile for another, if found. + * Depends on exiftool, no-op if not installed. + * @param string $filepath File to be manipulated (will be overwritten) + * @param array $colorSpaces Only process files with this/these Color Space(s) + * @param array $oldProfileStrings Exact name(s) of color profile to look for + * (the one that will be replaced) + * @param string $profileFilepath ICC profile file to apply to the file + * @since 1.26 + * @return bool + */ + public function swapICCProfile( $filepath, array $colorSpaces, + array $oldProfileStrings, $profileFilepath + ) { + global $wgExiftool; + + if ( !$wgExiftool || !is_executable( $wgExiftool ) ) { + return false; + } + + $cmd = wfEscapeShellArg( $wgExiftool, + '-EXIF:ColorSpace', + '-ICC_Profile:ProfileDescription', + '-S', + '-T', + $filepath + ); + + $output = wfShellExecWithStderr( $cmd, $retval ); + + // Explode EXIF data into an array with [0 => Color Space, 1 => Device Model Desc] + $data = explode( "\t", trim( $output ) ); + + if ( $retval !== 0 ) { + return false; + } + + // Make a regex out of the source data to match it to an array of color + // spaces in a case-insensitive way + $colorSpaceRegex = '/'.preg_quote( $data[0], '/' ).'/i'; + if ( empty( preg_grep( $colorSpaceRegex, $colorSpaces ) ) ) { + // We can't establish that this file matches the color space, don't process it + return false; + } + + $profileRegex = '/'.preg_quote( $data[1], '/' ).'/i'; + if ( empty( preg_grep( $profileRegex, $oldProfileStrings ) ) ) { + // We can't establish that this file has the expected ICC profile, don't process it + return false; + } + + $cmd = wfEscapeShellArg( $wgExiftool, + '-overwrite_original', + '-icc_profile<=' . $profileFilepath, + $filepath + ); + + $output = wfShellExecWithStderr( $cmd, $retval ); + + if ( $retval !== 0 ) { + $this->logErrorForExternalProcess( $retval, $output, $cmd ); + + return false; + } + + return true; + } } diff --git a/tests/phpunit/data/media/adobergb.jpg b/tests/phpunit/data/media/adobergb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..470c2d638fb5c01442c652181eeee8387a430908 GIT binary patch literal 5308 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 LGVVC#liB|Of{V!h literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/media/missingprofile.jpg b/tests/phpunit/data/media/missingprofile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4085f0aa0e231c0ed568b96c9a3479425536fd29 GIT binary patch literal 4576 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 $metadata, 1 ); $this->assertEquals( $expected, $res ); } - - /** - * @dataProvider provideSwappingICCProfile - * @covers ExifBitmapHandler::swapICCProfile - */ - public function testSwappingICCProfile( - $sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName - ) { - global $wgExiftool; - - if ( !$wgExiftool || !is_file( $wgExiftool ) ) { - $this->markTestSkipped( "Exiftool not installed, cannot test ICC profile swapping" ); - } - - $this->setMwGlobals( 'wgUseTinyRGBForJPGThumbnails', true ); - - $sourceFilepath = $this->filePath . $sourceFilename; - $controlFilepath = $this->filePath . $controlFilename; - $profileFilepath = $this->filePath . $newProfileFilename; - $filepath = $this->getNewTempFile(); - - copy( $sourceFilepath, $filepath ); - - $file = $this->dataFile( $sourceFilename, 'image/jpeg' ); - $this->handler->swapICCProfile( $filepath, $oldProfileName, $profileFilepath ); - - $this->assertEquals( - sha1( file_get_contents( $filepath ) ), - sha1( file_get_contents( $controlFilepath ) ) - ); - } - - public function provideSwappingICCProfile() { - return [ - // File with sRGB should end up with TinyRGB - [ - 'srgb.jpg', - 'tinyrgb.jpg', - 'tinyrgb.icc', - 'IEC 61966-2.1 Default RGB colour space - sRGB' - ], - // File with TinyRGB should be left unchanged - [ - 'tinyrgb.jpg', - 'tinyrgb.jpg', - 'tinyrgb.icc', - 'IEC 61966-2.1 Default RGB colour space - sRGB' - ], - // File with no profile should be left unchanged - [ - 'test.jpg', - 'test.jpg', - 'tinyrgb.icc', - 'IEC 61966-2.1 Default RGB colour space - sRGB' - ] - ]; - } } diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php index 05aed4ab21..b0f40ef006 100644 --- a/tests/phpunit/includes/media/JpegTest.php +++ b/tests/phpunit/includes/media/JpegTest.php @@ -51,4 +51,73 @@ class JpegTest extends MediaWikiMediaTestCase { $this->assertEquals( $res, $expected ); } + + /** + * @dataProvider provideSwappingICCProfile + * @covers ExifBitmapHandler::swapICCProfile + */ + public function testSwappingICCProfile( + $sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName + ) { + global $wgExiftool; + + if ( !$wgExiftool || !is_file( $wgExiftool ) ) { + $this->markTestSkipped( "Exiftool not installed, cannot test ICC profile swapping" ); + } + + $this->setMwGlobals( 'wgUseTinyRGBForJPGThumbnails', true ); + + $sourceFilepath = $this->filePath . $sourceFilename; + $controlFilepath = $this->filePath . $controlFilename; + $profileFilepath = $this->filePath . $newProfileFilename; + $filepath = $this->getNewTempFile(); + + copy( $sourceFilepath, $filepath ); + + $file = $this->dataFile( $sourceFilename, 'image/jpeg' ); + $this->handler->swapICCProfile( + $filepath, + [ 'sRGB', '-' ], + [ $oldProfileName ], + $profileFilepath + ); + + $this->assertEquals( + sha1( file_get_contents( $filepath ) ), + sha1( file_get_contents( $controlFilepath ) ) + ); + } + + public function provideSwappingICCProfile() { + return [ + // File with sRGB should end up with TinyRGB + [ + 'srgb.jpg', + 'tinyrgb.jpg', + 'tinyrgb.icc', + 'sRGB IEC61966-2.1' + ], + // File with TinyRGB should be left unchanged + [ + 'tinyrgb.jpg', + 'tinyrgb.jpg', + 'tinyrgb.icc', + 'sRGB IEC61966-2.1' + ], + // File without profile should end up with TinyRGB + [ + 'missingprofile.jpg', + 'tinyrgb.jpg', + 'tinyrgb.icc', + 'sRGB IEC61966-2.1' + ], + // Non-sRGB file should be left untouched + [ + 'adobergb.jpg', + 'adobergb.jpg', + 'tinyrgb.icc', + 'sRGB IEC61966-2.1' + ] + ]; + } } -- 2.20.1