Skip to content

Commit ecd8f9b

Browse files
feat: Make Image utility immutable
1 parent 241f9b0 commit ecd8f9b

3 files changed

Lines changed: 163 additions & 124 deletions

File tree

docs/utils/image.md

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ Image::setDriver(ImageDriver::GD);
4747
## Image Processing Operations
4848

4949
The `Image` utility class provides a range of image processing operations that can be performed on images. These
50-
operations include:
50+
operations are immutable, meaning that all operations essentially return a new instance of `Image` with the operation
51+
applied without affecting the current instance. This allows for method chaining and simplifies the process of applying
52+
multiple operations to an image.
5153

5254
- ### `read(string $input, array $options = [])`
5355
Reads an image from a file path, URL, or resource and returns an image object.
@@ -94,8 +96,7 @@ operations include:
9496
```
9597

9698
- ### `resize(int $width, int $height, int|Resample $resample = 2)`
97-
Resizes an image to the specified width and height, using the specified resampling method. This operation affects the
98-
instance it's called on, but still returns that instance for method chaining.
99+
Resizes an image to the specified width and height, using the specified resampling method.
99100

100101
Parameters:
101102
- `$width` (int) The target width of the resized image.
@@ -111,8 +112,7 @@ operations include:
111112
```
112113

113114
- ### `thumbnail(int $width, int $height, int|Resample $resample = 2)`
114-
Creates a thumbnail of the image with the specified width and height, using the specified resampling method. This
115-
operation affects the instance it's called on, but still returns that instance for method chaining.
115+
Creates a thumbnail of the image with the specified width and height, using the specified resampling method.
116116

117117
Parameters:
118118
- `$width` (int) The target width of the thumbnail.
@@ -128,8 +128,7 @@ operations include:
128128
```
129129

130130
- ### `crop(int $xMin, int $yMin, int $xMax, int $yMax)`
131-
Crops the image to the specified bounding box defined by the top-left and bottom-right coordinates. This operation
132-
affects the instance it's called on, but still returns that instance for method chaining.
131+
Crops the image to the specified bounding box defined by the top-left and bottom-right coordinates.
133132

134133
Parameters:
135134
- `$xMin` (int) The x-coordinate of the top-left corner of the bounding box.
@@ -146,8 +145,7 @@ operations include:
146145
```
147146

148147
- ### `centerCrop(int $width, int $height)`
149-
Crops the image to the specified width and height by centering the crop around the image's center. This operation
150-
affects the instance it's called on, but still returns that instance for method chaining.
148+
Crops the image to the specified width and height by centering the crop around the image's center.
151149

152150
Parameters:
153151
- `$width` (int) The target width of the cropped image.
@@ -162,8 +160,7 @@ operations include:
162160
```
163161

164162
- ### `pad(int $left, int $right, int $top, int $bottom)`
165-
Pads the image with the specified number of pixels on each side. This operation affects the instance it's called on,
166-
but still returns that instance for method chaining.
163+
Pads the image with the specified number of pixels on each side.
167164

168165
Parameters:
169166
- `$left` (int) The number of pixels to add to the left side.
@@ -180,8 +177,7 @@ operations include:
180177
```
181178

182179
- ### `grayscale()`
183-
Converts the image to grayscale. This operation affects the instance it's called on, but still returns that instance
184-
for method chaining.
180+
Converts the image to grayscale.
185181

186182
Returns:
187183
- An image object representing the grayscale image.
@@ -192,8 +188,7 @@ operations include:
192188
```
193189

194190
- ### `rgb()`
195-
Converts the image to RGB color space. This operation affects the instance it's called on, but still returns that
196-
instance for method chaining.
191+
Converts the image to RGB color space.
197192

198193
Returns:
199194
- An image object representing the RGB image.
@@ -204,8 +199,7 @@ operations include:
204199
```
205200

206201
- ### `rgba()`
207-
Converts the image to RGBA color space. This operation affects the instance it's called on, but still returns that
208-
instance for method chaining.
202+
Converts the image to RGBA color space.
209203

210204
Returns:
211205
- An image object representing the RGBA image.
@@ -214,10 +208,27 @@ operations include:
214208
```php
215209
$rgbaImage = $image->rgba();
216210
```
211+
212+
- ### `applyMask(Image $mask)`
213+
Applies a mask to the current image.
214+
215+
Parameters:
216+
- `$mask` (Image) The mask to apply.
217+
218+
Returns:
219+
- An image object representing the image with the mask applied.
220+
221+
Throws:
222+
- `InvalidArgumentException` if the given mask doesn't match the current image's size or if the image driver is unsupported.
223+
- `RuntimeException` if the apply mask operation fails.
224+
225+
Example:
226+
```php
227+
$maskedImage = $image->applyMask($mask);
228+
```
217229

218230
- ### `drawRectangle(int $xMin, int $yMin, int $xMax, int $yMax, string $color = 'FFF', $fill = false, float $thickness = 1)`
219-
Draws a rectangle on the image with the specified coordinates, color, and thickness. This operation affects the
220-
instance it's called on, but still returns that instance for method chaining.
231+
Draws a rectangle on the image with the specified coordinates, color, and thickness.
221232

222233
Parameters:
223234
- `$xMin` (int) The x-coordinate of the top-left corner of the rectangle.
@@ -238,8 +249,7 @@ operations include:
238249

239250
- ### `drawText(string $text, int $xPos, int $yPos, string $fontFile, int $fontSize = 16, string $color = 'FFF')`
240251

241-
Draws text on the image at the specified position with the specified font, size, and color. This operation affects the
242-
instance it's called on, but still returns that instance for method chaining.
252+
Draws text on the image at the specified position with the specified font, size, and color.
243253

244254
Parameters:
245255
- `$text` (string) The text to draw on the image.

src/FeatureExtractors/ImageFeatureExtractor.php

Lines changed: 23 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,64 +7,36 @@
77

88
use Codewithkyrian\Transformers\Tensor\Tensor;
99
use Codewithkyrian\Transformers\Utils\Image;
10+
use Exception;
1011
use Imagine\Image\Point;
1112

1213
class ImageFeatureExtractor extends FeatureExtractor
1314
{
14-
/**
15-
* The mean values for image normalization.
16-
*
17-
* @var int|int[]
18-
*/
15+
/** The mean values for image normalization. */
1916
protected int|array|null $imageMean;
2017

21-
/**
22-
* The standard deviation values for image normalization.
23-
*
24-
* @var int|int[]
25-
*/
18+
/** The standard deviation values for image normalization. */
2619
protected int|array|null $imageStd;
2720

28-
/*
29-
* What method to use for resampling.
30-
*/
21+
/* What method to use for resampling. */
3122
protected int $resample;
3223

33-
/**
34-
* Whether to rescale the image pixel values to the [0,1] range.
35-
*
36-
* @var bool
37-
*/
24+
/** Whether to rescale the image pixel values to the [0,1] range. */
3825
protected bool $doRescale;
3926

40-
/**
41-
* The factor to use for rescaling the image pixel values.
42-
*
43-
* @var float
44-
*/
27+
/** The factor to use for rescaling the image pixel values. */
4528
protected float $rescaleFactor;
4629

47-
/**
48-
* Whether to normalize the image pixel values.
49-
*
50-
* @var ?bool
51-
*/
30+
/** Whether to normalize the image pixel values. */
5231
protected ?bool $doNormalize;
5332

54-
/**
55-
* Whether to resize the image.
56-
*
57-
* @var ?bool
58-
*/
33+
/** Whether to resize the image. */
5934
protected ?bool $doResize;
6035

36+
/** The size to resize the image to. */
6137
protected ?bool $doThumbnail;
6238

63-
/**
64-
* The size to resize the image to.
65-
*
66-
* @var ?array
67-
*/
39+
/** The size to resize the image to. */
6840
protected ?array $size;
6941
protected mixed $sizeDivisibility;
7042
protected ?bool $doCenterCrop;
@@ -171,7 +143,7 @@ public function cropMargin(Image $image, int $grayThreshold = 200): static
171143
* @param int $constantValues The constant value to use for padding.
172144
*
173145
* @return Tensor The padded pixel data and image dimensions.
174-
* @throws \Exception
146+
* @throws Exception
175147
*/
176148
public function padImage(
177149
Tensor $imageTensor,
@@ -233,7 +205,7 @@ public function padImage(
233205

234206
if ($mode === 'symmetric') {
235207
if ($center) {
236-
throw new \Exception('`center` padding is not supported when `mode` is set to `symmetric`.');
208+
throw new Exception('`center` padding is not supported when `mode` is set to `symmetric`.');
237209
// TODO: Implement this
238210
}
239211
$h1 = $imageHeight - 1;
@@ -278,6 +250,7 @@ private function calculateReflectOffset(int $val, int $max): int
278250
* @param int|array|null $size The size to use for resizing the image.
279251
*
280252
* @return array The target (width, height) dimension of the output image after resizing.
253+
* @throws Exception
281254
*/
282255
public function getResizeOutputImageSize(Image $image, int|array|null $size): array
283256
{
@@ -351,7 +324,7 @@ public function getResizeOutputImageSize(Image $image, int|array|null $size): ar
351324
} elseif ($this->sizeDivisibility != null) {
352325
return $this->enforceSizeDivisibility([$srcWidth, $srcHeight], $this->sizeDivisibility);
353326
} else {
354-
throw new \Exception("Could not resize image due to unsupported 'size' parameter passed: ".json_encode($size));
327+
throw new Exception("Could not resize image due to unsupported 'size' parameter passed: ".json_encode($size));
355328
}
356329
}
357330

@@ -360,13 +333,13 @@ public function getResizeOutputImageSize(Image $image, int|array|null $size): ar
360333
* Preprocesses the given image.
361334
*
362335
* @param Image $image The image to preprocess.
363-
* @param ?bool $doNormalize
364-
* @param ?bool $doPad
365-
* @param ?bool $doConvertRGB
366-
* @param ?bool $doConvertGrayscale
336+
* @param ?bool $doNormalize Whether to normalize the image.
337+
* @param ?bool $doPad Whether to pad the image.
338+
* @param ?bool $doConvertRGB Whether to convert the image to RGB.
339+
* @param ?bool $doConvertGrayscale Whether to convert the image to grayscale.
367340
*
368341
* @return array The preprocessed image.
369-
* @throws \Exception
342+
* @throws Exception
370343
*/
371344
public function preprocess(
372345
Image $image,
@@ -382,7 +355,6 @@ public function preprocess(
382355
$image = $image->cropMargin();
383356
}
384357

385-
386358
$originalInputSize = $image->size(); // original image size
387359

388360
// Convert image to RGB if specified in config.
@@ -414,7 +386,7 @@ public function preprocess(
414386
$cropHeight = $this->cropSize['height'];
415387
}
416388

417-
$image = $image->centerCrop($cropWidth, $cropHeight);
389+
$image = $image->centerCrop($cropWidth, $cropHeight);
418390
}
419391

420392
$reshapedInputSize = $image->size();
@@ -449,7 +421,7 @@ public function preprocess(
449421
$imageStd = $imageStd->reshape($imageTensor->shape());
450422

451423
if (count($imageMean) !== $image->channels || count($imageStd) !== $image->channels) {
452-
throw new \Exception("When set to arrays, the length of `imageMean` (".count($imageMean).") and `imageStd` (".count($imageStd).") must match the number of channels in the image ({$image->channels}).");
424+
throw new Exception("When set to arrays, the length of `imageMean` (".count($imageMean).") and `imageStd` (".count($imageStd).") must match the number of channels in the image ({$image->channels}).");
453425
}
454426

455427
// Normalize pixel data
@@ -485,25 +457,18 @@ public function preprocess(
485457
*/
486458
public function __invoke(Image|array $images, ...$args): array
487459
{
488-
// Ensure $images is an array
489460
if (!is_array($images)) {
490461
$images = [$images];
491462
}
492463

493-
// Preprocess each image
494-
$imageData = [];
495-
foreach ($images as $image) {
496-
$imageData[] = $this->preprocess($image);
497-
}
464+
$imageData = array_map([$this, 'preprocess'], $images);
498465

499466
$pixelValues = array_column($imageData, 'pixel_values');
500467
$originalSizes = array_column($imageData, 'original_size');
501468
$reshapedInputSizes = array_column($imageData, 'reshaped_input_size');
502469

503-
$stackedPixelValues = Tensor::stack($pixelValues, 0);
504-
505470
return [
506-
'pixel_values' => $stackedPixelValues,
471+
'pixel_values' => Tensor::stack($pixelValues),
507472
'original_sizes' => $originalSizes,
508473
'reshaped_input_sizes' => $reshapedInputSizes
509474
];

0 commit comments

Comments
 (0)