PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/image.php

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