Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
36.54% covered (danger)
36.54%
19 / 52
14.29% covered (danger)
14.29%
1 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
XCFHandler
36.54% covered (danger)
36.54%
19 / 52
14.29% covered (danger)
14.29%
1 / 7
100.81
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
76.19% covered (warning)
76.19%
16 / 21
0.00% covered (danger)
0.00%
0 / 1
6.49
 isFileMetadataValid
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 hasGDSupport
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 * @license GPL-2.0-or-later
11 * @file
12 * @ingroup Media
13 */
14
15use MediaWiki\FileRepo\File\File;
16use Wikimedia\StringUtils\StringUtils;
17use Wikimedia\UnpackFailedException;
18
19/**
20 * Handler for the Gimp's native file format; getimagesize() doesn't
21 * support these files
22 *
23 * @ingroup Media
24 */
25class XCFHandler extends BitmapHandler {
26    /**
27     * @param File $file
28     * @return bool
29     */
30    public function mustRender( $file ) {
31        return true;
32    }
33
34    /**
35     * Render files as PNG
36     *
37     * @param string $ext
38     * @param string $mime
39     * @param array|null $params
40     * @return array
41     */
42    public function getThumbType( $ext, $mime, $params = null ) {
43        return [ 'png', 'image/png' ];
44    }
45
46    /**
47     * Metadata for a given XCF file
48     *
49     * Will return false if file magic signature is not recognized
50     * @author Hexmode
51     * @author Hashar
52     *
53     * @param string $filename Full path to a XCF file
54     * @return array|null Metadata Array just like PHP getimagesize()
55     */
56    private static function getXCFMetaData( $filename ) {
57        # Decode master structure
58        $f = fopen( $filename, 'rb' );
59        if ( !$f ) {
60            return null;
61        }
62        # The image structure always starts at offset 0 in the XCF file.
63        # So we just read it :-)
64        $binaryHeader = fread( $f, 26 );
65        fclose( $f );
66
67        /**
68         * Master image structure:
69         *
70         * byte[9] "gimp xcf "  File type magic
71         * byte[4] version      XCF version
72         *                        "file" - version 0
73         *                        "v001" - version 1
74         *                        "v002" - version 2
75         * byte    0            Zero-terminator for version tag
76         * uint32  width        With of canvas
77         * uint32  height       Height of canvas
78         * uint32  base_type    Color mode of the image; one of
79         *                         0: RGB color
80         *                         1: Grayscale
81         *                         2: Indexed color
82         *        (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h)
83         */
84        try {
85            $header = StringUtils::unpack(
86                "A9magic" . # A: space padded
87                    "/a5version" . # a: zero padded
88                    "/Nwidth" . # \
89                    "/Nheight" . # N: unsigned long 32bit big endian
90                    "/Nbase_type", # /
91                $binaryHeader
92            );
93        } catch ( UnpackFailedException ) {
94            return null;
95        }
96
97        # Check values
98        if ( $header['magic'] !== 'gimp xcf' ) {
99            wfDebug( __METHOD__ . " '$filename' has invalid magic signature." );
100
101            return null;
102        }
103        # TODO: we might want to check for correct values of width and height
104
105        wfDebug( __METHOD__ .
106            ": canvas size of '$filename' is {$header['width']} x {$header['height']} px" );
107
108        return $header;
109    }
110
111    /** @inheritDoc */
112    public function getSizeAndMetadata( $state, $filename ) {
113        $header = self::getXCFMetaData( $filename );
114        $metadata = [];
115        if ( $header ) {
116            // Try to be consistent with the names used by PNG files.
117            // Unclear from base media type if it has an alpha layer,
118            // so just assume that it does since it "potentially" could.
119            switch ( $header['base_type'] ) {
120                case 0:
121                    $metadata['colorType'] = 'truecolour-alpha';
122                    break;
123                case 1:
124                    $metadata['colorType'] = 'greyscale-alpha';
125                    break;
126                case 2:
127                    $metadata['colorType'] = 'index-coloured';
128                    break;
129                default:
130                    $metadata['colorType'] = 'unknown';
131            }
132        } else {
133            // Marker to prevent repeated attempted extraction
134            $metadata['error'] = true;
135        }
136        return [
137            'width' => $header['width'] ?? 0,
138            'height' => $header['height'] ?? 0,
139            'bits' => 8,
140            'metadata' => $metadata
141        ];
142    }
143
144    /**
145     * Should we refresh the metadata
146     *
147     * @param File $file The file object for the file in question
148     * @return bool|int One of the self::METADATA_(BAD|GOOD|COMPATIBLE) constants
149     */
150    public function isFileMetadataValid( $file ) {
151        if ( !$file->getMetadataArray() ) {
152            // Old metadata when we just put an empty string in there
153            return self::METADATA_BAD;
154        }
155
156        return self::METADATA_GOOD;
157    }
158
159    /** @inheritDoc */
160    protected function hasGDSupport() {
161        return false;
162    }
163
164    /**
165     * Can we render this file?
166     *
167     * Image magick doesn't support indexed xcf files as of current
168     * writing (as of 6.8.9-3)
169     * @param File $file
170     * @return bool
171     */
172    public function canRender( $file ) {
173        $xcfMeta = $file->getMetadataArray();
174        if ( isset( $xcfMeta['colorType'] ) && $xcfMeta['colorType'] === 'index-coloured' ) {
175            return false;
176        }
177        return parent::canRender( $file );
178    }
179}