image.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  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. //From user comments at http://dk2.php.net/manual/en/function.exif-imagetype.php
  23. if ( ! function_exists( 'exif_imagetype' ) ) {
  24. function exif_imagetype ( $filename ) {
  25. if ( ( list($width, $height, $type, $attr) = getimagesize( $filename ) ) !== false ) {
  26. return $type;
  27. }
  28. return false;
  29. }
  30. }
  31. function ellipsis($str, $maxlen) {
  32. if (strlen($str) > $maxlen) {
  33. $characters = floor($maxlen / 2);
  34. return substr($str, 0, $characters) . '...' . substr($str, -1 * $characters);
  35. }
  36. return $str;
  37. }
  38. /**
  39. * Class for basic image manipulation
  40. *
  41. */
  42. class OC_Image {
  43. protected $resource = false; // tmp resource.
  44. protected $imagetype = IMAGETYPE_PNG; // Default to png if file type isn't evident.
  45. protected $filepath = null;
  46. /**
  47. * @brief Get mime type for an image file.
  48. * @param $filepath The path to a local image file.
  49. * @returns string The mime type if the it could be determined, otherwise an empty string.
  50. */
  51. static public function getMimeTypeForFile($filepath) {
  52. $imagetype = exif_imagetype($filepath);
  53. return $imagetype ? image_type_to_mime_type($imagetype) : '';
  54. }
  55. /**
  56. * @brief Constructor.
  57. * @param $imageref The path to a local file, a base64 encoded string or a resource created by an imagecreate* function.
  58. * @returns bool False on error
  59. */
  60. public function __construct($imageref = null) {
  61. //OC_Log::write('core',__METHOD__.'(): start', OC_Log::DEBUG);
  62. if(!extension_loaded('gd') || !function_exists('gd_info')) {
  63. //if(!function_exists('imagecreatefromjpeg')) {
  64. OC_Log::write('core',__METHOD__.'(): GD module not installed', OC_Log::ERROR);
  65. return false;
  66. }
  67. if(!is_null($imageref)) {
  68. $this->load($imageref);
  69. }
  70. }
  71. /**
  72. * @brief Determine whether the object contains an image resource.
  73. * @returns bool
  74. */
  75. public function valid() { // apparently you can't name a method 'empty'...
  76. return is_resource($this->resource);
  77. }
  78. /**
  79. * @brief Returns the MIME type of the image or an empty string if no image is loaded.
  80. * @returns int
  81. */
  82. public function mimeType() {
  83. return $this->valid() ? image_type_to_mime_type($this->imagetype) : '';
  84. }
  85. /**
  86. * @brief Returns the width of the image or -1 if no image is loaded.
  87. * @returns int
  88. */
  89. public function width() {
  90. return $this->valid() ? imagesx($this->resource) : -1;
  91. }
  92. /**
  93. * @brief Returns the height of the image or -1 if no image is loaded.
  94. * @returns int
  95. */
  96. public function height() {
  97. return $this->valid() ? imagesy($this->resource) : -1;
  98. }
  99. /**
  100. * @brief Outputs the image.
  101. * @returns bool
  102. */
  103. public function show() {
  104. header('Content-Type: '.$this->mimeType());
  105. return $this->_output();
  106. }
  107. /**
  108. * @brief Saves the image.
  109. * @returns bool
  110. */
  111. public function save($filepath=null) {
  112. if($filepath === null && $this->filepath === null) {
  113. OC_Log::write('core',__METHOD__.'(): called with no path.', OC_Log::ERROR);
  114. return false;
  115. } elseif($filepath === null && $this->filepath !== null) {
  116. $filepath = $this->filepath;
  117. }
  118. return $this->_output($filepath);
  119. }
  120. /**
  121. * @brief Outputs/saves the image.
  122. */
  123. private function _output($filepath=null) {
  124. if($filepath) {
  125. if (!file_exists(dirname($filepath)))
  126. mkdir(dirname($filepath), 0777, true);
  127. if(!is_writable(dirname($filepath))) {
  128. OC_Log::write('core',__METHOD__.'(): Directory \''.dirname($filepath).'\' is not writable.', OC_Log::ERROR);
  129. return false;
  130. } elseif(is_writable(dirname($filepath)) && file_exists($filepath) && !is_writable($filepath)) {
  131. OC_Log::write('core',__METHOD__.'(): File \''.$filepath.'\' is not writable.', OC_Log::ERROR);
  132. return false;
  133. }
  134. }
  135. if (!$this->valid()) {
  136. return false;
  137. }
  138. $retval = false;
  139. switch($this->imagetype) {
  140. case IMAGETYPE_GIF:
  141. $retval = imagegif($this->resource, $filepath);
  142. break;
  143. case IMAGETYPE_JPEG:
  144. $retval = imagejpeg($this->resource, $filepath);
  145. break;
  146. case IMAGETYPE_PNG:
  147. $retval = imagepng($this->resource, $filepath);
  148. break;
  149. case IMAGETYPE_XBM:
  150. $retval = imagexbm($this->resource, $filepath);
  151. break;
  152. case IMAGETYPE_WBMP:
  153. case IMAGETYPE_BMP:
  154. $retval = imagewbmp($this->resource, $filepath);
  155. break;
  156. default:
  157. $retval = imagepng($this->resource, $filepath);
  158. }
  159. return $retval;
  160. }
  161. /**
  162. * @brief Prints the image when called as $image().
  163. */
  164. public function __invoke() {
  165. return $this->show();
  166. }
  167. /**
  168. * @returns Returns the image resource in any.
  169. */
  170. public function resource() {
  171. return $this->resource;
  172. }
  173. /**
  174. * @returns Returns the raw image data.
  175. */
  176. function data() {
  177. ob_start();
  178. $res = imagepng($this->resource);
  179. if (!$res) {
  180. OC_Log::write('core','OC_Image->data. Error getting image data.',OC_Log::ERROR);
  181. }
  182. return ob_get_clean();
  183. }
  184. /**
  185. * @returns Returns a base64 encoded string suitable for embedding in a VCard.
  186. */
  187. function __toString() {
  188. return base64_encode($this->data());
  189. }
  190. /**
  191. * (I'm open for suggestions on better method name ;)
  192. * @brief Fixes orientation based on EXIF data.
  193. * @returns bool.
  194. */
  195. public function fixOrientation() {
  196. if(!is_callable('exif_read_data')){
  197. OC_Log::write('core','OC_Image->fixOrientation() Exif module not enabled.', OC_Log::DEBUG);
  198. return false;
  199. }
  200. if(!$this->valid()) {
  201. OC_Log::write('core','OC_Image->fixOrientation() No image loaded.', OC_Log::DEBUG);
  202. return false;
  203. }
  204. if(is_null($this->filepath) || !is_readable($this->filepath)) {
  205. OC_Log::write('core','OC_Image->fixOrientation() No readable file path set.', OC_Log::DEBUG);
  206. return false;
  207. }
  208. $exif = @exif_read_data($this->filepath, 'IFD0');
  209. if(!$exif) {
  210. return false;
  211. }
  212. if(!isset($exif['Orientation'])) {
  213. return true; // Nothing to fix
  214. }
  215. $o = $exif['Orientation'];
  216. OC_Log::write('core','OC_Image->fixOrientation() Orientation: '.$o, OC_Log::DEBUG);
  217. $rotate = 0;
  218. $flip = false;
  219. switch($o) {
  220. case 1:
  221. $rotate = 0;
  222. $flip = false;
  223. break;
  224. case 2: // Not tested
  225. $rotate = 0;
  226. $flip = true;
  227. break;
  228. case 3:
  229. $rotate = 180;
  230. $flip = false;
  231. break;
  232. case 4: // Not tested
  233. $rotate = 180;
  234. $flip = true;
  235. break;
  236. case 5: // Not tested
  237. $rotate = 90;
  238. $flip = true;
  239. break;
  240. case 6:
  241. //$rotate = 90;
  242. $rotate = 270;
  243. $flip = false;
  244. break;
  245. case 7: // Not tested
  246. $rotate = 270;
  247. $flip = true;
  248. break;
  249. case 8:
  250. $rotate = 90;
  251. $flip = false;
  252. break;
  253. }
  254. if($rotate) {
  255. $res = imagerotate($this->resource, $rotate, -1);
  256. if($res) {
  257. if(imagealphablending($res, true)) {
  258. if(imagesavealpha($res, true)) {
  259. imagedestroy($this->resource);
  260. $this->resource = $res;
  261. return true;
  262. } else {
  263. OC_Log::write('core','OC_Image->fixOrientation() Error during alphasaving.', OC_Log::DEBUG);
  264. return false;
  265. }
  266. } else {
  267. OC_Log::write('core','OC_Image->fixOrientation() Error during alphablending.', OC_Log::DEBUG);
  268. return false;
  269. }
  270. } else {
  271. OC_Log::write('core','OC_Image->fixOrientation() Error during oriention fixing.', OC_Log::DEBUG);
  272. return false;
  273. }
  274. }
  275. }
  276. /**
  277. * @brief Loads an image from a local file, a base64 encoded string or a resource created by an imagecreate* function.
  278. * @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 ).
  279. * @returns An image resource or false on error
  280. */
  281. public function load($imageref) {
  282. if(is_resource($imageref)) {
  283. if(get_resource_type($imageref) == 'gd') {
  284. $this->resource = $res;
  285. return $this->resource;
  286. } elseif(in_array(get_resource_type($imageref), array('file','stream'))) {
  287. return $this->loadFromFileHandle($imageref);
  288. }
  289. } elseif($this->loadFromFile($imageref) !== false) {
  290. return $this->resource;
  291. } elseif($this->loadFromBase64($imageref) !== false) {
  292. return $this->resource;
  293. } elseif($this->loadFromData($imageref) !== false) {
  294. return $this->resource;
  295. } else {
  296. OC_Log::write('core',__METHOD__.'(): couldn\'t load anything. Giving up!', OC_Log::DEBUG);
  297. return false;
  298. }
  299. }
  300. /**
  301. * @brief Loads an image from an open file handle.
  302. * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again.
  303. * @param $handle
  304. * @returns An image resource or false on error
  305. */
  306. public function loadFromFileHandle($handle) {
  307. OC_Log::write('core',__METHOD__.'(): Trying', OC_Log::DEBUG);
  308. $contents = stream_get_contents($handle);
  309. if($this->loadFromData($contents)) {
  310. return $this->resource;
  311. }
  312. }
  313. /**
  314. * @brief Loads an image from a local file.
  315. * @param $imageref The path to a local file.
  316. * @returns An image resource or false on error
  317. */
  318. public function loadFromFile($imagepath=false) {
  319. if(!is_file($imagepath) || !file_exists($imagepath) || !is_readable($imagepath)) {
  320. // Debug output disabled because this method is tried before loadFromBase64?
  321. OC_Log::write('core','OC_Image->loadFromFile, couldn\'t load: '.ellipsis($imagepath, 50), OC_Log::DEBUG);
  322. return false;
  323. }
  324. $itype = exif_imagetype($imagepath);
  325. switch($itype) {
  326. case IMAGETYPE_GIF:
  327. if (imagetypes() & IMG_GIF) {
  328. $this->resource = imagecreatefromgif($imagepath);
  329. } else {
  330. OC_Log::write('core','OC_Image->loadFromFile, GIF images not supported: '.$imagepath, OC_Log::DEBUG);
  331. }
  332. break;
  333. case IMAGETYPE_JPEG:
  334. if (imagetypes() & IMG_JPG) {
  335. $this->resource = imagecreatefromjpeg($imagepath);
  336. } else {
  337. OC_Log::write('core','OC_Image->loadFromFile, JPG images not supported: '.$imagepath, OC_Log::DEBUG);
  338. }
  339. break;
  340. case IMAGETYPE_PNG:
  341. if (imagetypes() & IMG_PNG) {
  342. $this->resource = imagecreatefrompng($imagepath);
  343. } else {
  344. OC_Log::write('core','OC_Image->loadFromFile, PNG images not supported: '.$imagepath, OC_Log::DEBUG);
  345. }
  346. break;
  347. case IMAGETYPE_XBM:
  348. if (imagetypes() & IMG_XPM) {
  349. $this->resource = imagecreatefromxbm($imagepath);
  350. } else {
  351. OC_Log::write('core','OC_Image->loadFromFile, XBM/XPM images not supported: '.$imagepath, OC_Log::DEBUG);
  352. }
  353. break;
  354. case IMAGETYPE_WBMP:
  355. case IMAGETYPE_BMP:
  356. if (imagetypes() & IMG_WBMP) {
  357. $this->resource = imagecreatefromwbmp($imagepath);
  358. } else {
  359. OC_Log::write('core','OC_Image->loadFromFile, (W)BMP images not supported: '.$imagepath, OC_Log::DEBUG);
  360. }
  361. break;
  362. /*
  363. case IMAGETYPE_TIFF_II: // (intel byte order)
  364. break;
  365. case IMAGETYPE_TIFF_MM: // (motorola byte order)
  366. break;
  367. case IMAGETYPE_JPC:
  368. break;
  369. case IMAGETYPE_JP2:
  370. break;
  371. case IMAGETYPE_JPX:
  372. break;
  373. case IMAGETYPE_JB2:
  374. break;
  375. case IMAGETYPE_SWC:
  376. break;
  377. case IMAGETYPE_IFF:
  378. break;
  379. case IMAGETYPE_ICO:
  380. break;
  381. case IMAGETYPE_SWF:
  382. break;
  383. case IMAGETYPE_PSD:
  384. break;
  385. */
  386. default:
  387. // this is mostly file created from encrypted file
  388. $this->resource = imagecreatefromstring(\OC_Filesystem::file_get_contents(\OC_Filesystem::getLocalPath($imagepath)));
  389. $itype = IMAGETYPE_PNG;
  390. OC_Log::write('core','OC_Image->loadFromFile, Default', OC_Log::DEBUG);
  391. break;
  392. }
  393. if($this->valid()) {
  394. $this->imagetype = $itype;
  395. $this->filepath = $imagepath;
  396. }
  397. return $this->resource;
  398. }
  399. /**
  400. * @brief Loads an image from a string of data.
  401. * @param $str A string of image data as read from a file.
  402. * @returns An image resource or false on error
  403. */
  404. public function loadFromData($str) {
  405. if(is_resource($str)) {
  406. return false;
  407. }
  408. $this->resource = @imagecreatefromstring($str);
  409. if(!$this->resource) {
  410. OC_Log::write('core','OC_Image->loadFromData, couldn\'t load', OC_Log::DEBUG);
  411. return false;
  412. }
  413. return $this->resource;
  414. }
  415. /**
  416. * @brief Loads an image from a base64 encoded string.
  417. * @param $str A string base64 encoded string of image data.
  418. * @returns An image resource or false on error
  419. */
  420. public function loadFromBase64($str) {
  421. if(!is_string($str)) {
  422. return false;
  423. }
  424. $data = base64_decode($str);
  425. if($data) { // try to load from string data
  426. $this->resource = @imagecreatefromstring($data);
  427. if(!$this->resource) {
  428. OC_Log::write('core','OC_Image->loadFromBase64, couldn\'t load', OC_Log::DEBUG);
  429. return false;
  430. }
  431. return $this->resource;
  432. } else {
  433. return false;
  434. }
  435. }
  436. /**
  437. * @brief Resizes the image preserving ratio.
  438. * @param $maxsize The maximum size of either the width or height.
  439. * @returns bool
  440. */
  441. public function resize($maxsize) {
  442. if(!$this->valid()) {
  443. OC_Log::write('core',__METHOD__.'(): No image loaded', OC_Log::ERROR);
  444. return false;
  445. }
  446. $width_orig=imageSX($this->resource);
  447. $height_orig=imageSY($this->resource);
  448. $ratio_orig = $width_orig/$height_orig;
  449. if ($ratio_orig > 1) {
  450. $new_height = round($maxsize/$ratio_orig);
  451. $new_width = $maxsize;
  452. } else {
  453. $new_width = round($maxsize*$ratio_orig);
  454. $new_height = $maxsize;
  455. }
  456. $process = imagecreatetruecolor(round($new_width), round($new_height));
  457. if ($process == false) {
  458. OC_Log::write('core',__METHOD__.'(): Error creating true color image',OC_Log::ERROR);
  459. imagedestroy($process);
  460. return false;
  461. }
  462. imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $new_width, $new_height, $width_orig, $height_orig);
  463. if ($process == false) {
  464. OC_Log::write('core',__METHOD__.'(): Error resampling process image '.$new_width.'x'.$new_height,OC_Log::ERROR);
  465. imagedestroy($process);
  466. return false;
  467. }
  468. imagedestroy($this->resource);
  469. $this->resource = $process;
  470. return true;
  471. }
  472. public function preciseResize($width, $height) {
  473. if (!$this->valid()) {
  474. OC_Log::write('core',__METHOD__.'(): No image loaded', OC_Log::ERROR);
  475. return false;
  476. }
  477. $width_orig=imageSX($this->resource);
  478. $height_orig=imageSY($this->resource);
  479. $process = imagecreatetruecolor($width, $height);
  480. if ($process == false) {
  481. OC_Log::write('core',__METHOD__.'(): Error creating true color image',OC_Log::ERROR);
  482. imagedestroy($process);
  483. return false;
  484. }
  485. imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
  486. if ($process == false) {
  487. OC_Log::write('core',__METHOD__.'(): Error resampling process image '.$width.'x'.$height,OC_Log::ERROR);
  488. imagedestroy($process);
  489. return false;
  490. }
  491. imagedestroy($this->resource);
  492. $this->resource = $process;
  493. return true;
  494. }
  495. /**
  496. * @brief Crops the image to the middle square. If the image is already square it just returns.
  497. * @param int maximum size for the result (optional)
  498. * @returns bool for success or failure
  499. */
  500. public function centerCrop($size=0) {
  501. if(!$this->valid()) {
  502. OC_Log::write('core','OC_Image->centerCrop, No image loaded', OC_Log::ERROR);
  503. return false;
  504. }
  505. $width_orig=imageSX($this->resource);
  506. $height_orig=imageSY($this->resource);
  507. if($width_orig === $height_orig and $size==0) {
  508. return true;
  509. }
  510. $ratio_orig = $width_orig/$height_orig;
  511. $width = $height = min($width_orig, $height_orig);
  512. if ($ratio_orig > 1) {
  513. $x = ($width_orig/2) - ($width/2);
  514. $y = 0;
  515. } else {
  516. $y = ($height_orig/2) - ($height/2);
  517. $x = 0;
  518. }
  519. if($size>0){
  520. $targetWidth=$size;
  521. $targetHeight=$size;
  522. }else{
  523. $targetWidth=$width;
  524. $targetHeight=$height;
  525. }
  526. $process = imagecreatetruecolor($targetWidth, $targetHeight);
  527. if ($process == false) {
  528. OC_Log::write('core','OC_Image->centerCrop. Error creating true color image',OC_Log::ERROR);
  529. imagedestroy($process);
  530. return false;
  531. }
  532. imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height);
  533. if ($process == false) {
  534. OC_Log::write('core','OC_Image->centerCrop. Error resampling process image '.$width.'x'.$height,OC_Log::ERROR);
  535. imagedestroy($process);
  536. return false;
  537. }
  538. imagedestroy($this->resource);
  539. $this->resource = $process;
  540. return true;
  541. }
  542. /**
  543. * @brief Crops the image from point $x$y with dimension $wx$h.
  544. * @param $x Horizontal position
  545. * @param $y Vertical position
  546. * @param $w Width
  547. * @param $h Hight
  548. * @returns bool for success or failure
  549. */
  550. public function crop($x, $y, $w, $h) {
  551. if(!$this->valid()) {
  552. OC_Log::write('core',__METHOD__.'(): No image loaded', OC_Log::ERROR);
  553. return false;
  554. }
  555. $width_orig=imageSX($this->resource);
  556. $height_orig=imageSY($this->resource);
  557. //OC_Log::write('core',__METHOD__.'(): Original size: '.$width_orig.'x'.$height_orig, OC_Log::DEBUG);
  558. $process = imagecreatetruecolor($w, $h);
  559. if ($process == false) {
  560. OC_Log::write('core',__METHOD__.'(): Error creating true color image',OC_Log::ERROR);
  561. imagedestroy($process);
  562. return false;
  563. }
  564. imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h);
  565. if ($process == false) {
  566. OC_Log::write('core',__METHOD__.'(): Error resampling process image '.$w.'x'.$h,OC_Log::ERROR);
  567. imagedestroy($process);
  568. return false;
  569. }
  570. imagedestroy($this->resource);
  571. $this->resource = $process;
  572. return true;
  573. }
  574. public function destroy(){
  575. if($this->valid()){
  576. imagedestroy($this->resource);
  577. }
  578. $this->resource=null;
  579. }
  580. public function __destruct(){
  581. $this->destroy();
  582. }
  583. }