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