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