1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
##DOC-SIGNATURE## |
4
|
|
|
|
5
|
|
|
This file is part of WideImage. |
6
|
|
|
|
7
|
|
|
WideImage is free software; you can redistribute it and/or modify |
8
|
|
|
it under the terms of the GNU Lesser General Public License as published by |
9
|
|
|
the Free Software Foundation; either version 2.1 of the License, or |
10
|
|
|
(at your option) any later version. |
11
|
|
|
|
12
|
|
|
WideImage is distributed in the hope that it will be useful, |
13
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
14
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15
|
|
|
GNU Lesser General Public License for more details. |
16
|
|
|
|
17
|
|
|
You should have received a copy of the GNU Lesser General Public License |
18
|
|
|
along with WideImage; if not, write to the Free Software |
19
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
20
|
|
|
|
21
|
|
|
* @package WideImage |
22
|
|
|
**/ |
23
|
|
|
|
24
|
|
|
namespace WideImage; |
25
|
|
|
|
26
|
|
|
use WideImage\Exception\UnknownErrorWhileMappingException; |
27
|
|
|
use WideImage\Exception\GDFunctionResultException; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Base class for images |
31
|
|
|
* |
32
|
|
|
* @package WideImage |
33
|
|
|
*/ |
34
|
|
|
abstract class Image |
35
|
|
|
{ |
36
|
|
|
/** |
37
|
|
|
* Holds the image resource |
38
|
|
|
* @var resource |
39
|
|
|
*/ |
40
|
|
|
protected $handle = null; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Flag that determines if WideImage should call imagedestroy() upon object destruction |
44
|
|
|
* @var bool |
45
|
|
|
*/ |
46
|
|
|
protected $handleReleased = false; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Canvas object |
50
|
|
|
* @var \WideImage\Canvas |
51
|
|
|
*/ |
52
|
|
|
protected $canvas = null; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var string |
56
|
|
|
*/ |
57
|
|
|
protected $sdata = null; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* The base class constructor |
61
|
|
|
* |
62
|
|
|
* @param resource $handle Image handle (GD2 resource) |
63
|
|
|
*/ |
64
|
|
|
public function __construct($handle) |
65
|
|
|
{ |
66
|
|
|
WideImage::assertValidImageHandle($handle); |
67
|
|
|
$this->handle = $handle; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Cleanup |
72
|
|
|
* |
73
|
|
|
* Destroys the handle via \WideImage\Image::destroy() when called by the GC. |
74
|
|
|
*/ |
75
|
|
|
public function __destruct() |
76
|
|
|
{ |
77
|
|
|
$this->destroy(); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* This method destroy the image handle, and releases the image resource. |
82
|
|
|
* |
83
|
|
|
* After this is called, the object doesn't hold a valid image any more. |
84
|
|
|
* No operation should be called after that. |
85
|
|
|
*/ |
86
|
|
|
public function destroy() |
87
|
|
|
{ |
88
|
|
|
if ($this->isValid() && !$this->handleReleased) { |
89
|
|
|
imagedestroy($this->handle); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
$this->handle = null; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Returns the GD image resource |
97
|
|
|
* |
98
|
|
|
* @return resource GD image resource |
99
|
|
|
*/ |
100
|
|
|
public function getHandle() |
101
|
|
|
{ |
102
|
|
|
return $this->handle; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @return bool True, if the image object holds a valid GD image, false otherwise |
107
|
|
|
*/ |
108
|
|
|
public function isValid() |
109
|
|
|
{ |
110
|
|
|
return WideImage::isValidImageHandle($this->handle); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Releases the handle |
115
|
|
|
*/ |
116
|
|
|
public function releaseHandle() |
117
|
|
|
{ |
118
|
|
|
$this->handleReleased = true; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Saves an image to a file |
123
|
|
|
* |
124
|
|
|
* The file type is recognized from the $uri. If you save to a GIF8, truecolor images |
125
|
|
|
* are automatically converted to palette. |
126
|
|
|
* |
127
|
|
|
* This method supports additional parameters: quality (for jpeg images) and |
128
|
|
|
* compression quality and filters (for png images). See http://www.php.net/imagejpeg and |
129
|
|
|
* http://www.php.net/imagepng for details. |
130
|
|
|
* |
131
|
|
|
* Examples: |
132
|
|
|
* <code> |
133
|
|
|
* // save to a GIF |
134
|
|
|
* $image->saveToFile('image.gif'); |
135
|
|
|
* |
136
|
|
|
* // save to a PNG with compression=7 and no filters |
137
|
|
|
* $image->saveToFile('image.png', 7, PNG_NO_FILTER); |
138
|
|
|
* |
139
|
|
|
* // save to a JPEG with quality=80 |
140
|
|
|
* $image->saveToFile('image.jpg', 80); |
141
|
|
|
* |
142
|
|
|
* // save to a JPEG with default quality=100 |
143
|
|
|
* $image->saveToFile('image.jpg'); |
144
|
|
|
* </code> |
145
|
|
|
* |
146
|
|
|
* @param string $uri File location |
147
|
|
|
*/ |
148
|
|
|
public function saveToFile($uri) |
149
|
|
|
{ |
150
|
|
|
$mapper = MapperFactory::selectMapper($uri, null); |
151
|
|
|
$args = func_get_args(); |
152
|
|
|
array_unshift($args, $this->getHandle()); |
153
|
|
|
$res = call_user_func_array(array($mapper, 'save'), $args); |
154
|
|
|
|
155
|
|
|
if (!$res) { |
156
|
|
|
throw new UnknownErrorWhileMappingException(get_class($mapper) . " returned an invalid result while saving to $uri"); |
|
|
|
|
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Returns binary string with image data in format specified by $format |
162
|
|
|
* |
163
|
|
|
* Additional parameters may be passed to the function. See \WideImage\Image::saveToFile() for more details. |
164
|
|
|
* |
165
|
|
|
* @param string $format The format of the image |
166
|
|
|
* @return string The binary image data in specified format |
167
|
|
|
*/ |
168
|
|
|
public function asString($format) |
169
|
|
|
{ |
170
|
|
|
ob_start(); |
171
|
|
|
$args = func_get_args(); |
172
|
|
|
$args[0] = null; |
173
|
|
|
array_unshift($args, $this->getHandle()); |
174
|
|
|
|
175
|
|
|
$mapper = MapperFactory::selectMapper(null, $format); |
176
|
|
|
$res = call_user_func_array(array($mapper, 'save'), $args); |
177
|
|
|
|
178
|
|
|
if (!$res) { |
179
|
|
|
throw new UnknownErrorWhileMappingException(get_class($mapper) . " returned an invalid result while writing the image data"); |
|
|
|
|
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
return ob_get_clean(); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Output a header to browser. |
187
|
|
|
* |
188
|
|
|
* @param $name Name of the header |
|
|
|
|
189
|
|
|
* @param $data Data |
|
|
|
|
190
|
|
|
*/ |
191
|
|
|
protected function writeHeader($name, $data) |
192
|
|
|
{ |
193
|
|
|
header($name . ": " . $data); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Outputs the image to browser |
198
|
|
|
* |
199
|
|
|
* Sets headers Content-length and Content-type, and echoes the image in the specified format. |
200
|
|
|
* All other headers (such as Content-disposition) must be added manually. |
201
|
|
|
* |
202
|
|
|
* Example: |
203
|
|
|
* <code> |
204
|
|
|
* WideImage::load('image1.png')->resize(100, 100)->output('gif'); |
205
|
|
|
* </code> |
206
|
|
|
* |
207
|
|
|
* @param string $format Image format |
208
|
|
|
*/ |
209
|
|
|
public function output($format) |
210
|
|
|
{ |
211
|
|
|
$args = func_get_args(); |
212
|
|
|
$data = call_user_func_array(array($this, 'asString'), $args); |
213
|
|
|
|
214
|
|
|
$this->writeHeader('Content-length', strlen($data)); |
215
|
|
|
$this->writeHeader('Content-type', MapperFactory::mimeType($format)); |
216
|
|
|
echo $data; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* @return int Image width |
221
|
|
|
*/ |
222
|
|
|
public function getWidth() |
223
|
|
|
{ |
224
|
|
|
return imagesx($this->handle); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @return int Image height |
229
|
|
|
*/ |
230
|
|
|
public function getHeight() |
231
|
|
|
{ |
232
|
|
|
return imagesy($this->handle); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Allocate a color by RGB values. |
237
|
|
|
* |
238
|
|
|
* @param mixed $R Red-component value or an RGB array (with red, green, blue keys) |
239
|
|
|
* @param int $G If $R is int, this is the green component |
240
|
|
|
* @param int $B If $R is int, this is the blue component |
241
|
|
|
* @return int Image color index |
242
|
|
|
*/ |
243
|
|
|
public function allocateColor($R, $G = null, $B = null) |
244
|
|
|
{ |
245
|
|
|
if (is_array($R)) { |
246
|
|
|
return imageColorAllocate($this->handle, $R['red'], $R['green'], $R['blue']); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
return imageColorAllocate($this->handle, $R, $G, $B); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* @return bool True if the image is transparent, false otherwise |
254
|
|
|
*/ |
255
|
|
|
public function isTransparent() |
256
|
|
|
{ |
257
|
|
|
return $this->getTransparentColor() >= 0; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* @return int Transparent color index |
262
|
|
|
*/ |
263
|
|
|
public function getTransparentColor() |
264
|
|
|
{ |
265
|
|
|
return imagecolortransparent($this->handle); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Sets the current transparent color index. Only makes sense for palette images (8-bit). |
270
|
|
|
* |
271
|
|
|
* @param int $color Transparent color index |
272
|
|
|
*/ |
273
|
|
|
public function setTransparentColor($color) |
274
|
|
|
{ |
275
|
|
|
return imagecolortransparent($this->handle, $color); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Returns a RGB array of the transparent color or null if none. |
280
|
|
|
* |
281
|
|
|
* @return mixed Transparent color RGBA array |
282
|
|
|
*/ |
283
|
|
|
public function getTransparentColorRGB() |
284
|
|
|
{ |
285
|
|
|
$total = imagecolorstotal($this->handle); |
286
|
|
|
$tc = $this->getTransparentColor(); |
287
|
|
|
|
288
|
|
|
if ($tc >= $total && $total > 0) { |
289
|
|
|
return null; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
return $this->getColorRGB($tc); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* Returns a RGBA array for pixel at $x, $y |
297
|
|
|
* |
298
|
|
|
* @param int $x |
299
|
|
|
* @param int $y |
300
|
|
|
* @return array RGB array |
301
|
|
|
*/ |
302
|
|
|
public function getRGBAt($x, $y) |
303
|
|
|
{ |
304
|
|
|
return $this->getColorRGB($this->getColorAt($x, $y)); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* Writes a pixel at the designated coordinates |
309
|
|
|
* |
310
|
|
|
* Takes an associative array of colours and uses getExactColor() to |
311
|
|
|
* retrieve the exact index color to write to the image with. |
312
|
|
|
* |
313
|
|
|
* @param int $x |
314
|
|
|
* @param int $y |
315
|
|
|
* @param array $color |
316
|
|
|
*/ |
317
|
|
|
public function setRGBAt($x, $y, $color) |
318
|
|
|
{ |
319
|
|
|
$this->setColorAt($x, $y, $this->getExactColor($color)); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Returns a color's RGB |
324
|
|
|
* |
325
|
|
|
* @param int $colorIndex Color index |
326
|
|
|
* @return mixed RGBA array for a color with index $colorIndex |
327
|
|
|
*/ |
328
|
|
|
public function getColorRGB($colorIndex) |
329
|
|
|
{ |
330
|
|
|
return imageColorsForIndex($this->handle, $colorIndex); |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Returns an index of the color at $x, $y |
335
|
|
|
* |
336
|
|
|
* @param int $x |
337
|
|
|
* @param int $y |
338
|
|
|
* @return int Color index for a pixel at $x, $y |
339
|
|
|
*/ |
340
|
|
|
public function getColorAt($x, $y) |
341
|
|
|
{ |
342
|
|
|
return imagecolorat($this->handle, $x, $y); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Set the color index $color to a pixel at $x, $y |
347
|
|
|
* |
348
|
|
|
* @param int $x |
349
|
|
|
* @param int $y |
350
|
|
|
* @param int $color Color index |
351
|
|
|
*/ |
352
|
|
|
public function setColorAt($x, $y, $color) |
353
|
|
|
{ |
354
|
|
|
return imagesetpixel($this->handle, $x, $y, $color); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
/** |
358
|
|
|
* Returns closest color index that matches the given RGB value. Uses |
359
|
|
|
* PHP's imagecolorclosest() |
360
|
|
|
* |
361
|
|
|
* @param mixed $R Red or RGBA array |
362
|
|
|
* @param int $G Green component (or null if $R is an RGB array) |
363
|
|
|
* @param int $B Blue component (or null if $R is an RGB array) |
364
|
|
|
* @return int Color index |
365
|
|
|
*/ |
366
|
|
|
public function getClosestColor($R, $G = null, $B = null) |
367
|
|
|
{ |
368
|
|
|
if (is_array($R)) { |
369
|
|
|
return imagecolorclosest($this->handle, $R['red'], $R['green'], $R['blue']); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
return imagecolorclosest($this->handle, $R, $G, $B); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Returns the color index that exactly matches the given RGB value. Uses |
377
|
|
|
* PHP's imagecolorexact() |
378
|
|
|
* |
379
|
|
|
* @param mixed $R Red or RGBA array |
380
|
|
|
* @param int $G Green component (or null if $R is an RGB array) |
381
|
|
|
* @param int $B Blue component (or null if $R is an RGB array) |
382
|
|
|
* @return int Color index |
383
|
|
|
*/ |
384
|
|
|
public function getExactColor($R, $G = null, $B = null) |
385
|
|
|
{ |
386
|
|
|
if (is_array($R)) { |
387
|
|
|
return imagecolorexact($this->handle, $R['red'], $R['green'], $R['blue']); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
return imagecolorexact($this->handle, $R, $G, $B); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Copies transparency information from $sourceImage. Optionally fills |
395
|
|
|
* the image with the transparent color at (0, 0). |
396
|
|
|
* |
397
|
|
|
* @param object $sourceImage |
398
|
|
|
* @param bool $fill True if you want to fill the image with transparent color |
399
|
|
|
*/ |
400
|
|
|
public function copyTransparencyFrom($sourceImage, $fill = true) |
401
|
|
|
{ |
402
|
|
|
if ($sourceImage->isTransparent()) { |
403
|
|
|
$rgba = $sourceImage->getTransparentColorRGB(); |
404
|
|
|
|
405
|
|
|
if ($rgba === null) { |
406
|
|
|
return; |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
if ($this->isTrueColor()) { |
410
|
|
|
$rgba['alpha'] = 127; |
411
|
|
|
$color = $this->allocateColorAlpha($rgba); |
|
|
|
|
412
|
|
|
} else { |
413
|
|
|
$color = $this->allocateColor($rgba); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
$this->setTransparentColor($color); |
|
|
|
|
417
|
|
|
|
418
|
|
|
if ($fill) { |
419
|
|
|
$this->fill(0, 0, $color); |
|
|
|
|
420
|
|
|
} |
421
|
|
|
} |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Fill the image at ($x, $y) with color index $color |
426
|
|
|
* |
427
|
|
|
* @param int $x |
428
|
|
|
* @param int $y |
429
|
|
|
* @param int $color |
430
|
|
|
*/ |
431
|
|
|
public function fill($x, $y, $color) |
432
|
|
|
{ |
433
|
|
|
return imagefill($this->handle, $x, $y, $color); |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* Used internally to create Operation objects |
438
|
|
|
* |
439
|
|
|
* @param string $name |
440
|
|
|
* @return object |
441
|
|
|
*/ |
442
|
|
|
protected function getOperation($name) |
443
|
|
|
{ |
444
|
|
|
return OperationFactory::get($name); |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Returns the image's mask |
449
|
|
|
* |
450
|
|
|
* Mask is a greyscale image where the shade defines the alpha channel (black = transparent, white = opaque). |
451
|
|
|
* |
452
|
|
|
* For opaque images (JPEG), the result will be white. For images with single-color transparency (GIF, 8-bit PNG), |
453
|
|
|
* the areas with the transparent color will be black. For images with alpha channel transparenct, |
454
|
|
|
* the result will be alpha channel. |
455
|
|
|
* |
456
|
|
|
* @return \WideImage\Image An image mask |
457
|
|
|
**/ |
458
|
|
|
public function getMask() |
459
|
|
|
{ |
460
|
|
|
return $this->getOperation('GetMask')->execute($this); |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
/** |
464
|
|
|
* Resize the image to given dimensions. |
465
|
|
|
* |
466
|
|
|
* $width and $height are both smart coordinates. This means that you can pass any of these values in: |
467
|
|
|
* - positive or negative integer (100, -20, ...) |
468
|
|
|
* - positive or negative percent string (30%, -15%, ...) |
469
|
|
|
* - complex coordinate (50% - 20, 15 + 30%, ...) |
470
|
|
|
* - null: if one dimension is null, it's calculated proportionally from the other. |
471
|
|
|
* |
472
|
|
|
* $fit parameter can be set to one of these three values: |
473
|
|
|
* - 'inside': resize proportionally and fit the resulting image tightly in the $width x $height box |
474
|
|
|
* - 'outside': resize proportionally and fit the resulting image tighly outside the box |
475
|
|
|
* - 'fill': resize the image to fill the $width x $height box exactly |
476
|
|
|
* |
477
|
|
|
* $scale parameter can be: |
478
|
|
|
* - 'down': only resize the image if it's larger than the $width x $height box |
479
|
|
|
* - 'up': only resize the image if it's smaller than the $width x $height box |
480
|
|
|
* - 'any': resize the image |
481
|
|
|
* |
482
|
|
|
* Example (resize to half-size): |
483
|
|
|
* <code> |
484
|
|
|
* $smaller = $image->resize('50%'); |
485
|
|
|
* |
486
|
|
|
* $smaller = $image->resize('100', '100', 'inside', 'down'); |
487
|
|
|
* is the same as |
488
|
|
|
* $smaller = $image->resizeDown(100, 100, 'inside'); |
489
|
|
|
* </code> |
490
|
|
|
* |
491
|
|
|
* @param mixed $width The new width (smart coordinate), or null. |
492
|
|
|
* @param mixed $height The new height (smart coordinate), or null. |
493
|
|
|
* @param string $fit 'inside', 'outside', 'fill' |
494
|
|
|
* @param string $scale 'down', 'up', 'any' |
495
|
|
|
* @return \WideImage\Image The resized image |
496
|
|
|
*/ |
497
|
|
|
public function resize($width = null, $height = null, $fit = 'inside', $scale = 'any') |
498
|
|
|
{ |
499
|
|
|
return $this->getOperation('Resize')->execute($this, $width, $height, $fit, $scale); |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
/** |
503
|
|
|
* Same as \WideImage\Image::resize(), but the image is only applied if it is larger then the given dimensions. |
504
|
|
|
* Otherwise, the resulting image retains the source's dimensions. |
505
|
|
|
* |
506
|
|
|
* @param int $width New width, smart coordinate |
507
|
|
|
* @param int $height New height, smart coordinate |
508
|
|
|
* @param string $fit 'inside', 'outside', 'fill' |
509
|
|
|
* @return \WideImage\Image resized image |
510
|
|
|
*/ |
511
|
|
|
public function resizeDown($width = null, $height = null, $fit = 'inside') |
512
|
|
|
{ |
513
|
|
|
return $this->resize($width, $height, $fit, 'down'); |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
/** |
517
|
|
|
* Same as \WideImage\Image::resize(), but the image is only applied if it is smaller then the given dimensions. |
518
|
|
|
* Otherwise, the resulting image retains the source's dimensions. |
519
|
|
|
* |
520
|
|
|
* @param int $width New width, smart coordinate |
521
|
|
|
* @param int $height New height, smart coordinate |
522
|
|
|
* @param string $fit 'inside', 'outside', 'fill' |
523
|
|
|
* @return \WideImage\Image resized image |
524
|
|
|
*/ |
525
|
|
|
public function resizeUp($width = null, $height = null, $fit = 'inside') |
526
|
|
|
{ |
527
|
|
|
return $this->resize($width, $height, $fit, 'up'); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* Rotate the image for angle $angle clockwise. |
532
|
|
|
* |
533
|
|
|
* Preserves transparency. Has issues when saving to a BMP. |
534
|
|
|
* |
535
|
|
|
* @param int $angle Angle in degrees, clock-wise |
536
|
|
|
* @param int $bgColor color of the new background |
537
|
|
|
* @param bool $ignoreTransparent |
538
|
|
|
* @return \WideImage\Image The rotated image |
539
|
|
|
*/ |
540
|
|
|
public function rotate($angle, $bgColor = null, $ignoreTransparent = true) |
541
|
|
|
{ |
542
|
|
|
return $this->getOperation('Rotate')->execute($this, $angle, $bgColor, $ignoreTransparent); |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
/** |
546
|
|
|
* This method lays the overlay (watermark) on the image. |
547
|
|
|
* |
548
|
|
|
* Hint: if the overlay is a truecolor image with alpha channel, you should leave $pct at 100. |
549
|
|
|
* |
550
|
|
|
* This operation supports alignment notation in coordinates: |
551
|
|
|
* <code> |
552
|
|
|
* $watermark = WideImage::load('logo.gif'); |
553
|
|
|
* $base = WideImage::load('picture.jpg'); |
554
|
|
|
* $result = $base->merge($watermark, "right - 10", "bottom - 10", 50); |
555
|
|
|
* // applies a logo aligned to bottom-right corner with a 10 pixel margin |
556
|
|
|
* </code> |
557
|
|
|
* |
558
|
|
|
* @param \WideImage\Image $overlay The overlay image |
559
|
|
|
* @param mixed $left Left position of the overlay, smart coordinate |
560
|
|
|
* @param mixed $top Top position of the overlay, smart coordinate |
561
|
|
|
* @param int $pct The opacity of the overlay |
562
|
|
|
* @return \WideImage\Image The merged image |
563
|
|
|
*/ |
564
|
|
|
public function merge($overlay, $left = 0, $top = 0, $pct = 100) |
565
|
|
|
{ |
566
|
|
|
return $this->getOperation('Merge')->execute($this, $overlay, $left, $top, $pct); |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
/** |
570
|
|
|
* Resizes the canvas of the image, but doesn't scale the content of the image |
571
|
|
|
* |
572
|
|
|
* This operation creates an empty canvas with dimensions $width x $height, filled with |
573
|
|
|
* background color $bg_color and draws the original image onto it at position [$pos_x, $pos_y]. |
574
|
|
|
* |
575
|
|
|
* Arguments $width, $height, $pos_x and $pos_y are all smart coordinates. $width and $height are |
576
|
|
|
* relative to the current image size, $pos_x and $pos_y are relative to the newly calculated |
577
|
|
|
* canvas size. This can be confusing, but it makes sense. See the example below. |
578
|
|
|
* |
579
|
|
|
* The example below loads a 100x150 image and then resizes its canvas to 200% x 100%+20 |
580
|
|
|
* (which evaluates to 200x170). The image is placed at position [10, center+20], which evaluates to [10, 30]. |
581
|
|
|
* <code> |
582
|
|
|
* $image = WideImage::load('someimage.jpg'); // 100x150 |
583
|
|
|
* $white = $image->allocateColor(255, 255, 255); |
584
|
|
|
* $image->resizeCanvas('200%', '100% + 20', 10, 'center+20', $white); |
585
|
|
|
* </code> |
586
|
|
|
* |
587
|
|
|
* The parameter $merge defines whether the original image should be merged onto the new canvas. |
588
|
|
|
* This means it blends transparent color and alpha colors into the background color. If set to false, |
589
|
|
|
* the original image is just copied over, preserving the transparency/alpha information. |
590
|
|
|
* |
591
|
|
|
* You can set the $scale parameter to limit when to resize the canvas. For example, if you want |
592
|
|
|
* to resize the canvas only if the image is smaller than the new size, but leave the image intact |
593
|
|
|
* if it's larger, set it to 'up'. Likewise, if you want to shrink the canvas, but don't want to |
594
|
|
|
* change images that are already smaller, set it to 'down'. |
595
|
|
|
* |
596
|
|
|
* @param mixed $width Width of the new canvas (smart coordinate, relative to current image width) |
597
|
|
|
* @param mixed $height Height of the new canvas (smart coordinate, relative to current image height) |
598
|
|
|
* @param mixed $pos_x x-position of the image (smart coordinate, relative to the new width) |
599
|
|
|
* @param mixed $pos_y y-position of the image (smart coordinate, relative to the new height) |
600
|
|
|
* @param int $bg_color Background color (created with allocateColor or allocateColorAlpha), defaults to null (tries to use a transparent color) |
601
|
|
|
* @param string $scale Possible values: 'up' (enlarge only), 'down' (downsize only), 'any' (resize precisely to $width x $height). Defaults to 'any'. |
602
|
|
|
* @param bool $merge Merge the original image (flatten alpha channel and transparency) or copy it over (preserve). Defaults to false. |
603
|
|
|
* @return \WideImage\Image The resulting image with resized canvas |
604
|
|
|
*/ |
605
|
|
|
public function resizeCanvas($width, $height, $pos_x, $pos_y, $bg_color = null, $scale = 'any', $merge = false) |
606
|
|
|
{ |
607
|
|
|
return $this->getOperation('ResizeCanvas')->execute($this, $width, $height, $pos_x, $pos_y, $bg_color, $scale, $merge); |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
/** |
611
|
|
|
* Returns an image with round corners |
612
|
|
|
* |
613
|
|
|
* You can either set the corners' color or set them transparent. |
614
|
|
|
* |
615
|
|
|
* Note on $smoothness: 1 means jagged edges, 2 is much better, more than 4 doesn't noticeably improve the quality. |
616
|
|
|
* Rendering becomes increasingly slower if you increase smoothness. |
617
|
|
|
* |
618
|
|
|
* Example: |
619
|
|
|
* <code> |
620
|
|
|
* $nice = $ugly->roundCorners(20, $ugly->allocateColor(255, 0, 0), 2); |
621
|
|
|
* </code> |
622
|
|
|
* |
623
|
|
|
* Use $corners parameter to specify which corners to draw rounded. Possible values are |
624
|
|
|
* WideImage::SIDE_TOP_LEFT, WideImage::SIDE_TOP, |
625
|
|
|
* WideImage::SIDE_TOP_RIGHT, WideImage::SIDE_RIGHT, |
626
|
|
|
* WideImage::SIDE_BOTTOM_RIGHT, WideImage::SIDE_BOTTOM, |
627
|
|
|
* WideImage::SIDE_BOTTOM_LEFT, WideImage::SIDE_LEFT, and WideImage::SIDE_ALL. |
628
|
|
|
* You can specify any combination of corners with a + operation, see example below. |
629
|
|
|
* |
630
|
|
|
* Example: |
631
|
|
|
* <code> |
632
|
|
|
* $white = $image->allocateColor(255, 255, 255); |
633
|
|
|
* $diagonal_corners = $image->roundCorners(15, $white, 2, WideImage::SIDE_TOP_LEFT + WideImage::SIDE_BOTTOM_RIGHT); |
634
|
|
|
* $right_corners = $image->roundCorners(15, $white, 2, WideImage::SIDE_RIGHT); |
635
|
|
|
* </code> |
636
|
|
|
* |
637
|
|
|
* @param int $radius Radius of the corners |
638
|
|
|
* @param int $color The color of corners. If null, corners are rendered transparent (slower than using a solid color). |
639
|
|
|
* @param int $smoothness Specify the level of smoothness. Suggested values from 1 to 4. |
640
|
|
|
* @param int $corners Specify which corners to draw (defaults to WideImage::SIDE_ALL = all corners) |
641
|
|
|
* @return \WideImage\Image The resulting image with round corners |
642
|
|
|
*/ |
643
|
|
|
public function roundCorners($radius, $color = null, $smoothness = 2, $corners = 255) |
644
|
|
|
{ |
645
|
|
|
return $this->getOperation('RoundCorners')->execute($this, $radius, $color, $smoothness, $corners); |
646
|
|
|
} |
647
|
|
|
|
648
|
|
|
/** |
649
|
|
|
* Returns an image with applied mask |
650
|
|
|
* |
651
|
|
|
* A mask is a grayscale image, where the shade determines the alpha channel. Black is fully transparent |
652
|
|
|
* and white is fully opaque. |
653
|
|
|
* |
654
|
|
|
* @param \WideImage\Image $mask The mask image, greyscale |
655
|
|
|
* @param mixed $left Left coordinate, smart coordinate |
656
|
|
|
* @param mixed $top Top coordinate, smart coordinate |
657
|
|
|
* @return \WideImage\Image The resulting image |
658
|
|
|
**/ |
659
|
|
|
public function applyMask($mask, $left = 0, $top = 0) |
660
|
|
|
{ |
661
|
|
|
return $this->getOperation('ApplyMask')->execute($this, $mask, $left, $top); |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
/** |
665
|
|
|
* Applies a filter |
666
|
|
|
* |
667
|
|
|
* @param int $filter One of the IMG_FILTER_* constants |
668
|
|
|
* @param int $arg1 |
669
|
|
|
* @param int $arg2 |
670
|
|
|
* @param int $arg3 |
671
|
|
|
* @param int $arg4 |
672
|
|
|
* @return \WideImage\Image |
673
|
|
|
*/ |
674
|
|
|
public function applyFilter($filter, $arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null) |
675
|
|
|
{ |
676
|
|
|
return $this->getOperation('ApplyFilter')->execute($this, $filter, $arg1, $arg2, $arg3, $arg4); |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
/** |
680
|
|
|
* Applies convolution matrix with imageconvolution() |
681
|
|
|
* |
682
|
|
|
* @param array $matrix |
683
|
|
|
* @param float $div |
684
|
|
|
* @param float $offset |
685
|
|
|
* @return \WideImage\Image |
686
|
|
|
*/ |
687
|
|
|
public function applyConvolution($matrix, $div, $offset) |
688
|
|
|
{ |
689
|
|
|
return $this->getOperation('ApplyConvolution')->execute($this, $matrix, $div, $offset); |
690
|
|
|
} |
691
|
|
|
|
692
|
|
|
/** |
693
|
|
|
* Returns a cropped rectangular portion of the image |
694
|
|
|
* |
695
|
|
|
* If the rectangle specifies area that is out of bounds, it's limited to the current image bounds. |
696
|
|
|
* |
697
|
|
|
* Examples: |
698
|
|
|
* <code> |
699
|
|
|
* $cropped = $img->crop(10, 10, 150, 200); // crops a 150x200 rect at (10, 10) |
700
|
|
|
* $cropped = $img->crop(-100, -50, 100, 50); // crops a 100x50 rect at the right-bottom of the image |
701
|
|
|
* $cropped = $img->crop('25%', '25%', '50%', '50%'); // crops a 50%x50% rect from the center of the image |
702
|
|
|
* </code> |
703
|
|
|
* |
704
|
|
|
* This operation supports alignment notation in left/top coordinates. |
705
|
|
|
* Example: |
706
|
|
|
* <code> |
707
|
|
|
* $cropped = $img->crop("right", "bottom", 100, 200); // crops a 100x200 rect from right bottom |
708
|
|
|
* $cropped = $img->crop("center", "middle", 50, 30); // crops a 50x30 from the center of the image |
709
|
|
|
* </code> |
710
|
|
|
* |
711
|
|
|
* @param mixed $left Left-coordinate of the crop rect, smart coordinate |
712
|
|
|
* @param mixed $top Top-coordinate of the crop rect, smart coordinate |
713
|
|
|
* @param mixed $width Width of the crop rect, smart coordinate |
714
|
|
|
* @param mixed $height Height of the crop rect, smart coordinate |
715
|
|
|
* @return \WideImage\Image The cropped image |
716
|
|
|
**/ |
717
|
|
|
public function crop($left = 0, $top = 0, $width = '100%', $height = '100%') |
718
|
|
|
{ |
719
|
|
|
return $this->getOperation('Crop')->execute($this, $left, $top, $width, $height); |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* Performs an auto-crop on the image |
724
|
|
|
* |
725
|
|
|
* The image is auto-cropped from each of four sides. All sides are |
726
|
|
|
* scanned for pixels that differ from $base_color for more than |
727
|
|
|
* $rgb_threshold in absolute RGB difference. If more than $pixel_cutoff |
728
|
|
|
* differentiating pixels are found, that line is considered to be the crop line for the side. |
729
|
|
|
* If the line isn't different enough, the algorithm procedes to the next line |
730
|
|
|
* towards the other edge of the image. |
731
|
|
|
* |
732
|
|
|
* When the crop rectangle is found, it's enlarged by the $margin value on each of the four sides. |
733
|
|
|
* |
734
|
|
|
* @param int $margin Margin for the crop rectangle, can be negative. |
735
|
|
|
* @param int $rgb_threshold RGB difference which still counts as "same color". |
736
|
|
|
* @param int $pixel_cutoff How many pixels need to be different to mark a cut line. |
737
|
|
|
* @param int $base_color The base color index. If none specified (or null given), left-top pixel is used. |
738
|
|
|
* @return \WideImage\Image The cropped image |
739
|
|
|
*/ |
740
|
|
|
public function autoCrop($margin = 0, $rgb_threshold = 0, $pixel_cutoff = 1, $base_color = null) |
741
|
|
|
{ |
742
|
|
|
return $this->getOperation('AutoCrop')->execute($this, $margin, $rgb_threshold, $pixel_cutoff, $base_color); |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
/** |
746
|
|
|
* Returns a negative of the image |
747
|
|
|
* |
748
|
|
|
* This operation differs from calling \WideImage\Image::applyFilter(IMG_FILTER_NEGATIVE), because it's 8-bit and transparency safe. |
749
|
|
|
* This means it will return an 8-bit image, if the source image is 8-bit. If that 8-bit image has a palette transparency, |
750
|
|
|
* the resulting image will keep transparency. |
751
|
|
|
* |
752
|
|
|
* @return \WideImage\Image negative of the image |
753
|
|
|
*/ |
754
|
|
|
public function asNegative() |
755
|
|
|
{ |
756
|
|
|
return $this->getOperation('AsNegative')->execute($this); |
757
|
|
|
} |
758
|
|
|
|
759
|
|
|
/** |
760
|
|
|
* Returns a grayscale copy of the image |
761
|
|
|
* |
762
|
|
|
* @return \WideImage\Image grayscale copy |
763
|
|
|
**/ |
764
|
|
|
public function asGrayscale() |
765
|
|
|
{ |
766
|
|
|
return $this->getOperation('AsGrayscale')->execute($this); |
767
|
|
|
} |
768
|
|
|
|
769
|
|
|
/** |
770
|
|
|
* Returns a mirrored copy of the image |
771
|
|
|
* |
772
|
|
|
* @return \WideImage\Image Mirrored copy |
773
|
|
|
**/ |
774
|
|
|
public function mirror() |
775
|
|
|
{ |
776
|
|
|
return $this->getOperation('Mirror')->execute($this); |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
/** |
780
|
|
|
* Applies the unsharp filter |
781
|
|
|
* |
782
|
|
|
* @param float $amount |
783
|
|
|
* @param float $radius |
784
|
|
|
* @param float $threshold |
785
|
|
|
* @return \WideImage\Image Unsharpened copy of the image |
786
|
|
|
**/ |
787
|
|
|
public function unsharp($amount, $radius, $threshold) |
788
|
|
|
{ |
789
|
|
|
return $this->getOperation('Unsharp')->execute($this, $amount, $radius, $threshold); |
790
|
|
|
} |
791
|
|
|
|
792
|
|
|
/** |
793
|
|
|
* Returns a flipped (mirrored over horizontal line) copy of the image |
794
|
|
|
* |
795
|
|
|
* @return \WideImage\Image Flipped copy |
796
|
|
|
**/ |
797
|
|
|
public function flip() |
798
|
|
|
{ |
799
|
|
|
return $this->getOperation('Flip')->execute($this); |
800
|
|
|
} |
801
|
|
|
|
802
|
|
|
/** |
803
|
|
|
* Corrects gamma on the image |
804
|
|
|
* |
805
|
|
|
* @param float $inputGamma |
806
|
|
|
* @param float $outputGamma |
807
|
|
|
* @return \WideImage\Image Image with corrected gamma |
808
|
|
|
**/ |
809
|
|
|
public function correctGamma($inputGamma, $outputGamma) |
810
|
|
|
{ |
811
|
|
|
return $this->getOperation('CorrectGamma')->execute($this, $inputGamma, $outputGamma); |
812
|
|
|
} |
813
|
|
|
|
814
|
|
|
/** |
815
|
|
|
* Adds noise to the image |
816
|
|
|
* |
817
|
|
|
* @author Tomasz Kapusta |
818
|
|
|
* |
819
|
|
|
* @param int $amount Number of noise pixels to add |
820
|
|
|
* @param string $type Type of noise 'salt&pepper', 'color' or 'mono' |
821
|
|
|
* @return \WideImage\Image Image with noise added |
822
|
|
|
**/ |
823
|
|
|
public function addNoise($amount, $type) |
824
|
|
|
{ |
825
|
|
|
return $this->getOperation('AddNoise')->execute($this, $amount, $type); |
826
|
|
|
} |
827
|
|
|
|
828
|
|
|
/** |
829
|
|
|
* Used internally to execute operations |
830
|
|
|
* |
831
|
|
|
* @param string $name |
832
|
|
|
* @param array $args |
833
|
|
|
* @return \WideImage\Image |
834
|
|
|
*/ |
835
|
|
|
public function __call($name, $args) |
836
|
|
|
{ |
837
|
|
|
$op = $this->getOperation($name); |
838
|
|
|
array_unshift($args, $this); |
839
|
|
|
return call_user_func_array(array($op, 'execute'), $args); |
840
|
|
|
} |
841
|
|
|
|
842
|
|
|
/** |
843
|
|
|
* Returns an image in GIF or PNG format |
844
|
|
|
* |
845
|
|
|
* @return string |
846
|
|
|
*/ |
847
|
|
|
public function __toString() |
848
|
|
|
{ |
849
|
|
|
if ($this->isTransparent()) { |
850
|
|
|
return $this->asString('gif'); |
851
|
|
|
} |
852
|
|
|
|
853
|
|
|
return $this->asString('png'); |
854
|
|
|
} |
855
|
|
|
|
856
|
|
|
/** |
857
|
|
|
* Returns a copy of the image object |
858
|
|
|
* |
859
|
|
|
* @return \WideImage\Image The copy |
860
|
|
|
**/ |
861
|
|
|
public function copy() |
862
|
|
|
{ |
863
|
|
|
$dest = $this->doCreate($this->getWidth(), $this->getHeight()); |
|
|
|
|
864
|
|
|
$dest->copyTransparencyFrom($this, true); |
865
|
|
|
$this->copyTo($dest, 0, 0); |
866
|
|
|
return $dest; |
867
|
|
|
} |
868
|
|
|
|
869
|
|
|
/** |
870
|
|
|
* Copies this image onto another image |
871
|
|
|
* |
872
|
|
|
* @param \WideImage\Image $dest |
873
|
|
|
* @param int $left |
874
|
|
|
* @param int $top |
875
|
|
|
**/ |
876
|
|
|
public function copyTo($dest, $left = 0, $top = 0) |
877
|
|
|
{ |
878
|
|
|
if (!imagecopy($dest->getHandle(), $this->handle, $left, $top, 0, 0, $this->getWidth(), $this->getHeight())) { |
879
|
|
|
throw new GDFunctionResultException("imagecopy() returned false"); |
880
|
|
|
} |
881
|
|
|
} |
882
|
|
|
|
883
|
|
|
/** |
884
|
|
|
* Returns the canvas object |
885
|
|
|
* |
886
|
|
|
* The Canvas object can be used to draw text and shapes on the image |
887
|
|
|
* |
888
|
|
|
* Examples: |
889
|
|
|
* <code> |
890
|
|
|
* $img = WideImage::load('pic.jpg); |
891
|
|
|
* $canvas = $img->getCanvas(); |
892
|
|
|
* $canvas->useFont('arial.ttf', 15, $img->allocateColor(200, 220, 255)); |
893
|
|
|
* $canvas->writeText(10, 50, "Hello world!"); |
894
|
|
|
* |
895
|
|
|
* $canvas->filledRectangle(10, 10, 80, 40, $img->allocateColor(255, 127, 255)); |
896
|
|
|
* $canvas->line(60, 80, 30, 100, $img->allocateColor(255, 0, 0)); |
897
|
|
|
* $img->saveToFile('new.png'); |
898
|
|
|
* </code> |
899
|
|
|
* |
900
|
|
|
* @return \WideImage\Canvas The Canvas object |
901
|
|
|
**/ |
902
|
|
|
function getCanvas() |
|
|
|
|
903
|
|
|
{ |
904
|
|
|
if ($this->canvas == null) { |
905
|
|
|
$this->canvas = new Canvas($this); |
906
|
|
|
} |
907
|
|
|
|
908
|
|
|
return $this->canvas; |
909
|
|
|
} |
910
|
|
|
|
911
|
|
|
/** |
912
|
|
|
* Returns true if the image is true-color, false otherwise |
913
|
|
|
* |
914
|
|
|
* @return bool |
915
|
|
|
**/ |
916
|
|
|
abstract public function isTrueColor(); |
917
|
|
|
|
918
|
|
|
/** |
919
|
|
|
* Returns a true-color copy of the image |
920
|
|
|
* |
921
|
|
|
* @return \WideImage\TrueColorImage |
922
|
|
|
**/ |
923
|
|
|
abstract public function asTrueColor(); |
924
|
|
|
|
925
|
|
|
/** |
926
|
|
|
* Returns a palette copy (8bit) of the image |
927
|
|
|
* |
928
|
|
|
* @param int $nColors Number of colors in the resulting image, more than 0, less or equal to 255 |
929
|
|
|
* @param bool $dither Use dithering or not |
930
|
|
|
* @param bool $matchPalette Set to true to use imagecolormatch() to match the resulting palette more closely to the original image |
931
|
|
|
* @return \WideImage\Image |
932
|
|
|
**/ |
933
|
|
|
abstract public function asPalette($nColors = 255, $dither = null, $matchPalette = true); |
934
|
|
|
|
935
|
|
|
/** |
936
|
|
|
* Retrieve an image with selected channels |
937
|
|
|
* |
938
|
|
|
* Examples: |
939
|
|
|
* <code> |
940
|
|
|
* $channels = $img->getChannels('red', 'blue'); |
941
|
|
|
* $channels = $img->getChannels('alpha', 'green'); |
942
|
|
|
* $channels = $img->getChannels(array('green', 'blue')); |
943
|
|
|
* </code> |
944
|
|
|
* |
945
|
|
|
* @return \WideImage\Image |
946
|
|
|
**/ |
947
|
|
|
abstract public function getChannels(); |
948
|
|
|
|
949
|
|
|
/** |
950
|
|
|
* Returns an image without an alpha channel |
951
|
|
|
* |
952
|
|
|
* @return \WideImage\Image |
953
|
|
|
**/ |
954
|
|
|
abstract public function copyNoAlpha(); |
955
|
|
|
|
956
|
|
|
/** |
957
|
|
|
* Returns an array of serializable protected variables. Called automatically upon serialize(). |
958
|
|
|
* |
959
|
|
|
* @return array |
960
|
|
|
*/ |
961
|
|
|
public function __sleep() |
962
|
|
|
{ |
963
|
|
|
$this->sdata = $this->asString('png'); |
964
|
|
|
|
965
|
|
|
return array('sdata', 'handleReleased'); |
966
|
|
|
} |
967
|
|
|
|
968
|
|
|
/** |
969
|
|
|
* Restores an image from serialization. Called automatically upon unserialize(). |
970
|
|
|
*/ |
971
|
|
|
public function __wakeup() |
972
|
|
|
{ |
973
|
|
|
$temp_image = WideImage::loadFromString($this->sdata); |
974
|
|
|
$temp_image->releaseHandle(); |
975
|
|
|
$this->handle = $temp_image->handle; |
976
|
|
|
$temp_image = null; |
|
|
|
|
977
|
|
|
$this->sdata = null; |
978
|
|
|
} |
979
|
|
|
} |
980
|
|
|
|