XCFHandler.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. /**
  29. * Handler for the Gimp's native file format; getimagesize() doesn't
  30. * support these files
  31. *
  32. * @ingroup Media
  33. */
  34. class XCFHandler extends BitmapHandler {
  35. /**
  36. * @param File $file
  37. * @return bool
  38. */
  39. public function mustRender( $file ) {
  40. return true;
  41. }
  42. /**
  43. * Render files as PNG
  44. *
  45. * @param string $ext
  46. * @param string $mime
  47. * @param array|null $params
  48. * @return array
  49. */
  50. public function getThumbType( $ext, $mime, $params = null ) {
  51. return [ 'png', 'image/png' ];
  52. }
  53. /**
  54. * Get width and height from the XCF header.
  55. *
  56. * @param File|FSFile $image
  57. * @param string $filename
  58. * @return array|false
  59. */
  60. function getImageSize( $image, $filename ) {
  61. $header = self::getXCFMetaData( $filename );
  62. if ( !$header ) {
  63. return false;
  64. }
  65. # Forge a return array containing metadata information just like getimagesize()
  66. # See PHP documentation at: https://www.php.net/getimagesize
  67. return [
  68. 0 => $header['width'],
  69. 1 => $header['height'],
  70. 2 => null, # IMAGETYPE constant, none exist for XCF.
  71. 3 => "height=\"{$header['height']}\" width=\"{$header['width']}\"",
  72. 'mime' => 'image/x-xcf',
  73. 'channels' => null,
  74. 'bits' => 8, # Always 8-bits per color
  75. ];
  76. }
  77. /**
  78. * Metadata for a given XCF file
  79. *
  80. * Will return false if file magic signature is not recognized
  81. * @author Hexmode
  82. * @author Hashar
  83. *
  84. * @param string $filename Full path to a XCF file
  85. * @return bool|array Metadata Array just like PHP getimagesize()
  86. */
  87. static function getXCFMetaData( $filename ) {
  88. # Decode master structure
  89. $f = fopen( $filename, 'rb' );
  90. if ( !$f ) {
  91. return false;
  92. }
  93. # The image structure always starts at offset 0 in the XCF file.
  94. # So we just read it :-)
  95. $binaryHeader = fread( $f, 26 );
  96. fclose( $f );
  97. /**
  98. * Master image structure:
  99. *
  100. * byte[9] "gimp xcf " File type magic
  101. * byte[4] version XCF version
  102. * "file" - version 0
  103. * "v001" - version 1
  104. * "v002" - version 2
  105. * byte 0 Zero-terminator for version tag
  106. * uint32 width With of canvas
  107. * uint32 height Height of canvas
  108. * uint32 base_type Color mode of the image; one of
  109. * 0: RGB color
  110. * 1: Grayscale
  111. * 2: Indexed color
  112. * (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h)
  113. */
  114. try {
  115. $header = wfUnpack(
  116. "A9magic" . # A: space padded
  117. "/a5version" . # a: zero padded
  118. "/Nwidth" . # \
  119. "/Nheight" . # N: unsigned long 32bit big endian
  120. "/Nbase_type", # /
  121. $binaryHeader
  122. );
  123. } catch ( Exception $mwe ) {
  124. return false;
  125. }
  126. # Check values
  127. if ( $header['magic'] !== 'gimp xcf' ) {
  128. wfDebug( __METHOD__ . " '$filename' has invalid magic signature.\n" );
  129. return false;
  130. }
  131. # TODO: we might want to check for sane values of width and height
  132. wfDebug( __METHOD__ .
  133. ": canvas size of '$filename' is {$header['width']} x {$header['height']} px\n" );
  134. return $header;
  135. }
  136. /**
  137. * Store the channel type
  138. *
  139. * Greyscale files need different command line options.
  140. *
  141. * @param File|FSFile $file The image object, or false if there isn't one.
  142. * Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
  143. * @param string $filename
  144. * @return string
  145. */
  146. public function getMetadata( $file, $filename ) {
  147. $header = self::getXCFMetaData( $filename );
  148. $metadata = [];
  149. if ( $header ) {
  150. // Try to be consistent with the names used by PNG files.
  151. // Unclear from base media type if it has an alpha layer,
  152. // so just assume that it does since it "potentially" could.
  153. switch ( $header['base_type'] ) {
  154. case 0:
  155. $metadata['colorType'] = 'truecolour-alpha';
  156. break;
  157. case 1:
  158. $metadata['colorType'] = 'greyscale-alpha';
  159. break;
  160. case 2:
  161. $metadata['colorType'] = 'index-coloured';
  162. break;
  163. default:
  164. $metadata['colorType'] = 'unknown';
  165. }
  166. } else {
  167. // Marker to prevent repeated attempted extraction
  168. $metadata['error'] = true;
  169. }
  170. return serialize( $metadata );
  171. }
  172. /**
  173. * Should we refresh the metadata
  174. *
  175. * @param File $file The file object for the file in question
  176. * @param string $metadata Serialized metadata
  177. * @return bool|int One of the self::METADATA_(BAD|GOOD|COMPATIBLE) constants
  178. */
  179. public function isMetadataValid( $file, $metadata ) {
  180. if ( !$metadata ) {
  181. // Old metadata when we just put an empty string in there
  182. return self::METADATA_BAD;
  183. } else {
  184. return self::METADATA_GOOD;
  185. }
  186. }
  187. /**
  188. * Must use "im" for XCF
  189. *
  190. * @param string $dstPath
  191. * @param bool $checkDstPath
  192. * @return string
  193. */
  194. protected function getScalerType( $dstPath, $checkDstPath = true ) {
  195. return "im";
  196. }
  197. /**
  198. * Can we render this file?
  199. *
  200. * Image magick doesn't support indexed xcf files as of current
  201. * writing (as of 6.8.9-3)
  202. * @param File $file
  203. * @return bool
  204. */
  205. public function canRender( $file ) {
  206. Wikimedia\suppressWarnings();
  207. $xcfMeta = unserialize( $file->getMetadata() );
  208. Wikimedia\restoreWarnings();
  209. if ( isset( $xcfMeta['colorType'] ) && $xcfMeta['colorType'] === 'index-coloured' ) {
  210. return false;
  211. }
  212. return parent::canRender( $file );
  213. }
  214. }