You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

OC_Image.php 36KB

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