brendan-duncan / image

Dart Image Library for opening, manipulating, and saving various different image file formats.
MIT License
1.18k stars 266 forks source link

RangeError (index): Index out of range: index should be less than 3: 3 #673

Open stephane-archer opened 3 months ago

stephane-archer commented 3 months ago

Here is the error I observe, I might do something wrong:

RangeError (index): Index out of range: index should be less than 3: 3
dart:typed_data                                   Uint8List.[]
package:image/src/image/palette_uint8.dart 66:35  PaletteUint8.get
package:image/src/image/pixel_uint1.dart 166:18   PixelUint1._getChannel

The input is a simple png of 5x5 yellow pixels.

Here is the code:

Iterable<img.Image> getUniqTiles(img.Image image, int tileSize) {
  // Map to store unique tiles
  final Map<String, img.Image> uniqueTiles = {};

  // Split image into tiles
  for (int y = 0; y < image.height; y += tileSize) {
    for (int x = 0; x < image.width; x += tileSize) {
      if (y + tileSize > image.height || x + tileSize > image.width) {
        dev.log("skip too small tile");
        continue;
      }
      final img.Image tile = img.copyCrop(
        image,
        x: x,
        y: y,
        width: tileSize,
        height: tileSize,
        antialias: false,
      );

      // Convert tile to a unique key
      final tileKey = _tileToKey(tile);

      uniqueTiles[tileKey] = tile;
    }
  }
  return uniqueTiles.values;
}

String _tileToKey(img.Image tile) {
  if (!tile.isValid) {
    throw StateError("tile invalid");
  }
  if (tile.isEmpty) {
    throw StateError("tile empty");
  }
  final buffer = StringBuffer();
  for (int y = 0; y < tile.height; y++) {
    for (int x = 0; x < tile.width; x++) {
      final pixel = tile.getPixel(x, y);
      if (!pixel.isValid) {
        throw StateError("invalid pixel");
      }
      num red = pixel.r;
      num green = pixel.g;
      num blue = pixel.b;
      num alpha = pixel.a;
      buffer.write('$red,$green,$blue,$alpha;');
    }
  }
  return buffer.toString();
}

void main() {
  test('Counter value should be incremented', () async {
    img.Image? image = await img.decodePngFile('test/testData/5x5yellow.png');
    if (image == null) {
      throw StateError("bad input");
    }
    var tiles = getUniqTiles(image, 5);
    expect(tiles.length, 1);
  });
}

The error occurs on num red = pixel.r; (when I try to access the pixel value) in _tileToKey

my usage ofimg.copyCrop might be the issue.

stephane-archer commented 3 months ago

here is the image: New Project (11)

stephane-archer commented 3 months ago

if I do in the main:

   image =  image.convert(format: img.Format.uint8, numChannels: 3);

I don't get the Index out of range exception but:

if (!pixel.isValid) {
        throw StateError("invalid pixel");
}

some pixels are not valid (but their RGB values are correct) so doing the convert and removing the isValid check makes the code work but I still don't understand what is going on, and there is something wrong here with what I do or the library

stephane-archer commented 3 months ago

I think this is related to the fact that my image has a PaletteUint8 that make things go out of bounds on:

  num get(int index, int channel) =>
      channel < numChannels ? data[index * numChannels + channel] : 0;

called from:

  num _getChannel(int ci) => palette == null
      ? numChannels > ci
          ? _get(ci)
          : 0
      : palette!.get(_get(0), ci);

but I'm not able to understand this code and what it is doing:

  int _get(int ci) {
    var i = _index;
    var bi = 7 - (_bitIndex + ci);
    if (bi < 0) {
      bi += 8;
      i++;
    }
    if (i >= image.data.length) {
      return 0;
    }
    return (image.data[i] >> bi) & 0x1;
  }
stephane-archer commented 2 months ago

converting my input file not to use the palette removes the issue so there is something wrong with nt _get(int ci) and PNG palette support