Skip to content

Commit dbd21cc

Browse files
committed
Merge branch 'add-array-access'
2 parents 9df4a16 + c895baa commit dbd21cc

File tree

3 files changed

+211
-13
lines changed

3 files changed

+211
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to `:vips` will be documented in this file.
55

66
### Added
77
- add Image::newInterpolator() [Kleis Auke Wolthuizen]
8+
- implement array access interface [John Cupitt]
89

910
### Deprecated
1011
- Nothing

src/Image.php

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@
344344
* to append a constant 255 band to an image, perhaps to add an alpha channel. Of
345345
* course you can also write:
346346
*
347-
* ```ruby
347+
* ```php
348348
* $result = $image->bandjoin($image2);
349349
* $result = $image->bandjoin([image2, image3]);
350350
* $result = Image::bandjoin([image1, image2, image3]);
@@ -353,6 +353,43 @@
353353
*
354354
* and so on.
355355
*
356+
* # Array access
357+
*
358+
* Images can be treated as arrays of bands. You can write:
359+
*
360+
* ```php
361+
* $result = $image[1];
362+
* ```
363+
*
364+
* to get band 1 from an image (green, in an RGB image).
365+
*
366+
* You can assign to bands as well. You can write:
367+
*
368+
* ```php
369+
* $image[1] = $other_image;
370+
* ```
371+
*
372+
* And band 1 will be replaced by all the bands in `$other_image` using
373+
* `bandjoin`. Use no offset to mean append, use -1 to mean prepend:
374+
*
375+
* ```php
376+
* $image[] = $other_image; // append bands from other
377+
* $image[-1] = $other_image; // prepend bands from other
378+
* ```
379+
*
380+
* You can use number and array constants as well, for example:
381+
*
382+
* ```php
383+
* $image[] = 255; // append a constant 255
384+
* $image[1] = [1, 2, 3]; // swap band 1 for three constant bands
385+
* ```
386+
*
387+
* Finally, you can delete bands with `unset`:
388+
*
389+
* ```php
390+
* unset($image[1]); // remove band 1
391+
* ```
392+
*
356393
* # Exceptions
357394
*
358395
* The wrapper spots errors from vips operations and throws
@@ -1221,7 +1258,7 @@ public function hasAlpha(): bool
12211258
}
12221259

12231260
/**
1224-
* Our ArrayAccess interface ... we allow [] to get band.
1261+
* Does band exist in image.
12251262
*
12261263
* @param mixed $offset The index to fetch.
12271264
*
@@ -1233,7 +1270,7 @@ public function offsetExists($offset): bool
12331270
}
12341271

12351272
/**
1236-
* Our ArrayAccess interface ... we allow [] to get band.
1273+
* Get band from image.
12371274
*
12381275
* @param mixed $offset The index to fetch.
12391276
*
@@ -1245,28 +1282,93 @@ public function offsetGet($offset): Image
12451282
}
12461283

12471284
/**
1248-
* Our ArrayAccess interface ... we allow [] to get band.
1285+
* Set a band.
12491286
*
1250-
* @param mixed $offset The index to set.
1287+
* Use `$image[1] = $other_image;' to remove band 1 from this image,
1288+
* replacing it with all the bands in `$other_image`.
1289+
*
1290+
* Use `$image[] = $other_image;' to append all the bands in `$other_image`
1291+
* to `$image`.
1292+
*
1293+
* Use `$image[-1] = $other_image;` to prepend all the bands in
1294+
* `$other_image` to `$image`.
1295+
*
1296+
* You can use constants or arrays in place of `$other_image`. Use `$image[]
1297+
* = 255;` to append a constant 255 band, for example, or `$image[1]
1298+
* = [1, 2];` to replace band 1 with two constant bands.
1299+
*
1300+
* @param int $offset The index to set.
12511301
* @param Image $value The band to insert
12521302
*
1253-
* @return Image the expanded image.
1303+
* @return void
12541304
*/
1255-
public function offsetSet($offset, $value): Image
1305+
public function offsetSet($offset, $value)
12561306
{
1257-
throw new \BadMethodCallException('Image::offsetSet: not implemented');
1307+
// no offset means append
1308+
if (is_null($offset)) {
1309+
$offset = $this->bands;
1310+
}
1311+
1312+
if (!is_int($offset)) {
1313+
throw new \BadMethodCallException('Image::offsetSet: offset is not integer or null');
1314+
}
1315+
1316+
// number of bands to the left and right of $value
1317+
$n_left = min($this->bands, max(0, $offset));
1318+
$n_right = min($this->bands, max(0, $this->bands - 1 - $offset));
1319+
$offset = $this->bands - $n_right;
1320+
1321+
// if we are setting a constant as the first element, we must expand it
1322+
// to an image, since bandjoin must have an image as the first argument
1323+
if ($n_left == 0 && !($value instanceof Image)) {
1324+
$value = self::imageize($this, $value);
1325+
}
1326+
1327+
$components = [];
1328+
if ($n_left > 0) {
1329+
$components[] = $this->extract_band(0, ['n' => $n_left]);
1330+
}
1331+
$components[] = $value;
1332+
if ($n_right > 0) {
1333+
$components[] = $this->extract_band($offset, ['n' => $n_right]);
1334+
}
1335+
1336+
$head = array_shift($components);
1337+
$this->image = $head->bandjoin($components)->image;
12581338
}
12591339

12601340
/**
1261-
* Our ArrayAccess interface ... we allow [] to get band.
1341+
* Remove a band from an image.
12621342
*
1263-
* @param mixed $offset The index to remove.
1343+
* @param int $offset The index to remove.
12641344
*
1265-
* @return Image the reduced image.
1345+
* @return void
12661346
*/
1267-
public function offsetUnset($offset): Image
1347+
public function offsetUnset($offset)
12681348
{
1269-
throw new \BadMethodCallException('Image::offsetUnset: not implemented');
1349+
if (is_int($offset) && $offset >= 0 && $offset < $this->bands) {
1350+
if ($this->bands == 1) {
1351+
throw new \BadMethodCallException('Image::offsetUnset: cannot delete final band');
1352+
}
1353+
1354+
$components = [];
1355+
if ($offset > 0) {
1356+
$components[] = $this->extract_band(0, ['n' => $offset]);
1357+
}
1358+
if ($offset < $this->bands - 1) {
1359+
$components[] = $this->extract_band(
1360+
$offset + 1,
1361+
['n' => $this->bands - 1 - $offset]
1362+
);
1363+
}
1364+
1365+
$head = array_shift($components);
1366+
if (empty($components)) {
1367+
$this->image = $head->image;
1368+
} else {
1369+
$this->image = $head->bandjoin($components)->image;
1370+
}
1371+
}
12701372
}
12711373

12721374
/**

tests/shortcuts.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,101 @@ public function testVipsIndex()
236236
$this->assertEquals($vips->bands, 1);
237237
}
238238

239+
public function testOffsetSet()
240+
{
241+
$base = Vips\Image::newFromArray([1, 2, 3]);
242+
$image = $base->bandjoin([$base->add(1), $base->add(2)]);
243+
244+
// replace band with image
245+
$test = $image->copy();
246+
$test[1] = $base;
247+
$this->assertEquals($test->bands, 3);
248+
$this->assertEquals($test[0]->avg(), 2);
249+
$this->assertEquals($test[1]->avg(), 2);
250+
$this->assertEquals($test[2]->avg(), 4);
251+
252+
// replace band with constant
253+
$test = $image->copy();
254+
$test[1] = 12;
255+
$this->assertEquals($test->bands, 3);
256+
$this->assertEquals($test[0]->avg(), 2);
257+
$this->assertEquals($test[1]->avg(), 12);
258+
$this->assertEquals($test[2]->avg(), 4);
259+
260+
// replace band with array
261+
$test = $image->copy();
262+
$test[1] = [12, 13];
263+
$this->assertEquals($test->bands, 4);
264+
$this->assertEquals($test[0]->avg(), 2);
265+
$this->assertEquals($test[1]->avg(), 12);
266+
$this->assertEquals($test[2]->avg(), 13);
267+
$this->assertEquals($test[3]->avg(), 4);
268+
269+
// insert at start
270+
$test = $image->copy();
271+
$test[-1] = 12;
272+
$this->assertEquals($test->bands, 4);
273+
$this->assertEquals($test[0]->avg(), 12);
274+
$this->assertEquals($test[1]->avg(), 2);
275+
$this->assertEquals($test[2]->avg(), 3);
276+
$this->assertEquals($test[3]->avg(), 4);
277+
278+
// append at end
279+
$test = $image->copy();
280+
$test[] = 12;
281+
$this->assertEquals($test->bands, 4);
282+
$this->assertEquals($test[0]->avg(), 2);
283+
$this->assertEquals($test[1]->avg(), 3);
284+
$this->assertEquals($test[2]->avg(), 4);
285+
$this->assertEquals($test[3]->avg(), 12);
286+
287+
}
288+
289+
public function testOffsetUnset()
290+
{
291+
$base = Vips\Image::newFromArray([1, 2, 3]);
292+
$image = $base->bandjoin([$base->add(1), $base->add(2)]);
293+
294+
// remove middle
295+
$test = $image->copy();
296+
unset($test[1]);
297+
$this->assertEquals($test->bands, 2);
298+
$this->assertEquals($test[0]->avg(), 2);
299+
$this->assertEquals($test[1]->avg(), 4);
300+
301+
// remove first
302+
$test = $image->copy();
303+
unset($test[0]);
304+
$this->assertEquals($test->bands, 2);
305+
$this->assertEquals($test[0]->avg(), 3);
306+
$this->assertEquals($test[1]->avg(), 4);
307+
308+
// remove last
309+
$test = $image->copy();
310+
unset($test[2]);
311+
$this->assertEquals($test->bands, 2);
312+
$this->assertEquals($test[0]->avg(), 2);
313+
$this->assertEquals($test[1]->avg(), 3);
314+
315+
// remove outside range
316+
$test = $image->copy();
317+
unset($test[12]);
318+
$this->assertEquals($test->bands, 3);
319+
$this->assertEquals($test[0]->avg(), 2);
320+
$this->assertEquals($test[1]->avg(), 3);
321+
$this->assertEquals($test[2]->avg(), 4);
322+
323+
}
324+
325+
public function testOffsetUnsetAll()
326+
{
327+
$base = Vips\Image::newFromArray([1, 2, 3]);
328+
329+
// remove all
330+
$test = $base->copy();
331+
$this->expectException(BadMethodCallException::class);
332+
unset($test[0]);
333+
}
239334
}
240335

241336
/*

0 commit comments

Comments
 (0)