MediaWiki REL1_37
JpegHandler.php
Go to the documentation of this file.
1<?php
25
36 private const SRGB_EXIF_COLOR_SPACE = 'sRGB';
37 private const SRGB_ICC_PROFILE_DESCRIPTION = 'sRGB IEC61966-2.1';
38
39 public function normaliseParams( $image, &$params ) {
40 if ( !parent::normaliseParams( $image, $params ) ) {
41 return false;
42 }
43 if ( isset( $params['quality'] ) && !self::validateQuality( $params['quality'] ) ) {
44 return false;
45 }
46 return true;
47 }
48
49 public function validateParam( $name, $value ) {
50 if ( $name === 'quality' ) {
51 return self::validateQuality( $value );
52 } else {
53 return parent::validateParam( $name, $value );
54 }
55 }
56
61 private static function validateQuality( $value ) {
62 return $value === 'low';
63 }
64
65 public function makeParamString( $params ) {
66 // Prepend quality as "qValue-". This has to match parseParamString() below
67 $res = parent::makeParamString( $params );
68 if ( $res && isset( $params['quality'] ) ) {
69 $res = "q{$params['quality']}-$res";
70 }
71 return $res;
72 }
73
74 public function parseParamString( $str ) {
75 // $str contains "qlow-200px" or "200px" strings because thumb.php would strip the filename
76 // first - check if the string begins with "qlow-", and if so, treat it as quality.
77 // Pass the first portion, or the whole string if "qlow-" not found, to the parent
78 // The parsing must match the makeParamString() above
79 $res = false;
80 $m = false;
81 if ( preg_match( '/q([^-]+)-(.*)$/', $str, $m ) ) {
82 $v = $m[1];
83 if ( self::validateQuality( $v ) ) {
84 $res = parent::parseParamString( $m[2] );
85 if ( $res ) {
86 $res['quality'] = $v;
87 }
88 }
89 } else {
90 $res = parent::parseParamString( $str );
91 }
92 return $res;
93 }
94
95 protected function getScriptParams( $params ) {
96 $res = parent::getScriptParams( $params );
97 if ( isset( $params['quality'] ) ) {
98 $res['quality'] = $params['quality'];
99 }
100 return $res;
101 }
102
103 public function getSizeAndMetadata( $state, $filename ) {
104 try {
105 $meta = BitmapMetadataHandler::Jpeg( $filename );
106 if ( !is_array( $meta ) ) {
107 // This should never happen, but doesn't hurt to be paranoid.
108 throw new MWException( 'Metadata array is not an array' );
109 }
110 $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
111
112 $info = [
113 'width' => $meta['SOF']['width'] ?? 0,
114 'height' => $meta['SOF']['height'] ?? 0,
115 ];
116 if ( isset( $meta['SOF']['bits'] ) ) {
117 $info['bits'] = $meta['SOF']['bits'];
118 }
119 $info = $this->applyExifRotation( $info, $meta );
120 unset( $meta['SOF'] );
121 $info['metadata'] = $meta;
122 return $info;
123 } catch ( MWException $e ) {
124 // BitmapMetadataHandler throws an exception in certain exceptional
125 // cases like if file does not exist.
126 wfDebug( __METHOD__ . ': ' . $e->getMessage() );
127
128 // This used to return an integer-like string from getMetadata(),
129 // producing a value which could not be unserialized in
130 // img_metadata. The "_error" array key matches the legacy
131 // unserialization for such image rows.
132 return [ 'metadata' => [ '_error' => ExifBitmapHandler::BROKEN_FILE ] ];
133 }
134 }
135
143 public function rotate( $file, $params ) {
144 global $wgJpegTran;
145
146 $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
147
148 if ( $wgJpegTran && is_executable( $wgJpegTran ) ) {
149 $command = Shell::command( $wgJpegTran,
150 '-rotate',
151 $rotation,
152 '-outfile',
153 $params['dstPath'],
154 $params['srcPath']
155 );
156 $result = $command
157 ->includeStderr()
158 ->execute();
159 if ( $result->getExitCode() !== 0 ) {
160 $this->logErrorForExternalProcess( $result->getExitCode(),
161 $result->getStdout(),
163 );
164
165 return new MediaTransformError( 'thumbnail_error', 0, 0, $result->getStdout() );
166 }
167
168 return false;
169 } else {
170 return parent::rotate( $file, $params );
171 }
172 }
173
174 public function supportsBucketing() {
175 return true;
176 }
177
178 public function sanitizeParamsForBucketing( $params ) {
179 $params = parent::sanitizeParamsForBucketing( $params );
180
181 // Quality needs to be cleared for bucketing. Buckets need to be default quality
182 unset( $params['quality'] );
183
184 return $params;
185 }
186
190 protected function transformImageMagick( $image, $params ) {
192
193 $ret = parent::transformImageMagick( $image, $params );
194
195 if ( $ret ) {
196 return $ret;
197 }
198
200 // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller
201 // (and free) TinyRGB
202
212 $colorSpaces = [ self::SRGB_EXIF_COLOR_SPACE, '-' ];
214
215 // we'll also add TinyRGB profile to images lacking a profile, but
216 // only if they're not low quality (which are meant to save bandwith
217 // and we don't want to increase the filesize by adding a profile)
218 if ( isset( $params['quality'] ) && $params['quality'] > 30 ) {
219 $profiles[] = '-';
220 }
221
222 $this->swapICCProfile(
223 $params['dstPath'],
224 $colorSpaces,
225 $profiles,
226 realpath( __DIR__ ) . '/tinyrgb.icc'
227 );
228 }
229
230 return false;
231 }
232
244 public function swapICCProfile( $filepath, array $colorSpaces,
245 array $oldProfileStrings, $profileFilepath
246 ) {
247 global $wgExiftool;
248
249 if ( !$wgExiftool || !is_executable( $wgExiftool ) ) {
250 return false;
251 }
252
253 $result = Shell::command(
255 '-EXIF:ColorSpace',
256 '-ICC_Profile:ProfileDescription',
257 '-S',
258 '-T',
259 $filepath
260 )
261 ->includeStderr()
262 ->execute();
263
264 // Explode EXIF data into an array with [0 => Color Space, 1 => Device Model Desc]
265 $data = explode( "\t", trim( $result->getStdout() ) );
266
267 if ( $result->getExitCode() !== 0 ) {
268 return false;
269 }
270
271 // Make a regex out of the source data to match it to an array of color
272 // spaces in a case-insensitive way
273 $colorSpaceRegex = '/' . preg_quote( $data[0], '/' ) . '/i';
274 if ( empty( preg_grep( $colorSpaceRegex, $colorSpaces ) ) ) {
275 // We can't establish that this file matches the color space, don't process it
276 return false;
277 }
278
279 $profileRegex = '/' . preg_quote( $data[1], '/' ) . '/i';
280 if ( empty( preg_grep( $profileRegex, $oldProfileStrings ) ) ) {
281 // We can't establish that this file has the expected ICC profile, don't process it
282 return false;
283 }
284
285 $command = Shell::command( $wgExiftool,
286 '-overwrite_original',
287 '-icc_profile<=' . $profileFilepath,
288 $filepath
289 );
290 $result = $command
291 ->includeStderr()
292 ->execute();
293
294 if ( $result->getExitCode() !== 0 ) {
295 $this->logErrorForExternalProcess( $result->getExitCode(),
296 $result->getStdout(),
298 );
299
300 return false;
301 }
302
303 return true;
304 }
305}
$wgUseTinyRGBForJPGThumbnails
When this variable is true and JPGs use the sRGB ICC profile, swaps it for the more lightweight (and ...
$wgJpegTran
used for lossless jpeg rotation
$wgExiftool
Path to exiftool binary.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
static Jpeg( $filename)
Main entry point for jpeg's.
Stuff specific to JPEG and (built-in) TIFF handler.
applyExifRotation( $info, $metadata)
getRotation( $file)
On supporting image formats, try to read out the low-level orientation of the file and return the ang...
const BROKEN_FILE
Error extracting metadata.
static version()
#-
Definition Exif.php:590
JPEG specific handler.
makeParamString( $params)
Merge a parameter array into a string appropriate for inclusion in filenames.stringto overrideto over...
validateParam( $name, $value)
Validate a thumbnail parameter at parse time.Return true to accept the parameter, and false to reject...
const SRGB_ICC_PROFILE_DESCRIPTION
getScriptParams( $params)
to override
swapICCProfile( $filepath, array $colorSpaces, array $oldProfileStrings, $profileFilepath)
Swaps an embedded ICC profile for another, if found.
static validateQuality( $value)
Validate and normalize quality value to be between 1 and 100 (inclusive).
getSizeAndMetadata( $state, $filename)
Get image size information and metadata array.
supportsBucketing()
Returns whether or not this handler supports the chained generation of thumbnails according to bucket...
rotate( $file, $params)
const SRGB_EXIF_COLOR_SPACE
parseParamString( $str)
Parse a param string made with makeParamString back into an array.array|bool Array of parameters or f...
sanitizeParamsForBucketing( $params)
Returns a normalised params array for which parameters have been cleaned up for bucketing purposes....
normaliseParams( $image, &$params)
transformImageMagick( $image, $params)
Transform an image using ImageMagick.to overrideMediaTransformError|false Error object if error occur...
MediaWiki exception.
logErrorForExternalProcess( $retval, $err, $cmd)
Log an error that occurred in an external process.
Basic media transform error class.
Executes shell commands.
Definition Shell.php:45
$command
Definition mcc.php:125
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42