imageflutteruint8list

In Flutter, how to manipulate pixel by pixel of an ui.Image without using the Image package?


in Flutter what's the correct way to change pixel data of an image with ui.Image type without using the Image package ? I was able to update the pixels by using the Image package but I don't want to convert the image multiple times. So I'm trying to explore the possibility to update Uint8List data. Below is the code snippet. However, I got "Exception: Invalid image data" when I tried to update the image with manipulated Uint8List. Wonder what I did wrong ? Appreciate any feedback.

int clamp(int x, int a, int b) {
    return (x < a)
        ? a
        : (x > b)
            ? b
            : x;
  }

int getColorY(int a, int r, int g, int b ) =>
      (clamp(a, 0, 255) << 24) |
      (clamp(r, 0, 255) << 16) |
      (clamp(g, 0, 255) << 8) |
      (clamp(b, 0, 255));

 Future<ui.Image> setImageData(ui.Image uiX) async 

    int w = uiX.width;
    int h = uiX.height;

    //get byteData

    final rgbaImageData =
           await uiX.toByteData(format: ui.ImageByteFormat.png);

    // convert to Uint32

    Uint32List words = Uint32List.view(
        rgbaImageData.buffer,
        rgbaImageData.offsetInBytes,
        rgbaImageData.lengthInBytes ~/ Uint32List.bytesPerElement);
   
    int a = 0;
    int r = 0;
    int g = 0;
    int b = 0;

    for (int idx = 0; idx < words.length; idx++) {
      Color color = Color(words[idx]);

      if (color.red > 128) {
         a = 0;
      } else {
        r = 128;
        g = 135;
        b = 110;
   
      }
      words[idx] = getColorY(a, r, g, b);
    }
    //convert Uint32List to Uint8List 

    Uint8List bytes = words.buffer.asUint8List();
   
    final Completer<ui.Image> imageCompleter = new Completer();
    ui.decodeImageFromList(bytes, (ui.Image img) {
      imageCompleter.complete(img);
    });
    return imageCompleter.future;
  }

Solution

  • You can convert ui.Image to raw byte data, manipulate it, then convert it back to ui.Image using ui.imageFromBytes.

    final ui.Image image = ...;
    
    // Convert to raw rgba
    final ByteData bytes = image.toByteData(format: 
      ImageByteFormat.rawRgba,
    );
    
    // Set the first pixel of the image to red.
    bytes.setUint32(0, 0xFF0000FF);
    
    // Set pixel at (x, y) to green.
    final x = 10;
    final y = 10;
    bytes.setUint32((y * image.width + x) * 4, 0x00FF00FF);
    
    ui.decodeImageFromPixels(
      bytes.buffer.asUint8List(),
      image.width,
      image.height,
      ui.PixelFormat.rgba8888,
      (ui.Image result) {
        // use your result image
      },
    );
    

    The wrong part in your code is:

    final rgbaImageData = await uiX.toByteData(format: ui.ImageByteFormat.png);
    

    As format: specifies the format of the returned bytes. So, you had to pass ui.ImageByteFormat.rawRgba.

    Also, Color(int) expects an ARGB format instead of RGBA.