image.php 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
  1. <?php
  2. /**
  3. * @author Andreas Fischer <bantu@owncloud.com>
  4. * @author Bartek Przybylski <bart.p.pl@gmail.com>
  5. * @author Bart Visscher <bartv@thisnet.nl>
  6. * @author Björn Schießle <schiessle@owncloud.com>
  7. * @author Byron Marohn <combustible@live.com>
  8. * @author Christopher Schäpers <kondou@ts.unde.re>
  9. * @author Georg Ehrke <georg@owncloud.com>
  10. * @author j-ed <juergen@eisfair.org>
  11. * @author Joas Schilling <nickvergessen@owncloud.com>
  12. * @author Johannes Willnecker <johannes@willnecker.com>
  13. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  14. * @author Morris Jobke <hey@morrisjobke.de>
  15. * @author Olivier Paroz <github@oparoz.com>
  16. * @author Robin Appelman <icewind@owncloud.com>
  17. * @author Scrutinizer Auto-Fixer <auto-fixer@scrutinizer-ci.com>
  18. * @author Thomas Müller <thomas.mueller@tmit.eu>
  19. * @author Thomas Tanghus <thomas@tanghus.net>
  20. *
  21. * @copyright Copyright (c) 2015, ownCloud, Inc.
  22. * @license AGPL-3.0
  23. *
  24. * This code is free software: you can redistribute it and/or modify
  25. * it under the terms of the GNU Affero General Public License, version 3,
  26. * as published by the Free Software Foundation.
  27. *
  28. * This program is distributed in the hope that it will be useful,
  29. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  31. * GNU Affero General Public License for more details.
  32. *
  33. * You should have received a copy of the GNU Affero General Public License, version 3,
  34. * along with this program. If not, see <http://www.gnu.org/licenses/>
  35. *
  36. */
  37. /**
  38. * Class for basic image manipulation
  39. */
  40. class OC_Image implements \OCP\IImage {
  41. protected $resource = false; // tmp resource.
  42. protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident.
  43. protected $mimeType = "image/png"; // Default to png
  44. protected $bitDepth = 24;
  45. protected $filePath = null;
  46. private $fileInfo;
  47. /**
  48. * @var \OCP\ILogger
  49. */
  50. private $logger;
  51. /**
  52. * Get mime type for an image file.
  53. *
  54. * @param string|null $filePath The path to a local image file.
  55. * @return string The mime type if the it could be determined, otherwise an empty string.
  56. */
  57. static public function getMimeTypeForFile($filePath) {
  58. // exif_imagetype throws "read error!" if file is less than 12 byte
  59. if ($filePath !== null && filesize($filePath) > 11) {
  60. $imageType = exif_imagetype($filePath);
  61. } else {
  62. $imageType = false;
  63. }
  64. return $imageType ? image_type_to_mime_type($imageType) : '';
  65. }
  66. /**
  67. * Constructor.
  68. *
  69. * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by
  70. * an imagecreate* function.
  71. * @param \OCP\ILogger $logger
  72. */
  73. public function __construct($imageRef = null, $logger = null) {
  74. $this->logger = $logger;
  75. if (is_null($logger)) {
  76. $this->logger = \OC::$server->getLogger();
  77. }
  78. if (!extension_loaded('gd') || !function_exists('gd_info')) {
  79. $this->logger->error(__METHOD__ . '(): GD module not installed', array('app' => 'core'));
  80. return false;
  81. }
  82. if (\OC_Util::fileInfoLoaded()) {
  83. $this->fileInfo = new finfo(FILEINFO_MIME_TYPE);
  84. }
  85. if (!is_null($imageRef)) {
  86. $this->load($imageRef);
  87. }
  88. }
  89. /**
  90. * Determine whether the object contains an image resource.
  91. *
  92. * @return bool
  93. */
  94. public function valid() { // apparently you can't name a method 'empty'...
  95. return is_resource($this->resource);
  96. }
  97. /**
  98. * Returns the MIME type of the image or an empty string if no image is loaded.
  99. *
  100. * @return string
  101. */
  102. public function mimeType() {
  103. return $this->valid() ? $this->mimeType : '';
  104. }
  105. /**
  106. * Returns the width of the image or -1 if no image is loaded.
  107. *
  108. * @return int
  109. */
  110. public function width() {
  111. return $this->valid() ? imagesx($this->resource) : -1;
  112. }
  113. /**
  114. * Returns the height of the image or -1 if no image is loaded.
  115. *
  116. * @return int
  117. */
  118. public function height() {
  119. return $this->valid() ? imagesy($this->resource) : -1;
  120. }
  121. /**
  122. * Returns the width when the image orientation is top-left.
  123. *
  124. * @return int
  125. */
  126. public function widthTopLeft() {
  127. $o = $this->getOrientation();
  128. $this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, array('app' => 'core'));
  129. switch ($o) {
  130. case -1:
  131. case 1:
  132. case 2: // Not tested
  133. case 3:
  134. case 4: // Not tested
  135. return $this->width();
  136. case 5: // Not tested
  137. case 6:
  138. case 7: // Not tested
  139. case 8:
  140. return $this->height();
  141. }
  142. return $this->width();
  143. }
  144. /**
  145. * Returns the height when the image orientation is top-left.
  146. *
  147. * @return int
  148. */
  149. public function heightTopLeft() {
  150. $o = $this->getOrientation();
  151. $this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, array('app' => 'core'));
  152. switch ($o) {
  153. case -1:
  154. case 1:
  155. case 2: // Not tested
  156. case 3:
  157. case 4: // Not tested
  158. return $this->height();
  159. case 5: // Not tested
  160. case 6:
  161. case 7: // Not tested
  162. case 8:
  163. return $this->width();
  164. }
  165. return $this->height();
  166. }
  167. /**
  168. * Outputs the image.
  169. *
  170. * @param string $mimeType
  171. * @return bool
  172. */
  173. public function show($mimeType = null) {
  174. if ($mimeType === null) {
  175. $mimeType = $this->mimeType();
  176. }
  177. header('Content-Type: ' . $mimeType);
  178. return $this->_output(null, $mimeType);
  179. }
  180. /**
  181. * Saves the image.
  182. *
  183. * @param string $filePath
  184. * @param string $mimeType
  185. * @return bool
  186. */
  187. public function save($filePath = null, $mimeType = null) {
  188. if ($mimeType === null) {
  189. $mimeType = $this->mimeType();
  190. }
  191. if ($filePath === null && $this->filePath === null) {
  192. $this->logger->error(__METHOD__ . '(): called with no path.', array('app' => 'core'));
  193. return false;
  194. } elseif ($filePath === null && $this->filePath !== null) {
  195. $filePath = $this->filePath;
  196. }
  197. return $this->_output($filePath, $mimeType);
  198. }
  199. /**
  200. * Outputs/saves the image.
  201. *
  202. * @param string $filePath
  203. * @param string $mimeType
  204. * @return bool
  205. * @throws Exception
  206. */
  207. private function _output($filePath = null, $mimeType = null) {
  208. if ($filePath) {
  209. if (!file_exists(dirname($filePath)))
  210. mkdir(dirname($filePath), 0777, true);
  211. if (!is_writable(dirname($filePath))) {
  212. $this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', array('app' => 'core'));
  213. return false;
  214. } elseif (is_writable(dirname($filePath)) && file_exists($filePath) && !is_writable($filePath)) {
  215. $this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', array('app' => 'core'));
  216. return false;
  217. }
  218. }
  219. if (!$this->valid()) {
  220. return false;
  221. }
  222. $imageType = $this->imageType;
  223. if ($mimeType !== null) {
  224. switch ($mimeType) {
  225. case 'image/gif':
  226. $imageType = IMAGETYPE_GIF;
  227. break;
  228. case 'image/jpeg':
  229. $imageType = IMAGETYPE_JPEG;
  230. break;
  231. case 'image/png':
  232. $imageType = IMAGETYPE_PNG;
  233. break;
  234. case 'image/x-xbitmap':
  235. $imageType = IMAGETYPE_XBM;
  236. break;
  237. case 'image/bmp':
  238. $imageType = IMAGETYPE_BMP;
  239. break;
  240. default:
  241. throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format');
  242. }
  243. }
  244. switch ($imageType) {
  245. case IMAGETYPE_GIF:
  246. $retVal = imagegif($this->resource, $filePath);
  247. break;
  248. case IMAGETYPE_JPEG:
  249. $retVal = imagejpeg($this->resource, $filePath);
  250. break;
  251. case IMAGETYPE_PNG:
  252. $retVal = imagepng($this->resource, $filePath);
  253. break;
  254. case IMAGETYPE_XBM:
  255. if (function_exists('imagexbm')) {
  256. $retVal = imagexbm($this->resource, $filePath);
  257. } else {
  258. throw new Exception('\OC_Image::_output(): imagexbm() is not supported.');
  259. }
  260. break;
  261. case IMAGETYPE_WBMP:
  262. $retVal = imagewbmp($this->resource, $filePath);
  263. break;
  264. case IMAGETYPE_BMP:
  265. $retVal = imagebmp($this->resource, $filePath, $this->bitDepth);
  266. break;
  267. default:
  268. $retVal = imagepng($this->resource, $filePath);
  269. }
  270. return $retVal;
  271. }
  272. /**
  273. * Prints the image when called as $image().
  274. */
  275. public function __invoke() {
  276. return $this->show();
  277. }
  278. /**
  279. * @return resource Returns the image resource in any.
  280. */
  281. public function resource() {
  282. return $this->resource;
  283. }
  284. /**
  285. * @return null|string Returns the raw image data.
  286. */
  287. public function data() {
  288. if (!$this->valid()) {
  289. return null;
  290. }
  291. ob_start();
  292. switch ($this->mimeType) {
  293. case "image/png":
  294. $res = imagepng($this->resource);
  295. break;
  296. case "image/jpeg":
  297. $res = imagejpeg($this->resource);
  298. break;
  299. case "image/gif":
  300. $res = imagegif($this->resource);
  301. break;
  302. default:
  303. $res = imagepng($this->resource);
  304. $this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', array('app' => 'core'));
  305. break;
  306. }
  307. if (!$res) {
  308. $this->logger->error('OC_Image->data. Error getting image data.', array('app' => 'core'));
  309. }
  310. return ob_get_clean();
  311. }
  312. /**
  313. * @return string - base64 encoded, which is suitable for embedding in a VCard.
  314. */
  315. function __toString() {
  316. return base64_encode($this->data());
  317. }
  318. /**
  319. * (I'm open for suggestions on better method name ;)
  320. * Get the orientation based on EXIF data.
  321. *
  322. * @return int The orientation or -1 if no EXIF data is available.
  323. */
  324. public function getOrientation() {
  325. if ($this->imageType !== IMAGETYPE_JPEG) {
  326. $this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', array('app' => 'core'));
  327. return -1;
  328. }
  329. if (!is_callable('exif_read_data')) {
  330. $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', array('app' => 'core'));
  331. return -1;
  332. }
  333. if (!$this->valid()) {
  334. $this->logger->debug('OC_Image->fixOrientation() No image loaded.', array('app' => 'core'));
  335. return -1;
  336. }
  337. if (is_null($this->filePath) || !is_readable($this->filePath)) {
  338. $this->logger->debug('OC_Image->fixOrientation() No readable file path set.', array('app' => 'core'));
  339. return -1;
  340. }
  341. $exif = @exif_read_data($this->filePath, 'IFD0');
  342. if (!$exif) {
  343. return -1;
  344. }
  345. if (!isset($exif['Orientation'])) {
  346. return -1;
  347. }
  348. return $exif['Orientation'];
  349. }
  350. /**
  351. * (I'm open for suggestions on better method name ;)
  352. * Fixes orientation based on EXIF data.
  353. *
  354. * @return bool.
  355. */
  356. public function fixOrientation() {
  357. $o = $this->getOrientation();
  358. $this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, array('app' => 'core'));
  359. $rotate = 0;
  360. $flip = false;
  361. switch ($o) {
  362. case -1:
  363. return false; //Nothing to fix
  364. case 1:
  365. $rotate = 0;
  366. break;
  367. case 2:
  368. $rotate = 0;
  369. $flip = true;
  370. break;
  371. case 3:
  372. $rotate = 180;
  373. break;
  374. case 4:
  375. $rotate = 180;
  376. $flip = true;
  377. break;
  378. case 5:
  379. $rotate = 90;
  380. $flip = true;
  381. break;
  382. case 6:
  383. $rotate = 270;
  384. break;
  385. case 7:
  386. $rotate = 270;
  387. $flip = true;
  388. break;
  389. case 8:
  390. $rotate = 90;
  391. break;
  392. }
  393. if($flip && function_exists('imageflip')) {
  394. imageflip($this->resource, IMG_FLIP_HORIZONTAL);
  395. }
  396. if ($rotate) {
  397. $res = imagerotate($this->resource, $rotate, 0);
  398. if ($res) {
  399. if (imagealphablending($res, true)) {
  400. if (imagesavealpha($res, true)) {
  401. imagedestroy($this->resource);
  402. $this->resource = $res;
  403. return true;
  404. } else {
  405. $this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', array('app' => 'core'));
  406. return false;
  407. }
  408. } else {
  409. $this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', array('app' => 'core'));
  410. return false;
  411. }
  412. } else {
  413. $this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', array('app' => 'core'));
  414. return false;
  415. }
  416. }
  417. return false;
  418. }
  419. /**
  420. * Loads an image from a local file, a base64 encoded string or a resource created by an imagecreate* function.
  421. *
  422. * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by an imagecreate* function or a file resource (file handle ).
  423. * @return resource|false An image resource or false on error
  424. */
  425. public function load($imageRef) {
  426. if (is_resource($imageRef)) {
  427. if (get_resource_type($imageRef) == 'gd') {
  428. $this->resource = $imageRef;
  429. return $this->resource;
  430. } elseif (in_array(get_resource_type($imageRef), array('file', 'stream'))) {
  431. return $this->loadFromFileHandle($imageRef);
  432. }
  433. } elseif ($this->loadFromBase64($imageRef) !== false) {
  434. return $this->resource;
  435. } elseif ($this->loadFromFile($imageRef) !== false) {
  436. return $this->resource;
  437. } elseif ($this->loadFromData($imageRef) !== false) {
  438. return $this->resource;
  439. }
  440. $this->logger->debug(__METHOD__ . '(): could not load anything. Giving up!', array('app' => 'core'));
  441. return false;
  442. }
  443. /**
  444. * Loads an image from an open file handle.
  445. * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again.
  446. *
  447. * @param resource $handle
  448. * @return resource|false An image resource or false on error
  449. */
  450. public function loadFromFileHandle($handle) {
  451. $contents = stream_get_contents($handle);
  452. if ($this->loadFromData($contents)) {
  453. return $this->resource;
  454. }
  455. return false;
  456. }
  457. /**
  458. * Loads an image from a local file.
  459. *
  460. * @param bool|string $imagePath The path to a local file.
  461. * @return bool|resource An image resource or false on error
  462. */
  463. public function loadFromFile($imagePath = false) {
  464. // exif_imagetype throws "read error!" if file is less than 12 byte
  465. if (!@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) {
  466. return false;
  467. }
  468. $iType = exif_imagetype($imagePath);
  469. switch ($iType) {
  470. case IMAGETYPE_GIF:
  471. if (imagetypes() & IMG_GIF) {
  472. $this->resource = imagecreatefromgif($imagePath);
  473. // Preserve transparency
  474. imagealphablending($this->resource, true);
  475. imagesavealpha($this->resource, true);
  476. } else {
  477. $this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, array('app' => 'core'));
  478. }
  479. break;
  480. case IMAGETYPE_JPEG:
  481. if (imagetypes() & IMG_JPG) {
  482. $this->resource = imagecreatefromjpeg($imagePath);
  483. } else {
  484. $this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, array('app' => 'core'));
  485. }
  486. break;
  487. case IMAGETYPE_PNG:
  488. if (imagetypes() & IMG_PNG) {
  489. $this->resource = imagecreatefrompng($imagePath);
  490. // Preserve transparency
  491. imagealphablending($this->resource, true);
  492. imagesavealpha($this->resource, true);
  493. } else {
  494. $this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, array('app' => 'core'));
  495. }
  496. break;
  497. case IMAGETYPE_XBM:
  498. if (imagetypes() & IMG_XPM) {
  499. $this->resource = imagecreatefromxbm($imagePath);
  500. } else {
  501. $this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, array('app' => 'core'));
  502. }
  503. break;
  504. case IMAGETYPE_WBMP:
  505. if (imagetypes() & IMG_WBMP) {
  506. $this->resource = imagecreatefromwbmp($imagePath);
  507. } else {
  508. $this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, array('app' => 'core'));
  509. }
  510. break;
  511. case IMAGETYPE_BMP:
  512. $this->resource = $this->imagecreatefrombmp($imagePath);
  513. break;
  514. /*
  515. case IMAGETYPE_TIFF_II: // (intel byte order)
  516. break;
  517. case IMAGETYPE_TIFF_MM: // (motorola byte order)
  518. break;
  519. case IMAGETYPE_JPC:
  520. break;
  521. case IMAGETYPE_JP2:
  522. break;
  523. case IMAGETYPE_JPX:
  524. break;
  525. case IMAGETYPE_JB2:
  526. break;
  527. case IMAGETYPE_SWC:
  528. break;
  529. case IMAGETYPE_IFF:
  530. break;
  531. case IMAGETYPE_ICO:
  532. break;
  533. case IMAGETYPE_SWF:
  534. break;
  535. case IMAGETYPE_PSD:
  536. break;
  537. */
  538. default:
  539. // this is mostly file created from encrypted file
  540. $this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagePath)));
  541. $iType = IMAGETYPE_PNG;
  542. $this->logger->debug('OC_Image->loadFromFile, Default', array('app' => 'core'));
  543. break;
  544. }
  545. if ($this->valid()) {
  546. $this->imageType = $iType;
  547. $this->mimeType = image_type_to_mime_type($iType);
  548. $this->filePath = $imagePath;
  549. }
  550. return $this->resource;
  551. }
  552. /**
  553. * Loads an image from a string of data.
  554. *
  555. * @param string $str A string of image data as read from a file.
  556. * @return bool|resource An image resource or false on error
  557. */
  558. public function loadFromData($str) {
  559. if (is_resource($str)) {
  560. return false;
  561. }
  562. $this->resource = @imagecreatefromstring($str);
  563. if ($this->fileInfo) {
  564. $this->mimeType = $this->fileInfo->buffer($str);
  565. }
  566. if (is_resource($this->resource)) {
  567. imagealphablending($this->resource, false);
  568. imagesavealpha($this->resource, true);
  569. }
  570. if (!$this->resource) {
  571. $this->logger->debug('OC_Image->loadFromFile, could not load', array('app' => 'core'));
  572. return false;
  573. }
  574. return $this->resource;
  575. }
  576. /**
  577. * Loads an image from a base64 encoded string.
  578. *
  579. * @param string $str A string base64 encoded string of image data.
  580. * @return bool|resource An image resource or false on error
  581. */
  582. public function loadFromBase64($str) {
  583. if (!is_string($str)) {
  584. return false;
  585. }
  586. $data = base64_decode($str);
  587. if ($data) { // try to load from string data
  588. $this->resource = @imagecreatefromstring($data);
  589. if ($this->fileInfo) {
  590. $this->mimeType = $this->fileInfo->buffer($data);
  591. }
  592. if (!$this->resource) {
  593. $this->logger->debug('OC_Image->loadFromBase64, could not load', array('app' => 'core'));
  594. return false;
  595. }
  596. return $this->resource;
  597. } else {
  598. return false;
  599. }
  600. }
  601. /**
  602. * Create a new image from file or URL
  603. *
  604. * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm
  605. * @version 1.00
  606. * @param string $fileName <p>
  607. * Path to the BMP image.
  608. * </p>
  609. * @return bool|resource an image resource identifier on success, <b>FALSE</b> on errors.
  610. */
  611. private function imagecreatefrombmp($fileName) {
  612. if (!($fh = fopen($fileName, 'rb'))) {
  613. $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, array('app' => 'core'));
  614. return false;
  615. }
  616. // read file header
  617. $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
  618. // check for bitmap
  619. if ($meta['type'] != 19778) {
  620. fclose($fh);
  621. $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', array('app' => 'core'));
  622. return false;
  623. }
  624. // read image header
  625. $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
  626. // read additional 16bit header
  627. if ($meta['bits'] == 16) {
  628. $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
  629. }
  630. // set bytes and padding
  631. $meta['bytes'] = $meta['bits'] / 8;
  632. $this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call
  633. $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
  634. if ($meta['decal'] == 4) {
  635. $meta['decal'] = 0;
  636. }
  637. // obtain imagesize
  638. if ($meta['imagesize'] < 1) {
  639. $meta['imagesize'] = $meta['filesize'] - $meta['offset'];
  640. // in rare cases filesize is equal to offset so we need to read physical size
  641. if ($meta['imagesize'] < 1) {
  642. $meta['imagesize'] = @filesize($fileName) - $meta['offset'];
  643. if ($meta['imagesize'] < 1) {
  644. fclose($fh);
  645. $this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', array('app' => 'core'));
  646. return false;
  647. }
  648. }
  649. }
  650. // calculate colors
  651. $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
  652. // read color palette
  653. $palette = array();
  654. if ($meta['bits'] < 16) {
  655. $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
  656. // in rare cases the color value is signed
  657. if ($palette[1] < 0) {
  658. foreach ($palette as $i => $color) {
  659. $palette[$i] = $color + 16777216;
  660. }
  661. }
  662. }
  663. // create gd image
  664. $im = imagecreatetruecolor($meta['width'], $meta['height']);
  665. if ($im == false) {
  666. fclose($fh);
  667. $this->logger->warning(
  668. 'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'],
  669. array('app' => 'core'));
  670. return false;
  671. }
  672. $data = fread($fh, $meta['imagesize']);
  673. $p = 0;
  674. $vide = chr(0);
  675. $y = $meta['height'] - 1;
  676. $error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!';
  677. // loop through the image data beginning with the lower left corner
  678. while ($y >= 0) {
  679. $x = 0;
  680. while ($x < $meta['width']) {
  681. switch ($meta['bits']) {
  682. case 32:
  683. case 24:
  684. if (!($part = substr($data, $p, 3))) {
  685. $this->logger->warning($error, array('app' => 'core'));
  686. return $im;
  687. }
  688. $color = unpack('V', $part . $vide);
  689. break;
  690. case 16:
  691. if (!($part = substr($data, $p, 2))) {
  692. fclose($fh);
  693. $this->logger->warning($error, array('app' => 'core'));
  694. return $im;
  695. }
  696. $color = unpack('v', $part);
  697. $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3);
  698. break;
  699. case 8:
  700. $color = unpack('n', $vide . substr($data, $p, 1));
  701. $color[1] = $palette[$color[1] + 1];
  702. break;
  703. case 4:
  704. $color = unpack('n', $vide . substr($data, floor($p), 1));
  705. $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
  706. $color[1] = $palette[$color[1] + 1];
  707. break;
  708. case 1:
  709. $color = unpack('n', $vide . substr($data, floor($p), 1));
  710. switch (($p * 8) % 8) {
  711. case 0:
  712. $color[1] = $color[1] >> 7;
  713. break;
  714. case 1:
  715. $color[1] = ($color[1] & 0x40) >> 6;
  716. break;
  717. case 2:
  718. $color[1] = ($color[1] & 0x20) >> 5;
  719. break;
  720. case 3:
  721. $color[1] = ($color[1] & 0x10) >> 4;
  722. break;
  723. case 4:
  724. $color[1] = ($color[1] & 0x8) >> 3;
  725. break;
  726. case 5:
  727. $color[1] = ($color[1] & 0x4) >> 2;
  728. break;
  729. case 6:
  730. $color[1] = ($color[1] & 0x2) >> 1;
  731. break;
  732. case 7:
  733. $color[1] = ($color[1] & 0x1);
  734. break;
  735. }
  736. $color[1] = $palette[$color[1] + 1];
  737. break;
  738. default:
  739. fclose($fh);
  740. $this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', array('app' => 'core'));
  741. return false;
  742. }
  743. imagesetpixel($im, $x, $y, $color[1]);
  744. $x++;
  745. $p += $meta['bytes'];
  746. }
  747. $y--;
  748. $p += $meta['decal'];
  749. }
  750. fclose($fh);
  751. return $im;
  752. }
  753. /**
  754. * Resizes the image preserving ratio.
  755. *
  756. * @param integer $maxSize The maximum size of either the width or height.
  757. * @return bool
  758. */
  759. public function resize($maxSize) {
  760. if (!$this->valid()) {
  761. $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
  762. return false;
  763. }
  764. $widthOrig = imageSX($this->resource);
  765. $heightOrig = imageSY($this->resource);
  766. $ratioOrig = $widthOrig / $heightOrig;
  767. if ($ratioOrig > 1) {
  768. $newHeight = round($maxSize / $ratioOrig);
  769. $newWidth = $maxSize;
  770. } else {
  771. $newWidth = round($maxSize * $ratioOrig);
  772. $newHeight = $maxSize;
  773. }
  774. $this->preciseResize(round($newWidth), round($newHeight));
  775. return true;
  776. }
  777. /**
  778. * @param int $width
  779. * @param int $height
  780. * @return bool
  781. */
  782. public function preciseResize($width, $height) {
  783. if (!$this->valid()) {
  784. $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
  785. return false;
  786. }
  787. $widthOrig = imageSX($this->resource);
  788. $heightOrig = imageSY($this->resource);
  789. $process = imagecreatetruecolor($width, $height);
  790. if ($process == false) {
  791. $this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core'));
  792. imagedestroy($process);
  793. return false;
  794. }
  795. // preserve transparency
  796. if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
  797. imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
  798. imagealphablending($process, false);
  799. imagesavealpha($process, true);
  800. }
  801. imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig);
  802. if ($process == false) {
  803. $this->logger->error(__METHOD__ . '(): Error re-sampling process image', array('app' => 'core'));
  804. imagedestroy($process);
  805. return false;
  806. }
  807. imagedestroy($this->resource);
  808. $this->resource = $process;
  809. return true;
  810. }
  811. /**
  812. * Crops the image to the middle square. If the image is already square it just returns.
  813. *
  814. * @param int $size maximum size for the result (optional)
  815. * @return bool for success or failure
  816. */
  817. public function centerCrop($size = 0) {
  818. if (!$this->valid()) {
  819. $this->logger->error('OC_Image->centerCrop, No image loaded', array('app' => 'core'));
  820. return false;
  821. }
  822. $widthOrig = imageSX($this->resource);
  823. $heightOrig = imageSY($this->resource);
  824. if ($widthOrig === $heightOrig and $size == 0) {
  825. return true;
  826. }
  827. $ratioOrig = $widthOrig / $heightOrig;
  828. $width = $height = min($widthOrig, $heightOrig);
  829. if ($ratioOrig > 1) {
  830. $x = ($widthOrig / 2) - ($width / 2);
  831. $y = 0;
  832. } else {
  833. $y = ($heightOrig / 2) - ($height / 2);
  834. $x = 0;
  835. }
  836. if ($size > 0) {
  837. $targetWidth = $size;
  838. $targetHeight = $size;
  839. } else {
  840. $targetWidth = $width;
  841. $targetHeight = $height;
  842. }
  843. $process = imagecreatetruecolor($targetWidth, $targetHeight);
  844. if ($process == false) {
  845. $this->logger->error('OC_Image->centerCrop, Error creating true color image', array('app' => 'core'));
  846. imagedestroy($process);
  847. return false;
  848. }
  849. // preserve transparency
  850. if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
  851. imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
  852. imagealphablending($process, false);
  853. imagesavealpha($process, true);
  854. }
  855. imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height);
  856. if ($process == false) {
  857. $this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, array('app' => 'core'));
  858. imagedestroy($process);
  859. return false;
  860. }
  861. imagedestroy($this->resource);
  862. $this->resource = $process;
  863. return true;
  864. }
  865. /**
  866. * Crops the image from point $x$y with dimension $wx$h.
  867. *
  868. * @param int $x Horizontal position
  869. * @param int $y Vertical position
  870. * @param int $w Width
  871. * @param int $h Height
  872. * @return bool for success or failure
  873. */
  874. public function crop($x, $y, $w, $h) {
  875. if (!$this->valid()) {
  876. $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
  877. return false;
  878. }
  879. $process = imagecreatetruecolor($w, $h);
  880. if ($process == false) {
  881. $this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core'));
  882. imagedestroy($process);
  883. return false;
  884. }
  885. // preserve transparency
  886. if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
  887. imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
  888. imagealphablending($process, false);
  889. imagesavealpha($process, true);
  890. }
  891. imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h);
  892. if ($process == false) {
  893. $this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, array('app' => 'core'));
  894. imagedestroy($process);
  895. return false;
  896. }
  897. imagedestroy($this->resource);
  898. $this->resource = $process;
  899. return true;
  900. }
  901. /**
  902. * Resizes the image to fit within a boundary while preserving ratio.
  903. *
  904. * @param integer $maxWidth
  905. * @param integer $maxHeight
  906. * @return bool
  907. */
  908. public function fitIn($maxWidth, $maxHeight) {
  909. if (!$this->valid()) {
  910. $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
  911. return false;
  912. }
  913. $widthOrig = imageSX($this->resource);
  914. $heightOrig = imageSY($this->resource);
  915. $ratio = $widthOrig / $heightOrig;
  916. $newWidth = min($maxWidth, $ratio * $maxHeight);
  917. $newHeight = min($maxHeight, $maxWidth / $ratio);
  918. $this->preciseResize(round($newWidth), round($newHeight));
  919. return true;
  920. }
  921. /**
  922. * Destroys the current image and resets the object
  923. */
  924. public function destroy() {
  925. if ($this->valid()) {
  926. imagedestroy($this->resource);
  927. }
  928. $this->resource = null;
  929. }
  930. public function __destruct() {
  931. $this->destroy();
  932. }
  933. }
  934. if (!function_exists('imagebmp')) {
  935. /**
  936. * Output a BMP image to either the browser or a file
  937. *
  938. * @link http://www.ugia.cn/wp-data/imagebmp.php
  939. * @author legend <legendsky@hotmail.com>
  940. * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
  941. * @author mgutt <marc@gutt.it>
  942. * @version 1.00
  943. * @param string $fileName [optional] <p>The path to save the file to.</p>
  944. * @param int $bit [optional] <p>Bit depth, (default is 24).</p>
  945. * @param int $compression [optional]
  946. * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
  947. */
  948. function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) {
  949. if (!in_array($bit, array(1, 4, 8, 16, 24, 32))) {
  950. $bit = 24;
  951. } else if ($bit == 32) {
  952. $bit = 24;
  953. }
  954. $bits = pow(2, $bit);
  955. imagetruecolortopalette($im, true, $bits);
  956. $width = imagesx($im);
  957. $height = imagesy($im);
  958. $colorsNum = imagecolorstotal($im);
  959. $rgbQuad = '';
  960. if ($bit <= 8) {
  961. for ($i = 0; $i < $colorsNum; $i++) {
  962. $colors = imagecolorsforindex($im, $i);
  963. $rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0";
  964. }
  965. $bmpData = '';
  966. if ($compression == 0 || $bit < 8) {
  967. $compression = 0;
  968. $extra = '';
  969. $padding = 4 - ceil($width / (8 / $bit)) % 4;
  970. if ($padding % 4 != 0) {
  971. $extra = str_repeat("\0", $padding);
  972. }
  973. for ($j = $height - 1; $j >= 0; $j--) {
  974. $i = 0;
  975. while ($i < $width) {
  976. $bin = 0;
  977. $limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
  978. for ($k = 8 - $bit; $k >= $limit; $k -= $bit) {
  979. $index = imagecolorat($im, $i, $j);
  980. $bin |= $index << $k;
  981. $i++;
  982. }
  983. $bmpData .= chr($bin);
  984. }
  985. $bmpData .= $extra;
  986. }
  987. } // RLE8
  988. else if ($compression == 1 && $bit == 8) {
  989. for ($j = $height - 1; $j >= 0; $j--) {
  990. $lastIndex = "\0";
  991. $sameNum = 0;
  992. for ($i = 0; $i <= $width; $i++) {
  993. $index = imagecolorat($im, $i, $j);
  994. if ($index !== $lastIndex || $sameNum > 255) {
  995. if ($sameNum != 0) {
  996. $bmpData .= chr($sameNum) . chr($lastIndex);
  997. }
  998. $lastIndex = $index;
  999. $sameNum = 1;
  1000. } else {
  1001. $sameNum++;
  1002. }
  1003. }
  1004. $bmpData .= "\0\0";
  1005. }
  1006. $bmpData .= "\0\1";
  1007. }
  1008. $sizeQuad = strlen($rgbQuad);
  1009. $sizeData = strlen($bmpData);
  1010. } else {
  1011. $extra = '';
  1012. $padding = 4 - ($width * ($bit / 8)) % 4;
  1013. if ($padding % 4 != 0) {
  1014. $extra = str_repeat("\0", $padding);
  1015. }
  1016. $bmpData = '';
  1017. for ($j = $height - 1; $j >= 0; $j--) {
  1018. for ($i = 0; $i < $width; $i++) {
  1019. $index = imagecolorat($im, $i, $j);
  1020. $colors = imagecolorsforindex($im, $index);
  1021. if ($bit == 16) {
  1022. $bin = 0 << $bit;
  1023. $bin |= ($colors['red'] >> 3) << 10;
  1024. $bin |= ($colors['green'] >> 3) << 5;
  1025. $bin |= $colors['blue'] >> 3;
  1026. $bmpData .= pack("v", $bin);
  1027. } else {
  1028. $bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
  1029. }
  1030. }
  1031. $bmpData .= $extra;
  1032. }
  1033. $sizeQuad = 0;
  1034. $sizeData = strlen($bmpData);
  1035. $colorsNum = 0;
  1036. }
  1037. $fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad);
  1038. $infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0);
  1039. if ($fileName != '') {
  1040. $fp = fopen($fileName, 'wb');
  1041. fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
  1042. fclose($fp);
  1043. return true;
  1044. }
  1045. echo $fileHeader . $infoHeader . $rgbQuad . $bmpData;
  1046. return true;
  1047. }
  1048. }
  1049. if (!function_exists('exif_imagetype')) {
  1050. /**
  1051. * Workaround if exif_imagetype does not exist
  1052. *
  1053. * @link http://www.php.net/manual/en/function.exif-imagetype.php#80383
  1054. * @param string $fileName
  1055. * @return string|boolean
  1056. */
  1057. function exif_imagetype($fileName) {
  1058. if (($info = getimagesize($fileName)) !== false) {
  1059. return $info[2];
  1060. }
  1061. return false;
  1062. }
  1063. }