Processing (3.5.4) で surface.setSize() してから saveするとArrayIndexOutOfBoundsException: Coordinate out of bounds! となる問題


saveがどこで確保されたバッファを使っているかが見当ついてないので，かなり大雑把な推測が含まれている．問題の切り分け用メモ

# 問題
エンターを押すたびにランダムな要素数の円を描画してPNGに保存するプログラムを作ろうとして以下のようにすると
`save()`の時点で`ArrayIndexOutOfBoundsException: Coordinate out of bounds!
`というエラーが出る．

プログラムでは`draw()`のたびに`surface.setSize()`でWindowサイズを変えているが，これによりおそらく`save`のため確保されたバッファを超える（あるいは異なる）データを保存しようとしているために起こっている．


```java
int serial_id = 0;
int nofRow = 1;
int nofCol = 1;
final int diameter = 50;
final int interval = diameter / 2;
final int margin = diameter / 2;


void setup() {
  nofRow = 1;
  nofCol = 1;

  surface.setResizable(true);
  surface.setSize(
    diameter * nofCol + interval * (nofCol - 1) + margin * 2, 
    diameter * nofRow + interval * (nofRow - 1) + margin * 2
    );

  noLoop();

  ellipseMode(CENTER);

  colorMode(RGB, 255);
  background(255, 255, 255);
  noStroke();
}


void draw() {
  nofRow = round(random(1, 10));
  nofCol = round(random(1, 10));

  println("row: " + nofRow + "\t col: " + nofCol);

  surface.setSize(
    diameter * nofCol + interval * (nofCol - 1) + margin * 2, 
    diameter * nofRow + interval * (nofRow - 1) + margin * 2
    );

  drawCircles();

  save("img_" + (serial_id++) + ".png");
}


void keyPressed() {
  if (key == ENTER) {
    redraw();
  } else  if (key == 'S') {
  } else if (key == 'E') {
    exit();
  }
}


void drawCircles() {
  for (int row = 0; row < nofRow; row++) {
    for (int col = 0; col < nofCol; col++) {
      int val = round(random(0, 255));
      //print(im + ",");
      fill(0, (int)(255 - val) * 3, (int)val);

      ellipse(
        margin + diameter / 2 + (diameter + interval) * (col % nofCol), 
        margin + diameter / 2 + (diameter + interval) * (row % nofRow), 
        diameter, diameter
        );
    }
  }
}
```
# 解決方法その1

`save()`を`draw()`の先頭に持ってくる．というか`surface.setSize()`の前にもってくる．これで前回の`draw()`の内容を保存する．最もシンプル．

```diff

void draw() {
  nofRow = round(random(1, 10));
  nofCol = round(random(1, 10));

  println("row: " + nofRow + "\t col: " + nofCol);
+
+ save("img_" + (serial_id++) + ".png");

  surface.setSize(
    diameter * nofCol + interval * (nofCol - 1) + margin * 2, 
    diameter * nofRow + interval * (nofRow - 1) + margin * 2
    );
    
  drawCircles();
-
- save("img_" + (serial_id++) + ".png");
}
```

# 解決方法その2
`S`を押したときに保存するようにする．`save()`用のバッファはおそらく`setup()`や`draw()`が終わった後に用意・決定されている？ただし
`redraw()`の後にすぐ，あるいは`delay()`をしてから`save()`してもエラー．(解決方法その3）
なので`ENTER`おしてから十分待って`S`を押せばそのころにはいい感じになってる．


```diff
void draw() {
  ...
  drawCircles();
-
- save("img_" + (serial_id++) + ".png");
}

void keyPressed() {
  if (key == ENTER) {
    redraw();
  } else  if (key == 'S') {
+   save("img_" + (serial_id++) + ".png");
  } else if (key == 'E') {
    exit();
  }
}
```

# 解決方法その3(WIP)
`draw()`のなかで`surface.setSize()`しない．たぶん`surface.setSize()`はそもそも`draw()`の中でマイフレーム呼ぶものじゃなく，イベント時に設定する．`redraw()`と`keyPressed()`は別スレッドだから？か`redraw()`の後にちょっと待つ必要あり．~~2sec待ってるのは画像確認用（のつもりだったけど，deleyでdrawもフリーズしちゃうしやっぱ別の問題？）~~
[redraw()](https://processing.org/reference/redraw_.html) は`draw()`が実行されるためのフラグを立てるだけらしい．一定周期（frameRate）で`draw()`（の親？）が呼ばれていてそこでフラグが立ってれば描画するという事なんだろうか．`Loop()`と`noLoop()`はそれらのフラグを常に立てるか否かを設定している？

まあ少なくとも`redraw()`してちゃんと`draw()`が実行し終わったという確証があったのちsaveをすればよいので，適当なフラグを作って待ってみたら~~うまくいった~~まだたまにエラー吐く……(WIP)．

```diff
+ boolean isDrawn = false;
void draw() {
- nofRow = round(random(1, 10));
- nofCol = round(random(1, 10));
-
- println("row: " + nofRow + "\t col: " + nofCol);
-
- surface.setSize(
-   diameter * nofCol + interval * (nofCol - 1) + margin * 2, 
-   diameter * nofRow + interval * (nofRow - 1) + margin * 2
-   );
    
   drawCircles();
+  isDrawn = true;
-
-  save("img_" + (serial_id++) + ".png");
}

void keyPressed() {
  if (key == ENTER) {
+   nofRow = round(random(1, 10));
+   nofCol = round(random(1, 10));
+
+   println("row: " + nofRow + "\t col: " + nofCol);
+   surface.setSize(
+     diameter * nofCol + interval * (nofCol - 1) + margin * 2, 
+     diameter * nofRow + interval * (nofRow - 1) + margin * 2
+     );
    redraw();
+   while(!isDrawn)delay(100);
+   isDrawn = false;
+   save("img_" + (serial_id++) + ".png");
  } else  if (key == 'S') {
  } else if (key == 'E') {
    exit();
  }
}
```