Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
38.46% covered (danger)
38.46%
20 / 52
14.29% covered (danger)
14.29%
1 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
XCFHandler
38.46% covered (danger)
38.46%
20 / 52
14.29% covered (danger)
14.29%
1 / 7
93.51
0.00% covered (danger)
0.00%
0 / 1
 mustRender
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getThumbType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getXCFMetaData
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
 getSizeAndMetadata
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
6.25
 isFileMetadataValid
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getScalerType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 canRender
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Handler for the Gimp's native file format (XCF)
4 *
5 * Overview:
6 *   https://en.wikipedia.org/wiki/XCF_(file_format)
7 * Specification in Gnome repository:
8 *   http://svn.gnome.org/viewvc/gimp/trunk/devel-docs/xcf.txt?view=markup
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 * http://www.gnu.org/copyleft/gpl.html
24 *
25 * @file
26 * @ingroup Media
27 */
28
29use MediaWiki\Libs\UnpackFailedException;
30
31/**
32 * Handler for the Gimp's native file format; getimagesize() doesn't
33 * support these files
34 *
35 * @ingroup Media
36 */
37class XCFHandler extends BitmapHandler {
38    /**
39     * @param File $file
40     * @return bool
41     */
42    public function mustRender( $file ) {
43        return true;
44    }
45
46    /**
47     * Render files as PNG
48     *
49     * @param string $ext
50     * @param string $mime
51     * @param array|null $params
52     * @return array
53     */
54    public function getThumbType( $ext, $mime, $params = null ) {
55        return [ 'png', 'image/png' ];
56    }
57
58    /**
59     * Metadata for a given XCF file
60     *
61     * Will return false if file magic signature is not recognized
62     * @author Hexmode
63     * @author Hashar
64     *
65     * @param string $filename Full path to a XCF file
66     * @return array|null Metadata Array just like PHP getimagesize()
67     */
68    private static function getXCFMetaData( $filename ) {
69        # Decode master structure
70        $f = fopen( $filename, 'rb' );
71        if ( !$f ) {
72            return null;
73        }
74        # The image structure always starts at offset 0 in the XCF file.
75        # So we just read it :-)
76        $binaryHeader = fread( $f, 26 );
77        fclose( $f );
78
79        /**
80         * Master image structure:
81         *
82         * byte[9] "gimp xcf "  File type magic
83         * byte[4] version      XCF version
84         *                        "file" - version 0
85         *                        "v001" - version 1
86         *                        "v002" - version 2
87         * byte    0            Zero-terminator for version tag
88         * uint32  width        With of canvas
89         * uint32  height       Height of canvas
90         * uint32  base_type    Color mode of the image; one of
91         *                         0: RGB color
92         *                         1: Grayscale
93         *                         2: Indexed color
94         *        (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h)
95         */
96        try {
97            $header = StringUtils::unpack(
98                "A9magic" . # A: space padded
99                    "/a5version" . # a: zero padded
100                    "/Nwidth" . # \
101                    "/Nheight" . # N: unsigned long 32bit big endian
102                    "/Nbase_type", # /
103                $binaryHeader
104            );
105        } catch ( UnpackFailedException $_ ) {
106            return null;
107        }
108
109        # Check values
110        if ( $header['magic'] !== 'gimp xcf' ) {
111            wfDebug( __METHOD__ . " '$filename' has invalid magic signature." );
112
113            return null;
114        }
115        # TODO: we might want to check for correct values of width and height
116
117        wfDebug( __METHOD__ .
118            ": canvas size of '$filename' is {$header['width']} x {$header['height']} px" );
119
120        return $header;
121    }
122
123    public function getSizeAndMetadata( $state, $filename ) {
124        $header = self::getXCFMetaData( $filename );
125        $metadata = [];
126        if ( $header ) {
127            // Try to be consistent with the names used by PNG files.
128            // Unclear from base media type if it has an alpha layer,
129            // so just assume that it does since it "potentially" could.
130            switch ( $header['base_type'] ) {
131                case 0:
132                    $metadata['colorType'] = 'truecolour-alpha';
133                    break;
134                case 1:
135                    $metadata['colorType'] = 'greyscale-alpha';
136                    break;
137                case 2:
138                    $metadata['colorType'] = 'index-coloured';
139                    break;
140                default:
141                    $metadata['colorType'] = 'unknown';
142            }
143        } else {
144            // Marker to prevent repeated attempted extraction
145            $metadata['error'] = true;
146        }
147        return [
148            'width' => $header['width'] ?? 0,
149            'height' => $header['height'] ?? 0,
150            'bits' => 8,
151            'metadata' => $metadata
152        ];
153    }
154
155    /**
156     * Should we refresh the metadata
157     *
158     * @param File $file The file object for the file in question
159     * @return bool|int One of the self::METADATA_(BAD|GOOD|COMPATIBLE) constants
160     */
161    public function isFileMetadataValid( $file ) {
162        if ( !$file->getMetadataArray() ) {
163            // Old metadata when we just put an empty string in there
164            return self::METADATA_BAD;
165        }
166
167        return self::METADATA_GOOD;
168    }
169
170    /**
171     * Must use "im" for XCF
172     *
173     * @param string|null $dstPath
174     * @param bool $checkDstPath
175     * @return string
176     */
177    protected function getScalerType( $dstPath, $checkDstPath = true ) {
178        return "im";
179    }
180
181    /**
182     * Can we render this file?
183     *
184     * Image magick doesn't support indexed xcf files as of current
185     * writing (as of 6.8.9-3)
186     * @param File $file
187     * @return bool
188     */
189    public function canRender( $file ) {
190        $xcfMeta = $file->getMetadataArray();
191        if ( isset( $xcfMeta['colorType'] ) && $xcfMeta['colorType'] === 'index-coloured' ) {
192            return false;
193        }
194        return parent::canRender( $file );
195    }
196}