【Flutter】img.luminanceThresholdでthresholdの値を変えても結果が変わらない

# この記事の内容
img.luminanceThresholdを使用しているときに、thresholdの値を変えても結果が変わらないという現象が起き、解決にかなり時間を費やしたので備忘録としてまとめる。

# 発生した現象
以下のような構成のプログラムで実行していた。
```dart
class Hogehoge extends StatefulWidget{
    final img.Image resizedImage;
    // ...省略...
}
class testState extends State<Hogehoge>{
    img.Image? _binarizedImage;
    double _threshold = 0.5;
    // ...中略...

    @Override 
    Widget build(BuildContext context){
    return Column(
        children:[
            _binarizedImage != null
                      ? Container(
                          Image.memory(img.encodePng(_binarizedImage!)
                      : Center(
                              child: Column(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                CircularProgressIndicator(),
                                Text("Processing..."),
            Slider(
                value: _threshold,
                divisions: 100,
                min: 0,
                max: 1,
                label: _threshold.toString(),
                onChanged: (value){
                    setState((){
                        _binarizedImage = img.luminanceThreshold(
                        widget.resizedImage,
                        _threshold);
                    }
                }
                
    }
}
```
上のような感じで、Sliderでthresholdの値を変更すると、それに合わせて2値化画像がプレビュー表示されるような画面構成である。しかし、Sliderを動かしても2値化画像に変化が生まれない。_thresholdの値が変わっていることは確認済みである。
原因もわからずやっていると、Sliderの値が1に達すると、画像が真っ黒になり、値が0に達すると画像が真っ白になることが判明した。さらに、一度0に達して真っ白な画像になった後は、Sliderの値を1にするまで常に真っ白のままになることが分かった。
もうわけわかめっ！って感じの状態であったが、なんとなく、img.luminanceThresholdが悪さをしていそうであるということだけはなんとなく感じた。

# 解決策
luminanceThresholdの引数のImage srcだが、参照渡しになっていることに加えて、luminanceThreshold内でsrc引数を変更するような処理があることで今回の事象が起こったのではないかと考えている。
以下にluminanceThresholdのプログラムをあげる。
```dart
Image luminanceThreshold(Image src,
    {num threshold = 0.5,
    bool outputColor = false,
    num amount = 1,
    Image? mask,
    Channel maskChannel = Channel.luminance}) {
  if (src.hasPalette) {
    src = src.convert(numChannels: src.numChannels);
  }
  for (final frame in src.frames) {
    for (final p in frame) {
      final y =
          0.3 * p.rNormalized + 0.59 * p.gNormalized + 0.11 * p.bNormalized;
      if (outputColor) {
        final l = max(0, y - threshold);
        final sl = sign(l);
        final msk = mask?.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
        final mx = (msk ?? 1) * amount;
        p
          ..r = mix(p.r, p.r * sl, mx)
          ..g = mix(p.g, p.g * sl, mx)
          ..b *= mix(p.b, p.b * sl, mx);
      } else {
        final y2 = y < threshold ? 0 : p.maxChannelValue;
        final msk = mask?.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
        final mx = (msk ?? 1) * amount;
        p
          ..r = mix(p.r, y2, mx)
          ..g = mix(p.g, y2, mx)
          ..b = mix(p.b, y2, mx);
      }
    }
  }
  return src;
}
```
関数内の処理だがsrcそのものを変更するようにみえる。
そこで、自分のプログラムでluminanceThreholdに与えるときに新規オブジェクトを生成して、変更が関数の外に波及しないようにした。
```dart
 _binarizedImage = img.luminanceThreshold(
                        widget.resizedImage.clone(),
                        _threshold);
```
すると、これまで説明した現象が解決された。
うーん、変更されないはずと思っていたwidget.resizedImageそのものが変更されていた場合、Sliderで何回も変更した場合に、おかしな現象が起きるのは確かにその通りである。1回走った後は、(255,255,255)と(0,0,0)しか存在しないため,2回目以降は中途半端な閾値では変化が起きないということである。
ただ、自分の場合はfinalがついているので変更されるはずがないっ！！って思いこんでいたせいで、かなり解決に時間がかかりました。よくよく考えると、ミュータブルなオブジェクトの場合、中身は変わっている可能性があるという視点が抜けてましたね。
Flutterはまだまだなれないですね...