59 self::$gifFrameSep = pack(
"C", ord(
"," ) );
60 self::$gifExtensionSep = pack(
"C", ord(
"!" ) );
61 self::$gifTerm = pack(
"C", ord(
";" ) );
70 throw new Exception(
'No file name specified' );
72 if ( !file_exists( $filename ) || is_dir( $filename ) ) {
73 throw new Exception(
"File $filename does not exist" );
76 $fh = fopen( $filename,
'rb' );
79 throw new Exception(
"Unable to open file $filename" );
83 $buf = fread( $fh, 6 );
84 if ( !( $buf ===
'GIF87a' || $buf ===
'GIF89a' ) ) {
85 throw new Exception(
"Not a valid GIF file; header: $buf" );
89 $buf = fread( $fh, 2 );
90 if ( strlen( $buf ) < 2 ) {
91 throw new Exception(
"Not a valid GIF file; Unable to read width." );
93 $width = unpack(
'v', $buf )[1];
94 $buf = fread( $fh, 2 );
95 if ( strlen( $buf ) < 2 ) {
96 throw new Exception(
"Not a valid GIF file; Unable to read height." );
98 $height = unpack(
'v', $buf )[1];
101 $buf = fread( $fh, 1 );
102 [ $bpp, $have_map ] = self::decodeBPP( $buf );
110 self::readGCT( $fh, $bpp );
113 while ( !feof( $fh ) ) {
114 $buf = fread( $fh, 1 );
116 if ( $buf === self::$gifFrameSep ) {
120 # # Skip bounding box
125 $buf = fread( $fh, 1 );
126 [ $bpp, $have_map ] = self::decodeBPP( $buf );
130 self::readGCT( $fh, $bpp );
134 self::skipBlock( $fh );
135 } elseif ( $buf === self::$gifExtensionSep ) {
136 $buf = fread( $fh, 1 );
137 if ( strlen( $buf ) < 1 ) {
138 throw new Exception(
"Not a valid GIF file; Unable to read graphics control extension." );
140 $extension_code = unpack(
'C', $buf )[1];
142 if ( $extension_code === 0xF9 ) {
151 $buf = fread( $fh, 2 );
152 if ( strlen( $buf ) < 2 ) {
153 throw new Exception(
"Not a valid GIF file; Unable to read delay" );
155 $delay = unpack(
'v', $buf )[1];
156 $duration += $delay * 0.01;
161 $term = fread( $fh, 1 );
162 if ( strlen( $term ) < 1 ) {
163 throw new Exception(
"Not a valid GIF file; Unable to read terminator byte" );
165 $term = unpack(
'C', $term )[1];
167 throw new Exception(
"Malformed Graphics Control Extension block" );
169 } elseif ( $extension_code === 0xFE ) {
171 $data = self::readBlock( $fh );
172 if ( $data ===
"" ) {
173 throw new Exception(
'Read error, zero-length comment block' );
181 UtfNormal\Validator::quickIsNFCVerify( $dataCopy );
183 if ( $dataCopy !== $data ) {
184 AtEase::suppressWarnings();
185 $data = iconv(
'windows-1252',
'UTF-8', $data );
186 AtEase::restoreWarnings();
189 $commentCount = count( $comment );
190 if ( $commentCount === 0
192 || $comment[$commentCount - 1] !== $data
199 } elseif ( $extension_code === 0xFF ) {
202 $blockLength = fread( $fh, 1 );
203 if ( strlen( $blockLength ) < 1 ) {
204 throw new Exception(
"Not a valid GIF file; Unable to read block length" );
206 $blockLength = unpack(
'C', $blockLength )[1];
207 $data = fread( $fh, $blockLength );
209 if ( $blockLength !== 11 ) {
210 wfDebug( __METHOD__ .
" GIF application block with wrong length" );
211 fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
212 self::skipBlock( $fh );
217 if ( $data ===
'NETSCAPE2.0' ) {
218 $data = fread( $fh, 2 );
220 if ( $data !==
"\x03\x01" ) {
221 throw new Exception(
"Expected \x03\x01, got $data" );
225 $loopData = fread( $fh, 2 );
226 if ( strlen( $loopData ) < 2 ) {
227 throw new Exception(
"Not a valid GIF file; Unable to read loop count" );
229 $loopCount = unpack(
'v', $loopData )[1];
231 if ( $loopCount !== 1 ) {
238 } elseif ( $data ===
'XMP DataXMP' ) {
242 $xmp = self::readBlock( $fh,
true );
244 if ( substr( $xmp, -257, 3 ) !==
"\x01\xFF\xFE"
245 || substr( $xmp, -4 ) !==
"\x03\x02\x01\x00"
247 throw new Exception(
"XMP does not have magic trailer!" );
251 $xmp = substr( $xmp, 0, -257 );
254 fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
255 self::skipBlock( $fh );
258 self::skipBlock( $fh );
260 } elseif ( $buf === self::$gifTerm ) {
263 if ( strlen( $buf ) < 1 ) {
264 throw new Exception(
"Not a valid GIF file; Unable to read unknown byte." );
266 $byte = unpack(
'C', $buf )[1];
267 throw new Exception(
"At position: " . ftell( $fh ) .
", Unknown byte " . $byte );
272 'frameCount' => $frameCount,
273 'looped' => $isLooped,
274 'duration' => $duration,
276 'comment' => $comment,