Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
55.93% covered (warning)
55.93%
33 / 59
20.00% covered (danger)
20.00%
2 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
PNGHandler
55.93% covered (warning)
55.93%
33 / 59
20.00% covered (danger)
20.00%
2 / 10
107.02
0.00% covered (danger)
0.00%
0 / 1
 getSizeAndMetadata
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
3.00
 formatMetadata
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getCommonMetaArray
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 isAnimatedImage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 canAnimateThumbnail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMetadataType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isFileMetadataValid
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
6.22
 getLongDesc
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 getLength
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 supportsBucketing
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Handler for PNG images.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Media
22 */
23
24use MediaWiki\Context\IContextSource;
25use Wikimedia\RequestTimeout\TimeoutException;
26
27/**
28 * Handler for PNG images.
29 *
30 * @ingroup Media
31 */
32class PNGHandler extends BitmapHandler {
33    private const BROKEN_FILE = '0';
34
35    /**
36     * @param MediaHandlerState $state
37     * @param string $filename
38     * @return array
39     */
40    public function getSizeAndMetadata( $state, $filename ) {
41        try {
42            $metadata = BitmapMetadataHandler::PNG( $filename );
43        } catch ( TimeoutException $e ) {
44            throw $e;
45        } catch ( Exception $e ) {
46            // Broken file?
47            wfDebug( __METHOD__ . ': ' . $e->getMessage() );
48
49            return [ 'metadata' => [ '_error' => self::BROKEN_FILE ] ];
50        }
51
52        return [
53            'width' => $metadata['width'],
54            'height' => $metadata['height'],
55            'bits' => $metadata['bitDepth'],
56            'metadata' => array_diff_key(
57                $metadata,
58                [ 'width' => true, 'height' => true, 'bits' => true ]
59            )
60        ];
61    }
62
63    /**
64     * @param File $image
65     * @param IContextSource|false $context
66     * @return array[]|false
67     */
68    public function formatMetadata( $image, $context = false ) {
69        $meta = $this->getCommonMetaArray( $image );
70        if ( !$meta ) {
71            return false;
72        }
73
74        return $this->formatMetadataHelper( $meta, $context );
75    }
76
77    /**
78     * Get a file type independent array of metadata.
79     *
80     * @param File $image
81     * @return array The metadata array
82     */
83    public function getCommonMetaArray( File $image ) {
84        $meta = $image->getMetadataArray();
85
86        if ( !isset( $meta['metadata'] ) ) {
87            return [];
88        }
89        unset( $meta['metadata']['_MW_PNG_VERSION'] );
90
91        return $meta['metadata'];
92    }
93
94    /**
95     * @param File $image
96     * @return bool
97     */
98    public function isAnimatedImage( $image ) {
99        $metadata = $image->getMetadataArray();
100        return isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 1;
101    }
102
103    /**
104     * We do not support making APNG thumbnails, so always false
105     * @param File $image
106     * @return bool False
107     */
108    public function canAnimateThumbnail( $image ) {
109        return false;
110    }
111
112    public function getMetadataType( $image ) {
113        return 'parsed-png';
114    }
115
116    public function isFileMetadataValid( $image ) {
117        $data = $image->getMetadataArray();
118        if ( $data === [ '_error' => self::BROKEN_FILE ] ) {
119            // Do not repetitively regenerate metadata on broken file.
120            return self::METADATA_GOOD;
121        }
122
123        if ( !$data || isset( $data['_error'] ) ) {
124            wfDebug( __METHOD__ . " invalid png metadata" );
125
126            return self::METADATA_BAD;
127        }
128
129        if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
130            || $data['metadata']['_MW_PNG_VERSION'] !== PNGMetadataExtractor::VERSION
131        ) {
132            wfDebug( __METHOD__ . " old but compatible png metadata" );
133
134            return self::METADATA_COMPATIBLE;
135        }
136
137        return self::METADATA_GOOD;
138    }
139
140    /**
141     * @param File $image
142     * @return string
143     */
144    public function getLongDesc( $image ) {
145        global $wgLang;
146        $original = parent::getLongDesc( $image );
147
148        $metadata = $image->getMetadataArray();
149
150        if ( !$metadata || isset( $metadata['_error'] ) || $metadata['frameCount'] <= 0 ) {
151            return $original;
152        }
153
154        $info = [];
155        $info[] = $original;
156
157        if ( $metadata['loopCount'] == 0 ) {
158            $info[] = wfMessage( 'file-info-png-looped' )->parse();
159        } elseif ( $metadata['loopCount'] > 1 ) {
160            $info[] = wfMessage( 'file-info-png-repeat' )->numParams( $metadata['loopCount'] )->parse();
161        }
162
163        if ( $metadata['frameCount'] > 0 ) {
164            $info[] = wfMessage( 'file-info-png-frames' )->numParams( $metadata['frameCount'] )->parse();
165        }
166
167        if ( $metadata['duration'] ) {
168            $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
169        }
170
171        return $wgLang->commaList( $info );
172    }
173
174    /**
175     * Return the duration of an APNG file.
176     *
177     * Shown in the &query=imageinfo&iiprop=size api query.
178     *
179     * @param File $file
180     * @return float The duration of the file.
181     */
182    public function getLength( $file ) {
183        $metadata = $file->getMetadataArray();
184
185        if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
186            return 0.0;
187        }
188
189        return (float)$metadata['duration'];
190    }
191
192    // PNGs should be easy to support, but it will need some sharpening applied
193    // and another user test to check if the perceived quality change is noticeable
194    public function supportsBucketing() {
195        return false;
196    }
197}