概要
DartのImageライブラリを使えば、画像の切り抜きや合成、色味の編集、エンボス加工などさまざまな画像処理が行えます。 とはいえ用意されているメソッドは全てを網羅しているわけではなく、例えばクロマキーで透過画像を生成することなど、意外とできないことも多いです。
そのような用意されていないことをしたい場合にはバイト配列を取り出して自分で加工する必要があります。
公式のドキュメントには、画像から取り出した時のバイト配列の仕様など説明がなかったようなので、 本記事では、バイト配列を取り出して加工する方法について紹介します。
※ 具体的な画像の加工アルゴリズムについてはここでは紹介しません。
免責事項
実装中のコードから紹介できる形に加工しているだけなので、そのままコピペしても動かない可能性があります。 また、紹介しているプログラムや方法を流用した際のあらゆるトラブルについて責任を負いませんので、必ず記事やプログラムの内容を確認した上でご参考にしてください。
実行環境
name | version |
---|---|
Dart | 3.2.4 |
Flutter | 3.16.7 |
image | 4.1.4 |
手順
step0: Image
のインスタンスを生成する
適当な方法で画像を読み込んでデコードします。
import 'package:image/image.dart' as img; Future<img.Image?> readImage() async { final XFile? imageFile = await ImagePicker().pickImage( source: ImageSource.gallery, imageQuality: 100, ); final bytes = await imageFile?.readAsBytes(); if (bytes == null) { return null; } return img.decodeImage(bytes)?.convert(numChannels: 4); }
step1: バイト配列を取り出す
order
を渡すことで、バイト配列の並びを指定できます。
rgba
以外にもbgra
などもあるそうです。
いつ使うんだろう。
ChannelOrder enum - image library - Dart API
rgbaを指定した場合はalpha
を指定できます。
alphaチャンネルが必要なければ省略可能です。
Uint8List getBytes(img.Image image) { return image.getBytes(order: img.ChannelOrder.rgba, alpha: 255); }
step2: 加工する
得られたバイト配列はサイズが(width * height) * channelSize
のUint8List
になります。
配列の中にはヘッダなどは含まれず、各画素の色情報のみが含まれます。
各画素は、色情報が色チャンネル数分連続して並んでいて、左上の画素から右下の画素まで順番に並んでいます。
ChannelOrder.rgba
のUint8List
からある画素(x, y)
の色を取り出したい場合は、下記のような実装になります。
int getIndex({ required int x, required int y, required int width, required int height, }) => y * width * 4 + x * 4; img.ColorUint8 getColor({ required Uint8List bytes, required int index, }) { final int r = bytes[index]; final int g = bytes[index + 1]; final int b = bytes[index + 2]; final int a = bytes[index + 3]; return img.ColorUint8.rgba(r, g, b, a); }
上記メソッドを使って例えばネガポジ反転を実装するとこうなります。
Uint8List invertColor({ required Uint8List bytes, required int width, required int height, }) { Uint8List inverted = Uint8List(bytes.length); for (var x = 0; x < width; x++) { for (var y = 0; y < height; y++) { final int index = getIndex( x: x, y: y, width: width, height: height, ); final img.ColorUint8 color = getColor( bytes: bytes, index: index, ); inverted[index] = 255 - color.r.toInt(); inverted[index + 1] = 255 - color.g.toInt(); inverted[index + 2] = 255 - color.b.toInt(); inverted[index + 3] = color.a.toInt(); } } return inverted; }
step3: バイト配列からImageオブジェクトに変換する
最後に加工したbytesからImageに戻します。 注意すべき点はbytesをそのまま渡すのではなくByteBufferを取り出して渡さないといけない点です。
他は加工したbytesデータに合わせて設定すればOKです。
final img.Image invertedImage = img.Image.fromBytes( bytes: inverted.buffer, width: image.width, height: image.height, numChannels: 4, order: img.ChannelOrder.rgba, format: img.Format.uint8, );
結果
付録
ソースコードはこちら。 iOSで動作確認しています。 github.com