brendan-duncan / image

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

ImageData documentation #447

Open gmpassos opened 1 year ago

gmpassos commented 1 year ago

Please add a documentation to ImageData and how the channels in Image are handled. Looking the code it seems that the channels order is always red, green, blue and alpha.

Also I don't know a use case for an Uint32 channel if ImageData has multiple channels. I use in other projects an Uint32 to represent a 32bit pixels with 4 8bits values in it (like: r,g,b,a).

brendan-duncan commented 1 year ago

I did add some notes about that to https://github.com/brendan-duncan/image/blob/main/doc/image_data.md.

I have a personal TODO for improving dartdoc comments in the code, but now work has picked up again and I'll have to squeeze in time to do it...and that's the least fun part :-).

If the Image has Format.uint8 format, and 4 channels, then the data would be packed the same way it used to be, as an RGBA uint32. In that case you could use Image.buffer.asUint32List() to get the Uint32List of all the pixels, the way they used to be stored in the old version. I had to make the new version a lot more flexible than the old version to handle the different types of images.

In other words, to replicate an Image from the old version:

final img = Image(width: w, height: h, numChannels: 4); // the default format is uint8, the defualt # channels is 3. final u32Data = img.buffer.asUint32List(); // u32Data[0] is the first pixel as an RGBA packed uint32

gmpassos commented 1 year ago

Note that the correct way to call buffer.asUint32List is to pass the parameters offsetInBytes and length:

  var bytes = ... // could be an `Uint8List`, `Uint8ClampedList` or any other `TypedData`.
  var td = bytes as TypedData;
  return td.buffer.asUint32List(td.offsetInBytes, td.lengthInBytes ~/ 4);

See: https://github.com/gmpassos/pcanvas/blob/master/lib/src/pcanvas_base.dart#L902

brendan-duncan commented 1 year ago

You're right, I typed that without fully thinking it through. I can add as* helper methods to Image to reduce the boilerplate code.

brendan-duncan commented 1 year ago

Actually, I believe with the ByteBuffer.as* methods, offset and size are optional and will be full views of the data without them specified...but I'm not at the computer now to check. In any case, I think some helper methods to make sure might be helpful. I'll take a look at that tomorrow.

brendan-duncan commented 1 year ago

Just checked and they are optional and will be full view of the buffer without being specified, https://api.flutter.dev/flutter/dart-typed_data/ByteBuffer/asUint32List.html. It just requires the buffer lengthInBytes be divisable by the elementSizeInBytes (4 for Uint32List). So image.buffer.asUint32List() would be correct, unless you want to view a subset of the image data.

brendan-duncan commented 1 year ago

Nice PCanvas library, by the way.

gmpassos commented 1 year ago

The offset is optional, but if the buffer doesn't start at '0' the 'as...' method will return a "bad" view of the data.

I prefer to always pass the offset and length parameters and avoid issues. You only can skip the offset parameter if you can guarantee that the instance was allocated from position '0' in the underlying buffer, BTW.

brendan-duncan commented 1 year ago

True, but in this case buffer is always at 0 offset. Either way works.

ekuleshov commented 1 year ago

@brendan-duncan I'm also struggling with some 4.x changes regarding channels and transparency.

The 3.x API allowed the following, but now there is no such API anymore. Could you please advise how to handle this in the new API?

  final Image image = decodeImage(...);
  if (config.removeAlphaIOS) {
    image.channels = Channels.rgb; // how to remove transparency from the image in 4.x?
  }
  if (image.channels == Channels.rgba) { // how to check if image contains alpha channel?
     ...
  }   
brendan-duncan commented 1 year ago

Image.numChannels will be 4 if it has alpha, or 3 if it doesn't. I could add a Image.hasAlpha property to help clarify that.

The Image.convert method can add/remove an alpha channel by converting the image to a new image.

In your example,

var image = decodeImage(...);
if (config.removeAlphaIOS && image.numChannels == 4) { // How to check if the image contains alpha channel
  image = image.convert(numChannels: 3); // how to remove transparency from the image
}

Sorry that it's a bit more complicated now. Over the years, one of the biggest complaints was about how the library didn't support other formats, like 1-bit images or paletted images, because it used a rigid 32-bit image buffer. Now that it's much more flexible, it comes at a cost of a bit more complication, or at least learning curve. I'm hoping to iron out the complexity over time with helper functions as we find pain points of the new API.

brendan-duncan commented 1 year ago

I'll add a 3.x migration doc, too. So much to do.

gmpassos commented 1 year ago

IMHO: examples are the best way to show it. Code can be self explanatory.