47 self::$pngSig = pack(
"C8", 137, 80, 78, 71, 13, 10, 26, 10 );
52 self::$textChunks =
array(
53 'xml:com.adobe.xmp' =>
'xmp',
54 # Artist
is unofficial. Author
is the recommended
61 'comment' =>
'PNGFileComment',
62 'description' =>
'ImageDescription',
63 'title' =>
'ObjectName',
64 'copyright' =>
'Copyright',
65 # Source
as in original device
used to make image
66 # not
as in who gave you the image
68 'software' =>
'Software',
69 'disclaimer' =>
'Disclaimer',
70 'warning' =>
'ContentWarning',
71 'url' =>
'Identifier', # Not sure
if this is best mapping. Maybe WebStatement.
73 'creation time' =>
'DateTimeDigitized',
82 $colorType =
'unknown';
85 throw new Exception( __METHOD__ .
": No file name specified" );
86 } elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
87 throw new Exception( __METHOD__ .
": File $filename does not exist" );
90 $fh = fopen( $filename,
'rb' );
93 throw new Exception( __METHOD__ .
": Unable to open file $filename" );
97 $buf = fread( $fh, 8 );
98 if ( $buf != self::$pngSig ) {
99 throw new Exception( __METHOD__ .
": Not a valid PNG file; header: $buf" );
103 while ( !feof( $fh ) ) {
104 $buf = fread( $fh, 4 );
105 if ( !$buf || strlen( $buf ) < 4 ) {
106 throw new Exception( __METHOD__ .
": Read error" );
108 $chunk_size = unpack(
"N", $buf );
109 $chunk_size = $chunk_size[1];
111 if ( $chunk_size < 0 ) {
112 throw new Exception( __METHOD__ .
": Chunk size too big for unpack" );
115 $chunk_type = fread( $fh, 4 );
116 if ( !$chunk_type || strlen( $chunk_type ) < 4 ) {
117 throw new Exception( __METHOD__ .
": Read error" );
120 if ( $chunk_type ==
"IHDR" ) {
122 if ( !$buf || strlen( $buf ) < $chunk_size ) {
123 throw new Exception( __METHOD__ .
": Read error" );
125 $bitDepth = ord( substr( $buf, 8, 1 ) );
128 switch ( ord( substr( $buf, 9, 1 ) ) ) {
130 $colorType =
'greyscale';
133 $colorType =
'truecolour';
136 $colorType =
'index-coloured';
139 $colorType =
'greyscale-alpha';
142 $colorType =
'truecolour-alpha';
145 $colorType =
'unknown';
148 } elseif ( $chunk_type ==
"acTL" ) {
149 $buf = fread( $fh, $chunk_size );
150 if ( !$buf || strlen( $buf ) < $chunk_size || $chunk_size < 4 ) {
151 throw new Exception( __METHOD__ .
": Read error" );
154 $actl = unpack(
"Nframes/Nplays", $buf );
155 $frameCount = $actl[
'frames'];
156 $loopCount = $actl[
'plays'];
157 } elseif ( $chunk_type ==
"fcTL" ) {
159 if ( !$buf || strlen( $buf ) < $chunk_size ) {
160 throw new Exception( __METHOD__ .
": Read error" );
162 $buf = substr( $buf, 20 );
163 if ( strlen( $buf ) < 4 ) {
164 throw new Exception( __METHOD__ .
": Read error" );
167 $fctldur = unpack(
"ndelay_num/ndelay_den", $buf );
168 if ( $fctldur[
'delay_den'] == 0 ) {
169 $fctldur[
'delay_den'] = 100;
171 if ( $fctldur[
'delay_num'] ) {
172 $duration += $fctldur[
'delay_num'] / $fctldur[
'delay_den'];
174 } elseif ( $chunk_type ==
"iTXt" ) {
179 '/^([^\x00]{1,79})\x00(\x00|\x01)\x00([^\x00]*)(.)[^\x00]*\x00(.*)$/Ds',
188 $items[1] = strtolower( $items[1] );
189 if ( !isset( self::$textChunks[$items[1]] ) ) {
191 fseek( $fh, self::$crcSize, SEEK_CUR );
195 $items[3] = strtolower( $items[3] );
196 if ( $items[3] ==
'' ) {
198 $items[3] =
'x-default';
202 if ( $items[2] ==
"\x01" ) {
203 if ( function_exists(
'gzuncompress' ) && $items[4] ===
"\x00" ) {
205 $items[5] = gzuncompress( $items[5] );
208 if ( $items[5] ===
false ) {
210 wfDebug( __METHOD__ .
' Error decompressing iTxt chunk - ' . $items[1] .
"\n" );
211 fseek( $fh, self::$crcSize, SEEK_CUR );
215 wfDebug( __METHOD__ .
' Skipping compressed png iTXt chunk due to lack of zlib,'
216 .
" or potentially invalid compression method\n" );
217 fseek( $fh, self::$crcSize, SEEK_CUR );
221 $finalKeyword = self::$textChunks[$items[1]];
222 $text[$finalKeyword][$items[3]] = $items[5];
223 $text[$finalKeyword][
'_type'] =
'lang';
226 throw new Exception( __METHOD__ .
": Read error on iTXt chunk" );
228 } elseif ( $chunk_type ==
'tEXt' ) {
232 if ( strpos( $buf,
"\x00" ) ===
false ) {
233 throw new Exception( __METHOD__ .
": Read error on tEXt chunk" );
236 list( $keyword, $content ) = explode(
"\x00", $buf, 2 );
237 if ( $keyword ===
'' || $content ===
'' ) {
238 throw new Exception( __METHOD__ .
": Read error on tEXt chunk" );
242 $keyword = strtolower( $keyword );
243 if ( !isset( self::$textChunks[$keyword] ) ) {
245 fseek( $fh, self::$crcSize, SEEK_CUR );
249 $content = iconv(
'ISO-8859-1',
'UTF-8', $content );
252 if ( $content ===
false ) {
253 throw new Exception( __METHOD__ .
": Read error (error with iconv)" );
256 $finalKeyword = self::$textChunks[$keyword];
257 $text[$finalKeyword][
'x-default'] = $content;
258 $text[$finalKeyword][
'_type'] =
'lang';
259 } elseif ( $chunk_type ==
'zTXt' ) {
260 if ( function_exists(
'gzuncompress' ) ) {
264 if ( strpos( $buf,
"\x00" ) ===
false ) {
265 throw new Exception( __METHOD__ .
": Read error on zTXt chunk" );
268 list( $keyword, $postKeyword ) = explode(
"\x00", $buf, 2 );
269 if ( $keyword ===
'' || $postKeyword ===
'' ) {
270 throw new Exception( __METHOD__ .
": Read error on zTXt chunk" );
273 $keyword = strtolower( $keyword );
275 if ( !isset( self::$textChunks[$keyword] ) ) {
277 fseek( $fh, self::$crcSize, SEEK_CUR );
280 $compression = substr( $postKeyword, 0, 1 );
281 $content = substr( $postKeyword, 1 );
282 if ( $compression !==
"\x00" ) {
283 wfDebug( __METHOD__ .
" Unrecognized compression method in zTXt ($keyword). Skipping.\n" );
284 fseek( $fh, self::$crcSize, SEEK_CUR );
289 $content = gzuncompress( $content );
292 if ( $content ===
false ) {
294 wfDebug( __METHOD__ .
' Error decompressing zTXt chunk - ' . $keyword .
"\n" );
295 fseek( $fh, self::$crcSize, SEEK_CUR );
300 $content = iconv(
'ISO-8859-1',
'UTF-8', $content );
303 if ( $content ===
false ) {
304 throw new Exception( __METHOD__ .
": Read error (error with iconv)" );
307 $finalKeyword = self::$textChunks[$keyword];
308 $text[$finalKeyword][
'x-default'] = $content;
309 $text[$finalKeyword][
'_type'] =
'lang';
311 wfDebug( __METHOD__ .
" Cannot decompress zTXt chunk due to lack of zlib. Skipping.\n" );
312 fseek( $fh, $chunk_size, SEEK_CUR );
314 } elseif ( $chunk_type ==
'tIME' ) {
316 if ( $chunk_size !== 7 ) {
317 throw new Exception( __METHOD__ .
": tIME wrong size" );
320 if ( !$buf || strlen( $buf ) < $chunk_size ) {
321 throw new Exception( __METHOD__ .
": Read error" );
325 $t = unpack(
"ny/Cm/Cd/Ch/Cmin/Cs", $buf );
326 $strTime = sprintf(
"%04d%02d%02d%02d%02d%02d",
328 $t[
'min'],
$t[
's'] );
333 $text[
'DateTime'] = $exifTime;
335 } elseif ( $chunk_type ==
'pHYs' ) {
337 if ( $chunk_size !== 9 ) {
338 throw new Exception( __METHOD__ .
": pHYs wrong size" );
342 if ( !$buf || strlen( $buf ) < $chunk_size ) {
343 throw new Exception( __METHOD__ .
": Read error" );
346 $dim = unpack(
"Nwidth/Nheight/Cunit", $buf );
347 if ( $dim[
'unit'] == 1 ) {
350 if ( $dim[
'width'] > 0 && $dim[
'height'] > 0 ) {
353 $text[
'XResolution'] = $dim[
'width']
355 $text[
'YResolution'] = $dim[
'height']
357 $text[
'ResolutionUnit'] = 3;
361 } elseif ( $chunk_type ==
"IEND" ) {
364 fseek( $fh, $chunk_size, SEEK_CUR );
366 fseek( $fh, self::$crcSize, SEEK_CUR );
370 if ( $loopCount > 1 ) {
371 $duration *= $loopCount;
374 if ( isset( $text[
'DateTimeDigitized'] ) ) {
377 if (
$name ===
'_type' ) {
402 'frameCount' => $frameCount,
403 'loopCount' => $loopCount,
404 'duration' => $duration,
406 'bitDepth' => $bitDepth,
407 'colorType' => $colorType,
420 if (
$size > self::MAX_CHUNK_SIZE ) {
421 throw new Exception( __METHOD__ .
': Chunk size of ' .
$size .
422 ' too big. Max size is: ' . self::MAX_CHUNK_SIZE );
425 return fread( $fh,
$size );