//========================================================================================
//  SpritePatternViewer.java
//    en:Sprite pattern viewer
//    ja:スプライトパターンビュア
//  Copyright (C) 2003-2025 Makoto Kamada
//
//  This file is part of the XEiJ (X68000 Emulator in Java).
//  You can use, modify and redistribute the XEiJ if the conditions are met.
//  Read the XEiJ License for more details.
//  https://stdkmd.net/xeij/
//========================================================================================

package xeij;

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;  //TextLayout
import java.awt.geom.*;  //Rectangle2D
import java.awt.image.*;
import java.lang.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;  //ChangeListener

public class SpritePatternViewer {

  public static final boolean SPV_ON = true;

  //定数
  static final String SPV_RULER_NAME = "Dialog";  //目盛りのフォント
  static final int SPV_RULER_STYLE = Font.PLAIN;
  static final int SPV_RULER_SIZE = 18;
  static final Font SPV_RULER_FONT = new Font (SPV_RULER_NAME, SPV_RULER_STYLE, SPV_RULER_SIZE);
  static final int SPV_RULER_WIDTH = SPV_RULER_SIZE * 3;
  static final int SPV_RULER_HEIGHT = SPV_RULER_SIZE;
  static final int SPV_CELL_WIDTH = 36;  //セルの幅
  static final int SPV_CELL_HEIGHT = 36;  //セルの高さ
  static final int SPV_LATTICE_WIDTH = 2;  //格子の幅
  static final int SPV_LATTICE_HEIGHT = 2;  //格子の高さ
  static final int SPV_BACKGROUND_RGB = 0xff555555;  //背景色
  static final int SPV_BLOCK_DOT_RGB = 0xffffffff;  //パレットブロックの点の色
  static final int SPV_GRAY_LINE_RGB = 0xffaaaaaa;  //灰色の線の色
  static final int SPV_RULER_RGB = 0xffffffff;  //目盛りの色
  static final int SPV_LATTICE_RGB = 0xffaaaaaa;  //格子の色

  //イメージ
  static int spvImageWidth;  //イメージ幅
  static int spvImageHeight;  //イメージ高さ
  static BufferedImage spvBufferedImage;  //イメージ

  //ビットマップ
  static int[] spvBitmap;  //ビットマップ

  //キャンバス
  static ScrollCanvas spvCanvas;  //キャンバス

  //バンク
  //  0  0
  //    :
  //  15  15
  //  16  0-3
  //    :
  //  19  12-15
  //  20  0-15
  static int spvBankNumber;  //バンク
  static JComboBox<String> spvBankComboBox;  //バンクコンボボックス
  static int spvCellOffset;  //開始セル番号
  static int spvCellLength;  //セル数
  static int spvCols;  //セル列数。16,32,64
  static int spvRows;  //セル行数。16,32,64
  static int spvWidth;  //有効幅
  static int spvHeight;  //有効高さ

  //サイズ
  static final int[] spvLastSizeArray = new int[4096];  //前回のサイズ
  static final int[] spvSizeArray = new int[4096];  //サイズ。0=8x8,1=16x16
  static final int SPV_DEFAULT_SIZE = 1;  //デフォルトのサイズ
  static int spvSizeNumber;  //サイズ。-1=自動,0～1=固定
  static JComboBox<String> spvSizeComboBox;  //サイズコンボボックス

  //パレットブロック
  static final int[] spvLastBlockArray = new int[4 * 4096];  //前回のパレットブロック
  static final int[] spvBlockArray = new int[4 * 4096];  //パレットブロック
  static final int SPV_DEFAULT_BLOCK = 1;  //デフォルトのパレットブロック
  static int spvBlockNumber;  //パレットブロック。-1=自動,0～15=固定
  static JComboBox<String> spvBlockComboBox;  //パレットブロックコンボボックス

  //反転
  static final int[] spvLastFlipArray = new int[4 * 4096];  //前回の反転
  static final int[] spvFlipArray = new int[4 * 4096];  //反転。bit1=上下反転,bit0=左右反転
  static final int SPV_DEFAULT_FLIP = 0;  //デフォルトの反転
  static int spvFlipNumber;  //反転。-1=自動。0～3=固定
  static JComboBox<String> spvFlipComboBox;  //反転コンボボックス

  //スケールシフト
  static JComboBox<String> spvScaleComboBox;  //スケールシフトコンボボックス

  //テキストフィールド
  static JTextField spvTextField;
  static boolean spvTextLocked;

  //ウインドウ
  static JFrame spvFrame;

  //ポップアップメニュー
  static JPopupMenu spvPopupMenu;
  static int spvOffsetToCopy;
  static int spvLengthToCopy;

  //タイマー
  public static final int SPV_INTERVAL = 10;
  public static int spvTimer;

  //spvInit ()
  //  初期化
  public static void spvInit () {

    //イメージ
    spvImageWidth = spvColToX (64) - SPV_LATTICE_WIDTH;
    spvImageHeight = spvRowToY (64) - SPV_LATTICE_HEIGHT;

    //バンク
    spvSetBankNumber (0);

    //サイズ
    spvSizeNumber = -1;  //自動
    Arrays.fill (spvLastSizeArray, SPV_DEFAULT_SIZE);

    //パレットブロック
    spvBlockNumber = -1;  //自動
    Arrays.fill (spvLastBlockArray, SPV_DEFAULT_BLOCK);

    //反転
    spvFlipNumber = -1;  //自動
    Arrays.fill (spvLastFlipArray, SPV_DEFAULT_FLIP);

    //停止
    spvSetStoppedOff ();

    //ウインドウ
    spvFrame = null;

    //タイマー
    spvTimer = 0;

  }  //spvInit

  //spvSetBankNumber (number)
  //  バンク番号を設定する
  static void spvSetBankNumber (int number) {
    spvBankNumber = number;
    spvCellOffset = (spvBankNumber < 16 ? 256 * spvBankNumber :
                     spvBankNumber < 20 ? 1024 * (spvBankNumber - 16) :
                     0);
    spvCellLength = (spvBankNumber < 16 ? 256 :
                     spvBankNumber < 20 ? 1024 :
                     4096);
    spvCols =
      spvRows = (spvBankNumber < 16 ? 16 :
                 spvBankNumber < 20 ? 32 :
                 64);
    spvWidth = spvColToX (spvCols) - SPV_LATTICE_WIDTH;
    spvHeight = spvRowToY (spvRows) - SPV_LATTICE_HEIGHT;
    if (spvCanvas != null) {
      spvDrawRuler ();
      spvCanvas.setImage (spvWidth, spvHeight);
    }
  }  //spvSetBankNumber

  //x = spvColToX (col)
  //  セル桁からX座標を求める
  static int spvColToX (int col) {
    return SPV_RULER_WIDTH + (SPV_CELL_WIDTH * 8 + SPV_LATTICE_WIDTH) * (col >> 3) + SPV_CELL_WIDTH * (col & 7);
  }  //spvColToX

  //col = spvXToCol (x)
  //  X座標からセル桁を求める。-1=範囲外
  static int spvXToCol (int x) {
    x -= SPV_RULER_WIDTH;
    if (x < 0 || (SPV_CELL_WIDTH * 8 + SPV_LATTICE_WIDTH) * (spvCols >> 3) <= x) {
      return -1;
    }
    int q = x / (SPV_CELL_WIDTH * 8 + SPV_LATTICE_WIDTH);
    x -= (SPV_CELL_WIDTH * 8 + SPV_LATTICE_WIDTH) * q;
    if (SPV_CELL_WIDTH * 8 <= x) {
      return -1;
    }
    return 8 * q + x / SPV_CELL_WIDTH;
  }  //spvXToCol

  //y = spvRowToY (row)
  //  セル行からY座標を求める
  static int spvRowToY (int row) {
    return SPV_RULER_HEIGHT + (SPV_CELL_HEIGHT * 8 + SPV_LATTICE_HEIGHT) * (row >> 3) + SPV_CELL_HEIGHT * (row & 7);
  }  //spvRowToY

  //row = spvYToRow (y)
  //  Y座標からセル行を求める。-1=範囲外
  static int spvYToRow (int y) {
    y -= SPV_RULER_HEIGHT;
    if (y < 0 || (SPV_CELL_HEIGHT * 8 + SPV_LATTICE_HEIGHT) * (spvRows >> 3) <= y) {
      return -1;
    }
    int q = y / (SPV_CELL_HEIGHT * 8 + SPV_LATTICE_HEIGHT);
    y -= (SPV_CELL_HEIGHT * 8 + SPV_LATTICE_HEIGHT) * q;
    if (SPV_CELL_HEIGHT * 8 <= y) {
      return -1;
    }
    return 8 * q + y / SPV_CELL_HEIGHT;
  }  //spvYToRow

  //spvStart ()
  //  開始
  public static void spvStart () {
    if (RestorableFrame.rfmGetOpened (Settings.SGS_SPV_FRAME_KEY)) {
      spvOpen ();
    }
  }  //spvStart

  //spvOpen ()
  //  開く
  public static void spvOpen () {
    if (spvFrame == null) {
      spvMakeFrame ();
    } else {
      spvUpdateFrame ();
    }
    XEiJ.dbgVisibleMask |= XEiJ.DBG_SPV_VISIBLE_MASK;
    XEiJ.pnlExitFullScreen (false);
    spvFrame.setVisible (true);
  }  //spvOpen

  //spvMakeFrame ()
  //  作る
  //  ここでは開かない
  static void spvMakeFrame () {

    //イメージ
    spvBufferedImage = new BufferedImage (spvImageWidth, spvImageHeight, BufferedImage.TYPE_INT_ARGB);
    spvDrawRuler ();

    //ビットマップ
    spvBitmap = ((DataBufferInt) spvBufferedImage.getRaster ().getDataBuffer ()).getData ();

    //キャンバス
    spvCanvas = new ScrollCanvas (spvBufferedImage, spvWidth, spvHeight);
    spvCanvas.setMatColor (new Color (SPV_BACKGROUND_RGB));

    //アクションリスナー
    ActionListener listener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Object source = ae.getSource ();
        String command = ae.getActionCommand ();
        switch (command) {
        case "Bank":
          spvSetBankNumber (spvBankComboBox.getSelectedIndex ());
          if (XEiJ.mpuTask == null) {
            spvUpdateFrame ();
          }
          break;
        case "Size":
          spvSizeNumber = spvSizeComboBox.getSelectedIndex () - 1;
          if (XEiJ.mpuTask == null) {
            spvUpdateFrame ();
          }
          break;
        case "Block":
          spvBlockNumber = spvBlockComboBox.getSelectedIndex () - 1;
          if (XEiJ.mpuTask == null) {
            spvUpdateFrame ();
          }
          break;
        case "Flip":
          spvFlipNumber = spvFlipComboBox.getSelectedIndex () - 1;
          if (XEiJ.mpuTask == null) {
            spvUpdateFrame ();
          }
          break;
        case "Stop":
          spvStoppedRequest = (((JCheckBox) source).isSelected ());
          if (XEiJ.mpuTask == null) {
            spvUpdateFrame ();
          }
          break;
        case "Scale":
          spvCanvas.setScaleShift (spvScaleComboBox.getSelectedIndex () - 4);
          break;
        case "Copy as hexadecimal":
          spvCopyPattern ();
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }
    };

    //テキストフィールド
    spvTextField = new JTextField ();
    spvTextField.setEditable (false);
    spvTextField.setHorizontalAlignment (JTextField.CENTER);

    //ウインドウ
    spvFrame = Multilingual.mlnTitle (
      ComponentFactory.createRestorableSubFrame (
        Settings.SGS_SPV_FRAME_KEY,
        "Sprite Pattern Viewer",
        null,
        ComponentFactory.createBorderPanel (
          0, 0,
          //CENTER
          spvCanvas,
          //NORTH
          ComponentFactory.createVerticalBox (
            //1行目
            ComponentFactory.createHorizontalBox (
              Box.createHorizontalGlue (),
              //
              Multilingual.mlnText (
                ComponentFactory.createLabel ("Bank "),
                "ja", "バンク "),
              spvBankComboBox = ComponentFactory.createComboBox (
                spvBankNumber,
                "Bank",
                listener,
                "0", "1", "2", "3", "4", "5", "6", "7", "8",
                "9", "10", "11", "12", "13", "14", "15",
                "0-3", "4-7", "8-11", "12-15", "0-15"),
              //
              Multilingual.mlnText (
                ComponentFactory.createLabel (" Size "),
                "ja", " サイズ "),
              spvSizeComboBox = ComponentFactory.createComboBox (
                spvSizeNumber + 1,
                "Size",
                listener,
                4,
                Multilingual.mlnJapanese ? "自動" : "Auto",
                "8x8", "16x16"),
              //
              Multilingual.mlnText (
                ComponentFactory.createLabel (" Block "),
                "ja", " ブロック "),
              spvBlockComboBox = ComponentFactory.createComboBox (
                spvBlockNumber + 1,
                "Block",
                listener,
                4,
                Multilingual.mlnJapanese ? "自動" : "Auto",
                "0", "1", "2", "3", "4", "5", "6", "7", "8",
                "9", "10", "11", "12", "13", "14", "15"),
              //
              Multilingual.mlnText (
                ComponentFactory.createLabel (" Flip "),
                "ja", " 反転 "),
              spvFlipComboBox = ComponentFactory.createComboBox (
                spvFlipNumber + 1,
                "Flip",
                listener,
                4,
                Multilingual.mlnJapanese ? "自動" : "Auto",
                Multilingual.mlnJapanese ? "なし" : "-",
                Multilingual.mlnJapanese ? "左右" : "H",
                Multilingual.mlnJapanese ? "上下" : "V",
                Multilingual.mlnJapanese ? "上下左右" : "H&V"),
              //
              Box.createHorizontalGlue ()
              ),  //HorizontalBox
            //2行目
            ComponentFactory.createHorizontalBox (
              Box.createHorizontalStrut (10),
              //
              spvTextField,
              //
              Box.createHorizontalStrut (10),
              Multilingual.mlnText (
                ComponentFactory.createCheckBox (spvStoppedRequest, "Stop", listener),
                "ja", "停止"),
              //
              Box.createHorizontalStrut (10),
              //
              Multilingual.mlnText (
                ComponentFactory.createLabel (" Scale "),
                "ja", " 倍率 "),
              spvScaleComboBox = ComponentFactory.createComboBox (
                spvCanvas.getScaleShift () + 4,
                "Scale",
                listener,
                "1/8", "1/4", "1/2", "1", "2", "4", "8", "16", "32"),
              //
              Box.createHorizontalStrut (10)
              )  //HorizontalBox
            )  //VerticalBox
          )  //BorderPanel
        ),  //SubFrame
      "ja", "スプライトパターンビュア");

    //スケールシフトリスナー
    spvCanvas.addScaleShiftListener (new ScrollCanvas.ScaleShiftListener () {
      @Override public void scaleShiftChanged (int scaleShift) {
        spvScaleComboBox.setSelectedIndex (scaleShift + 4);
      }
    });

    //ウインドウリスナー
    ComponentFactory.addListener (
      spvFrame,
      new WindowAdapter () {
        @Override public void windowClosing (WindowEvent we) {
          XEiJ.dbgVisibleMask &= ~XEiJ.DBG_SPV_VISIBLE_MASK;
        }
      });

    //ポップアップメニュー
    spvPopupMenu = ComponentFactory.createPopupMenu (
      Multilingual.mlnText (
        ComponentFactory.createMenuItem ("Copy as hexadecimal", 'C', listener),
        "ja", "16進数でコピー")
      );

    //マウスリスナー
    MouseAdapter ma = new MouseAdapter () {
      @Override public void mouseClicked (MouseEvent me) {
        spvTextLocked = !spvTextLocked;  //テキストフィールドのロックを反転する
      }  //mouseClicked
      @Override public void mouseMoved (MouseEvent me) {
        if (!spvTextLocked) {  //テキストフィールドがロックされていないとき
          MouseEvent2D me2D = (MouseEvent2D) me;
          int x = (int) me2D.getX2D ();
          int y = (int) me2D.getY2D ();
          String s = spvGetPixel (x, y);
          spvTextField.setText (s == null ? "" : s);  //テキストフィールドに表示する
        }
      }  //mouseMoved
      @Override public void mousePressed (MouseEvent me) {
        spvShowPopup (me);
      }  //mousePressed
      @Override public void mouseReleased (MouseEvent me) {
        spvShowPopup (me);
      }  //mouseReleased
    };
    spvCanvas.addMouseListener (ma);
    spvCanvas.addMouseMotionListener (ma);

  }  //spvMakeFrame

  //spvDrawRuler ()
  //  目盛りと格子を描く
  static void spvDrawRuler () {
    Graphics2D g2 = spvBufferedImage.createGraphics ();
    //背景
    g2.setColor (new Color (SPV_BACKGROUND_RGB));
    g2.fillRect (0, 0, spvWidth, spvHeight);
    //格子
    g2.setColor (new Color (SPV_LATTICE_RGB));
    g2.fillRect (SPV_RULER_WIDTH,
                 SPV_RULER_HEIGHT,
                 spvWidth - SPV_RULER_WIDTH,
                 spvHeight - SPV_RULER_HEIGHT);
    //目盛り
    g2.setColor (new Color (SPV_RULER_RGB));
    g2.setFont (SPV_RULER_FONT);
    FontRenderContext frc = g2.getFontRenderContext ();
    for (int col = 0; col < spvCols; col++) {
      String s = String.format ("%d", col);
      TextLayout tl = new TextLayout (s, SPV_RULER_FONT, frc);
      Rectangle2D r = tl.getBounds ();
      int rx = (int) Math.round (r.getX ());
      int ry = (int) Math.round (r.getY ());
      int rw = (int) Math.round (r.getWidth ());
      int rh = (int) Math.round (r.getHeight ());
      int bx = spvColToX (col) + SPV_CELL_WIDTH / 2;  //下辺の中央を合わせる位置
      int by = spvRowToY (0) - SPV_RULER_SIZE / 12;
      g2.drawString (s, bx - rx - rw / 2, by - ry - rh);
    }  //for col
    for (int row = 0; row < spvRows; row++) {
      String s = String.format ("%d", spvCellOffset + spvCols * row);
      TextLayout tl = new TextLayout (s, SPV_RULER_FONT, frc);
      Rectangle2D r = tl.getBounds ();
      int rx = (int) Math.round (r.getX ());
      int ry = (int) Math.round (r.getY ());
      int rw = (int) Math.round (r.getWidth ());
      int rh = (int) Math.round (r.getHeight ());
      int bx = spvColToX (0) - SPV_RULER_SIZE / 6;  //右辺の中央を合わせる位置
      int by = spvRowToY (row) + SPV_CELL_HEIGHT / 2;
      g2.drawString (s, bx - rx - rw, by - ry - rh / 2);
    }  //for row
  }  //spvDrawRuler

  //spvShowPopup (me)
  //  ポップアップメニューを表示する
  static void spvShowPopup (MouseEvent me) {
    if (me.isPopupTrigger ()) {
      MouseEvent2D me2D = (MouseEvent2D) me;
      int x = (int) me2D.getX2D ();
      int y = (int) me2D.getY2D ();
      if (spvGetPatternToCopy (x, y)) {
        Point p = spvCanvas.getPopupPoint (me2D);
        spvPopupMenu.show (spvCanvas, p.x, p.y);
      }
    }
  }  //spvShowPopup

  //f = spvGetPatternToCopy (x, y)
  //  座標からspvPatのコピー範囲を求めてspvOffsetToCopyとspvLengthToCopyを設定する
  //  1セルの長さは32、1/4セルの長さは8
  static boolean spvGetPatternToCopy (int x, int y) {
    int cCol = spvXToCol (x);  //セル桁
    int cRow = spvYToRow (y);  //セル行
    if (0 <= cCol && 0 <= cRow) {  //セル範囲内
      x -= spvColToX (cCol);  //セル桁端数
      y -= spvRowToY (cRow);  //セル行端数
      int n = (spvCellOffset + cCol + spvCols * cRow) << 2;  //8x8パターン番号
      int size = spvSizeArray[n >> 2];  //サイズ
      if (size < 0) {
        size = spvLastSizeArray[n >> 2];
      }
      if (size == 0) {  //8x8
        int qCol = x / (SPV_CELL_WIDTH / 2);  //1/4セル桁
        int qRow = y / (SPV_CELL_HEIGHT / 2);  //1/4セル行
        x -= (SPV_CELL_WIDTH / 2) * qCol;  //1/4セル桁端数
        y -= (SPV_CELL_HEIGHT / 2) * qRow;  //1/4セル行端数
        n += qRow + 2 * qCol;  //8x8パターン番号
        spvOffsetToCopy = n << 3;
        spvLengthToCopy = 1 << 3;
        return true;
      } else {  //16x16
        spvOffsetToCopy = n << 3;
        spvLengthToCopy = 4 << 3;
        return true;
      }
    }  //if セル範囲内
    spvOffsetToCopy = 0;
    spvLengthToCopy = 0;
    return false;
  }  //spvGetPatternToCopy

  //spvCopyPattern ()
  //  設定された範囲のパターンを16進数でコピーする
  static void spvCopyPattern () {
    if (spvLengthToCopy != 0) {
      StringBuilder sb = new StringBuilder ();
      for (int i = 0; i < spvLengthToCopy; i++) {
        sb.append (String.format ("%08X\n", spvPat[spvOffsetToCopy + i]));
      }
      XEiJ.clpCopy (sb.toString ());
    }
  }  //spvCopyPattern

  //s = spvGetPixel (x, y)
  //  座標からピクセルの情報を求める
  static String spvGetPixel (int x, int y) {
    int cCol = spvXToCol (x);  //セル桁
    int cRow = spvYToRow (y);  //セル行
    if (0 <= cCol && 0 <= cRow) {  //セル範囲内
      x -= spvColToX (cCol);  //セル桁端数
      y -= spvRowToY (cRow);  //セル行端数
      int n = (spvCellOffset + cCol + spvCols * cRow) << 2;  //8x8パターン番号
      int size = spvSizeArray[n >> 2];  //サイズ
      if (size < 0) {
        size = spvLastSizeArray[n >> 2];
      }
      if (size == 0) {  //8x8
        int qCol = x / (SPV_CELL_WIDTH / 2);  //1/4セル桁
        int qRow = y / (SPV_CELL_HEIGHT / 2);  //1/4セル行
        x -= (SPV_CELL_WIDTH / 2) * qCol;  //1/4セル桁端数
        y -= (SPV_CELL_HEIGHT / 2) * qRow;  //1/4セル行端数
        n += qRow + 2 * qCol;  //8x8パターン番号
        x -= 1;
        y -= 1;
        if (0 <= x && x < 2 * 8 &&
            0 <= y && y < 2 * 8) {
          x >>= 1;  //8x8パターン内X座標
          y >>= 1;  //8x8パターン内Y座標
          int pb = spvBlockArray[n];  //パレットブロック
          if (pb < 0) {
            pb = spvLastBlockArray[n];
          }
          int vh = spvFlipArray[n];  //反転
          if (vh < 0) {
            vh = spvLastFlipArray[n];
          }
          int v = vh >> 1;
          int h = vh & 1;
          if (h != 0) {  //水平反転
            x ^= 7;
          }
          if (v != 0) {  //垂直反転
            y ^= 7;
          }
          int pc = (spvPat[(n << 3) + y] >> ((7 - x) << 2)) & 15;  //パレットコード
          int p = pb << 4 | pc;
          int c = spvPal16TS[p];
          return String.format ("8x8 n=%d x=%d y=%d p=0x%02X c=0x%04X(%d,%d,%d,%d)",
                                n, x, y, p, c,
                                c >> 11, (c >> 6) & 31, (c >> 1) & 31, c & 1);
        }
      } else {  //16x16
        x -= 2;
        y -= 2;
        if (0 <= x && x < 2 * 16 &&
            0 <= y && y < 2 * 16) {
          x >>= 1;  //16x16パターン内X座標
          y >>= 1;  //16x16パターン内Y座標
          int pb = spvBlockArray[n];  //パレットブロック
          if (pb < 0) {
            pb = spvLastBlockArray[n];
          }
          int vh = spvFlipArray[n];  //反転
          if (vh < 0) {
            vh = spvLastFlipArray[n];
          }
          int v = vh >> 1;
          int h = vh & 1;
          if ((vh & 1) != 0) {  //水平反転
            x ^= 15;
          }
          if ((vh & 2) != 0) {  //垂直反転
            y ^= 15;
          }
          int pc = (spvPat[(n << 3) + y + ((x & 8) << 1)] >> ((7 - (x & 7)) << 2)) & 15;  //パレットコード
          int p = pb << 4 | pc;
          int c = spvPal16TS[p];
          return String.format ("16x16 n=%d x=%d y=%d p=0x%02X c=0x%04X(%d,%d,%d,%d)",
                                n >> 2, x, y, p, c,
                                c >> 11, (c >> 6) & 31, (c >> 1) & 31, c & 1);
        }
      }  //if 8x8 else 16x16
    }  //if セル範囲内
    return null;
  }  //spvGetPixel

  //spvUpdateFrame ()
  //  更新する
  static void spvUpdateFrame () {
    if (spvFrame == null) {  //未初期化
      return;
    }

    //停止/動作
    if (spvStopped != spvStoppedRequest) {
      if (spvStoppedRequest) {  //動作→停止
        spvSetStoppedOn ();
      } else {  //停止→動作
        spvSetStoppedOff ();
      }
    }

    //パレットテーブルを作る
    for (int i = 0; i < 256; i++) {
      spvPal32TS[i] = spvPalTbl[spvPal16TS[i]];
    }

    //情報を集める
    Arrays.fill (spvSizeArray, spvSizeNumber);
    Arrays.fill (spvBlockArray, spvBlockNumber);
    Arrays.fill (spvFlipArray, spvFlipNumber);
    //自動の要素があるときだけ集める
    if (spvSizeNumber < 0 ||
        spvBlockNumber < 0 ||
        spvFlipNumber < 0) {  //自動の要素がある
      //スプライトから集める
      for (int sn = 0; sn < SpriteScreen.sprNumberOfSprites; sn++) {  //スプライト番号
        if ((256 <= sn && sn < 256 + 8) ||  //欠番
            spvPrw[sn] == 0) {  //表示されていない
          continue;
        }
        int n = spvNum[sn] << 2;  //8x8パターン番号
        if (spvSizeArray[n >> 2] < 0) {
          spvSizeArray[n >> 2] =
            spvLastSizeArray[n >> 2] = 1;  //16x16
        }
        int pb = spvCol[sn] >> 4;  //パレットブロック
        int vh = ((spvV[sn] ? 2 : 0) |
                  (spvH[sn] ? 1 : 0));  //反転
        for (int k = 0; k < 4; k++) {
          if (spvBlockArray[n + k] < 0) {
            spvBlockArray[n + k] =
              spvLastBlockArray[n + k] = pb;
          }
          if (spvFlipArray[n + k] < 0) {
            spvFlipArray[n + k] =
              spvLastFlipArray[n + k] = vh;
          }
        }
      }  //for sn
      //バックグラウンドから集める
      int tm = 0;  //表示されているテキストページのマスク
      if ((SpriteScreen.sprReg4BgCtrlPort & 1) != 0) {  //BG0が表示されている
        tm |= 1 << ((SpriteScreen.sprReg4BgCtrlPort >> 1) & 3);  //BG0に割り当てられているテキストページ
      }
      if (((SpriteScreen.sprReg8ResoPort & 3) == 0 ||  //256x256または
           SpriteScreen.spr512bg1) &&  //512x512でBG1を表示かつ
          (SpriteScreen.sprReg4BgCtrlPort & 8) != 0) {  //BG1が表示されている
        tm |= 1 << ((SpriteScreen.sprReg4BgCtrlPort >> 4) & 3);  //BG1に割り当てられているテキストページ
      }
      if ((SpriteScreen.sprReg8ResoPort & 3) == 0) {  //256x256
        for (int tp = 0; tp < 4; tp++) {  //テキストページ
          if ((tm & (1 << tp)) == 0) {  //表示されていない
            continue;
          }
          int o = 4096 * tp;  //テキスト配列インデックス開始位置
          for (int i = 0; i < 4096; i++) {  //テキスト配列インデックス
            int n = spvTNum[o + i] >> 3;  //8x8パターン番号
            if (spvSizeArray[n >> 2] < 0) {
              spvSizeArray[n >> 2] =
                spvLastSizeArray[n >> 2] = 0;  //8x8
            }
            int pb = spvTCol[o + i] >> 4;  //パレットブロック
            int vh = ((spvTV[o + i] ? 2 : 0) |
                      (spvTH[o + i] ? 1 : 0));  //反転
            if (spvBlockArray[n] < 0) {
              spvBlockArray[n] =
                spvLastBlockArray[n] = pb;
            }
            if (spvFlipArray[n] < 0) {
              spvFlipArray[n] =
                spvLastFlipArray[n] = vh;
            }
          }  //for i
        }  //for tp
      } else {  //512x512
        for (int tp = 0; tp < 4; tp++) {  //テキストページ
          if ((tm & (1 << tp)) == 0) {  //表示されていない
            continue;
          }
          int o = 4096 * tp;  //テキスト配列インデックス開始位置
          for (int i = 0; i < 4096; i++) {  //テキスト配列インデックス
            int n = spvTNum[o + i] >> 1;  //8x8パターン番号
            if (spvSizeArray[n >> 2] < 0) {
              spvSizeArray[n >> 2] =
                spvLastSizeArray[n >> 2] = 1;  //16x16
            }
            int pb = spvTCol[o + i] >> 4;  //パレットブロック
            int vh = ((spvTV[o + i] ? 2 : 0) |
                      (spvTH[o + i] ? 1 : 0));  //反転
            for (int k = 0; k < 4; k++) {
              if (spvBlockArray[n + k] < 0) {
                spvBlockArray[n + k] =
                  spvLastBlockArray[n + k] = pb;
              }
              if (spvFlipArray[n + k] < 0) {
                spvFlipArray[n + k] =
                  spvLastFlipArray[n + k] = vh;
              }
            }
          }  //for i
        }  //for tp
      }  //if 256x256 else 512x512
      //集められなかったものは前回と同じにする
      for (int n = 0; n < 4096; n++) {  //16x16パターン番号
        if (spvSizeArray[n] < 0) {
          spvSizeArray[n] = spvLastSizeArray[n];
        }
      }
      for (int n = 0; n < 4 * 4096; n++) {  //8x8パターン番号
        if (spvBlockArray[n] < 0) {
          spvBlockArray[n] = spvLastBlockArray[n];
        }
        if (spvFlipArray[n] < 0) {
          spvFlipArray[n] = spvLastFlipArray[n];
        }
      }
    }  //if 自動の要素がある

    //パターンを表示する
    int a = 32 * spvCellOffset;  //パターン配列インデックス
    for (int cRow = 0; cRow < spvRows; cRow++) {  //セル行
      int cY = spvRowToY (cRow);  //セル左上(0,0)Y座標
      for (int cCol = 0; cCol < spvCols; cCol++) {  //セル桁
        int cX = spvColToX (cCol);  //セル左上(0,0)X座標
        int cIndex = cX + spvImageWidth * cY;  //セル左上ビットマップインデックス
        //背景色で塗り潰す
        //  サイズが変わるときゴミが残らないようにする
        int i = cIndex;
        for (int v = 0; v < SPV_CELL_HEIGHT; v++) {
          for (int h = 0; h < SPV_CELL_WIDTH; h++) {
            spvBitmap[i + h] = SPV_BACKGROUND_RGB;
          }
          i += spvImageWidth;  //(+0,+1)
        }
        int n = (spvCellOffset + cCol + spvCols * cRow) << 2;  //8x8パターン番号
        if (spvSizeArray[n >> 2] == 0) {  //8x8
          for (int qCol = 0; qCol < 2; qCol++) {  //1/4セル桁
            int qX = cX + SPV_CELL_WIDTH / 2 * qCol;  //1/4セル左上X座標
            for (int qRow = 0; qRow < 2; qRow++) {  //1/4セル行
              int qY = cY + SPV_CELL_HEIGHT / 2 * qRow;  //1/4セル左上Y座標
              int qIndex = qX + spvImageWidth * qY;  //1/4セル左上ビットマップインデックス
              int pb = spvBlockArray[n];  //パレットブロック
              int vh = spvFlipArray[n];  //反転
              int hMask = (vh & 1) != 0 ? 7 : 0;  //左右反転マスク
              int vMask = (vh & 2) != 0 ? 15 : 0;  //上下反転マスク
              //灰色の線とパレットブロックを表す点
              i = qIndex + (hMask == 0 ? 0 + spvImageWidth * 1 :
                            1 + 2 * 8 + spvImageWidth * 1);  //左上ビットマップインデックス
              for (int v = 0; v <= 15; v++) {  //描画方向。上0→下15
                int vm = v ^ vMask;  //参照方向。上0→下15または下15→上0
                if (vm == pb) {  //パレットブロックの点
                  spvBitmap[i] = SPV_BLOCK_DOT_RGB;
                } else if (vm < 8) {  //灰色の線
                  spvBitmap[i] = SPV_GRAY_LINE_RGB;
                }
                i += spvImageWidth * 1;  //(+0,+1)
              }
              //パターン
              pb <<= 4;  //パレットブロック<<4
              vMask >>= 1;
              i = qIndex + 1 + spvImageWidth * 1;  //左上(1,1)ビットマップインデックス
              for (int v = 0; v <= 7; v++) {  //描画方向。上0→下7
                int vm = v ^ vMask;  //参照方向。上0→下7または下7→上0
                int d = spvPat[a + vm];
                for (int h = 7; 0 <= h; h--) {  //描画方向。左7→右0
                  int hm = h ^ hMask;  //参照方向。左7→右0または右0→左7
                  spvBitmap[i] =
                    spvBitmap[i + 1] =
                      spvBitmap[i + spvImageWidth] =
                        spvBitmap[i + spvImageWidth + 1] = spvPal32TS[pb + ((d >> (hm << 2)) & 15)];
                  i += 2;  //(+2,+0)
                }  //for h
                i += spvImageWidth * 2 - 2 * 8;  //(-2*8,+2)
              }  //for v
              a += 8;
              n++;
            }  //for qRow
          }  //for qCol
        } else {  //16x16
          int pb = spvBlockArray[n];  //パレットブロック
          int vh = spvFlipArray[n];  //反転
          int hMask = (vh & 1) != 0 ? 15 : 0;  //左右反転マスク
          int vMask = (vh & 2) != 0 ? 15 : 0;  //上下反転マスク
          //灰色の線とパレットブロックの点
          i = cIndex + (hMask == 0 ? 0 + spvImageWidth * 2 :
                        2 + 2 * 16 + spvImageWidth * 2);  //左上ビットマップインデックス
          for (int v = 0; v <= 15; v++) {  //描画方向。上0→下15
            int vm = v ^ vMask;  //参照方向。上0→下15または下15→上0
            if (vm == pb) {  //パレットブロックの点
              spvBitmap[i] =
                spvBitmap[i + 1] =
                  spvBitmap[i + spvImageWidth] =
                    spvBitmap[i + spvImageWidth + 1] = SPV_BLOCK_DOT_RGB;
            } else if (vm < 8) {  //灰色の線
              spvBitmap[i] =
                spvBitmap[i + 1] =
                  spvBitmap[i + spvImageWidth] =
                    spvBitmap[i + spvImageWidth + 1] = SPV_GRAY_LINE_RGB;
            }
            i += spvImageWidth * 2;  //(+0,+2)
          }
          //パターン
          pb <<= 4;  //パレットブロック<<4
          i = cIndex + 2 + spvImageWidth * 2;  //左上(2,2)ビットマップインデックス
          for (int v = 0; v <= 15; v++) {  //描画方向。上0→下15
            int vm = v ^ vMask;  //参照方向。上0→下15または下15→上0
            long d = ((long) spvPat[a + vm] << 32 |
                      (long) spvPat[a + vm + 16] & 0xffffffffL);
            for (int h = 15; 0 <= h; h--) {  //描画方向。左15→右0
              int hm = h ^ hMask;  //参照方向。左15→右0または右0→左15
              spvBitmap[i] =
                spvBitmap[i + 1] =
                  spvBitmap[i + spvImageWidth] =
                    spvBitmap[i + spvImageWidth + 1] = spvPal32TS[pb + ((int) (d >> (hm << 2)) & 15)];
              i += 2;  //(+2,+0)
            }  //for h
            i += spvImageWidth * 2 - 2 * 16;  //(-2*16,+2)
          }  //for v
          a += 32;
        }  //if 8x8 else 16x16
      }  //for cCol
    }  //for cRow
    spvCanvas.repaint ();

  }  //spvUpdateFrame

  //停止
  static boolean spvStopped;  //true=停止中
  static boolean spvStoppedRequest;

  static final short[] spvCopiedNum = new short[SpriteScreen.SPR_COUNT];
  static final short[] spvCopiedCol = new short[SpriteScreen.SPR_COUNT];
  static final byte[] spvCopiedPrw = new byte[SpriteScreen.SPR_COUNT];
  static final boolean[] spvCopiedH = new boolean[SpriteScreen.SPR_COUNT];
  static final boolean[] spvCopiedV = new boolean[SpriteScreen.SPR_COUNT];
  static final int[] spvCopiedPat = new int[32 * 4096];
  static final short[] spvCopiedTNum = new short[4096 * 4];
  static final short[] spvCopiedTCol = new short[4096 * 4];
  static final boolean[] spvCopiedTH = new boolean[4096 * 4];
  static final boolean[] spvCopiedTV = new boolean[4096 * 4];
  static final int[] spvCopiedPalTbl = new int[65536];
  static final int[] spvCopiedPal16TS = new int[256];

  static short[] spvNum;
  static short[] spvCol;
  static byte[] spvPrw;
  static boolean[] spvH;
  static boolean[] spvV;
  static int[] spvPat;
  static short[] spvTNum;
  static short[] spvTCol;
  static boolean[] spvTH;
  static boolean[] spvTV;
  static int[] spvPalTbl;
  static int[] spvPal16TS;

  static final int[] spvPal32TS = new int[256];

  static void spvSetStoppedOn () {
    spvStopped = true;
    spvStoppedRequest = true;
    System.arraycopy (SpriteScreen.sprNumPort, 0, spvCopiedNum, 0, SpriteScreen.SPR_COUNT);
    System.arraycopy (SpriteScreen.sprColPort, 0, spvCopiedCol, 0, SpriteScreen.SPR_COUNT);
    System.arraycopy (SpriteScreen.sprPrw, 0, spvCopiedPrw, 0, SpriteScreen.SPR_COUNT);
    System.arraycopy (SpriteScreen.sprH, 0, spvCopiedH, 0, SpriteScreen.SPR_COUNT);
    System.arraycopy (SpriteScreen.sprV, 0, spvCopiedV, 0, SpriteScreen.SPR_COUNT);
    System.arraycopy (SpriteScreen.sprPatPort, 0, spvCopiedPat, 0, 32 * 4096);
    System.arraycopy (SpriteScreen.sprTNum, 0, spvCopiedTNum, 0, 4096 * 4);
    System.arraycopy (SpriteScreen.sprTColPort, 0, spvCopiedTCol, 0, 4096 * 4);
    System.arraycopy (SpriteScreen.sprTH, 0, spvCopiedTH, 0, 4096 * 4);
    System.arraycopy (SpriteScreen.sprTV, 0, spvCopiedTV, 0, 4096 * 4);
    System.arraycopy (VideoController.vcnPalBase[VideoController.VCN_CONTRAST_SCALE * 15], 0, spvCopiedPalTbl, 0, 65536);
    System.arraycopy (VideoController.vcnPal16TS, 0, spvCopiedPal16TS, 0, 256);
    spvNum = spvCopiedNum;
    spvCol = spvCopiedCol;
    spvPrw = spvCopiedPrw;
    spvH = spvCopiedH;
    spvV = spvCopiedV;
    spvPat = spvCopiedPat;
    spvTNum = spvCopiedTNum;
    spvTCol = spvCopiedTCol;
    spvTH = spvCopiedTH;
    spvTV = spvCopiedTV;
    spvPalTbl = spvCopiedPalTbl;
    spvPal16TS = spvCopiedPal16TS;
  }  //spvSetStoppedOn

  static void spvSetStoppedOff () {
    spvStopped = false;
    spvStoppedRequest = false;
    spvNum = SpriteScreen.sprNumPort;
    spvCol = SpriteScreen.sprColPort;
    spvPrw = SpriteScreen.sprPrw;
    spvH = SpriteScreen.sprH;
    spvV = SpriteScreen.sprV;
    spvPat = SpriteScreen.sprPatPort;
    spvTNum = SpriteScreen.sprTNum;
    spvTCol = SpriteScreen.sprTColPort;
    spvTH = SpriteScreen.sprTH;
    spvTV = SpriteScreen.sprTV;
    spvPalTbl = VideoController.vcnPalBase[VideoController.VCN_CONTRAST_SCALE * 15];
    spvPal16TS = VideoController.vcnPal16TS;
  }  //spvSetStoppedOff

}  //class SpritePatternViewer
