SpritePatternViewer.java
     1: //========================================================================================
     2: //  SpritePatternViewer.java
     3: //    en:Sprite pattern viewer
     4: //    ja:スプライトパターンビュア
     5: //  Copyright (C) 2003-2025 Makoto Kamada
     6: //
     7: //  This file is part of the XEiJ (X68000 Emulator in Java).
     8: //  You can use, modify and redistribute the XEiJ if the conditions are met.
     9: //  Read the XEiJ License for more details.
    10: //  https://stdkmd.net/xeij/
    11: //========================================================================================
    12: 
    13: package xeij;
    14: 
    15: import java.awt.*;
    16: import java.awt.event.*;
    17: import java.awt.font.*;  //TextLayout
    18: import java.awt.geom.*;  //Rectangle2D
    19: import java.awt.image.*;
    20: import java.lang.*;
    21: import java.util.*;
    22: import javax.swing.*;
    23: import javax.swing.event.*;  //ChangeListener
    24: 
    25: public class SpritePatternViewer {
    26: 
    27:   public static final boolean SPV_ON = true;
    28: 
    29:   //定数
    30:   static final String SPV_RULER_NAME = "Dialog";  //目盛りのフォント
    31:   static final int SPV_RULER_STYLE = Font.PLAIN;
    32:   static final int SPV_RULER_SIZE = 18;
    33:   static final Font SPV_RULER_FONT = new Font (SPV_RULER_NAME, SPV_RULER_STYLE, SPV_RULER_SIZE);
    34:   static final int SPV_RULER_WIDTH = SPV_RULER_SIZE * 3;
    35:   static final int SPV_RULER_HEIGHT = SPV_RULER_SIZE;
    36:   static final int SPV_CELL_WIDTH = 36;  //セルの幅
    37:   static final int SPV_CELL_HEIGHT = 36;  //セルの高さ
    38:   static final int SPV_LATTICE_WIDTH = 2;  //格子の幅
    39:   static final int SPV_LATTICE_HEIGHT = 2;  //格子の高さ
    40:   static final int SPV_BACKGROUND_RGB = 0xff555555;  //背景色
    41:   static final int SPV_BLOCK_DOT_RGB = 0xffffffff;  //パレットブロックの点の色
    42:   static final int SPV_GRAY_LINE_RGB = 0xffaaaaaa;  //灰色の線の色
    43:   static final int SPV_RULER_RGB = 0xffffffff;  //目盛りの色
    44:   static final int SPV_LATTICE_RGB = 0xffaaaaaa;  //格子の色
    45: 
    46:   //イメージ
    47:   static int spvImageWidth;  //イメージ幅
    48:   static int spvImageHeight;  //イメージ高さ
    49:   static BufferedImage spvBufferedImage;  //イメージ
    50: 
    51:   //ビットマップ
    52:   static int[] spvBitmap;  //ビットマップ
    53: 
    54:   //キャンバス
    55:   static ScrollCanvas spvCanvas;  //キャンバス
    56: 
    57:   //バンク
    58:   //  0  0
    59:   //    :
    60:   //  15  15
    61:   //  16  0-3
    62:   //    :
    63:   //  19  12-15
    64:   //  20  0-15
    65:   static int spvBankNumber;  //バンク
    66:   static JComboBox<String> spvBankComboBox;  //バンクコンボボックス
    67:   static int spvCellOffset;  //開始セル番号
    68:   static int spvCellLength;  //セル数
    69:   static int spvCols;  //セル列数。16,32,64
    70:   static int spvRows;  //セル行数。16,32,64
    71:   static int spvWidth;  //有効幅
    72:   static int spvHeight;  //有効高さ
    73: 
    74:   //サイズ
    75:   static final int[] spvLastSizeArray = new int[4096];  //前回のサイズ
    76:   static final int[] spvSizeArray = new int[4096];  //サイズ。0=8x8,1=16x16
    77:   static final int SPV_DEFAULT_SIZE = 1;  //デフォルトのサイズ
    78:   static int spvSizeNumber;  //サイズ。-1=自動,0~1=固定
    79:   static JComboBox<String> spvSizeComboBox;  //サイズコンボボックス
    80: 
    81:   //パレットブロック
    82:   static final int[] spvLastBlockArray = new int[4 * 4096];  //前回のパレットブロック
    83:   static final int[] spvBlockArray = new int[4 * 4096];  //パレットブロック
    84:   static final int SPV_DEFAULT_BLOCK = 1;  //デフォルトのパレットブロック
    85:   static int spvBlockNumber;  //パレットブロック。-1=自動,0~15=固定
    86:   static JComboBox<String> spvBlockComboBox;  //パレットブロックコンボボックス
    87: 
    88:   //反転
    89:   static final int[] spvLastFlipArray = new int[4 * 4096];  //前回の反転
    90:   static final int[] spvFlipArray = new int[4 * 4096];  //反転。bit1=上下反転,bit0=左右反転
    91:   static final int SPV_DEFAULT_FLIP = 0;  //デフォルトの反転
    92:   static int spvFlipNumber;  //反転。-1=自動。0~3=固定
    93:   static JComboBox<String> spvFlipComboBox;  //反転コンボボックス
    94: 
    95:   //スケールシフト
    96:   static JComboBox<String> spvScaleComboBox;  //スケールシフトコンボボックス
    97: 
    98:   //テキストフィールド
    99:   static JTextField spvTextField;
   100:   static boolean spvTextLocked;
   101: 
   102:   //ウインドウ
   103:   static JFrame spvFrame;
   104: 
   105:   //ポップアップメニュー
   106:   static JPopupMenu spvPopupMenu;
   107:   static int spvOffsetToCopy;
   108:   static int spvLengthToCopy;
   109: 
   110:   //タイマー
   111:   public static final int SPV_INTERVAL = 10;
   112:   public static int spvTimer;
   113: 
   114:   //spvInit ()
   115:   //  初期化
   116:   public static void spvInit () {
   117: 
   118:     //イメージ
   119:     spvImageWidth = spvColToX (64) - SPV_LATTICE_WIDTH;
   120:     spvImageHeight = spvRowToY (64) - SPV_LATTICE_HEIGHT;
   121: 
   122:     //バンク
   123:     spvSetBankNumber (0);
   124: 
   125:     //サイズ
   126:     spvSizeNumber = -1;  //自動
   127:     Arrays.fill (spvLastSizeArray, SPV_DEFAULT_SIZE);
   128: 
   129:     //パレットブロック
   130:     spvBlockNumber = -1;  //自動
   131:     Arrays.fill (spvLastBlockArray, SPV_DEFAULT_BLOCK);
   132: 
   133:     //反転
   134:     spvFlipNumber = -1;  //自動
   135:     Arrays.fill (spvLastFlipArray, SPV_DEFAULT_FLIP);
   136: 
   137:     //停止
   138:     spvSetStoppedOff ();
   139: 
   140:     //ウインドウ
   141:     spvFrame = null;
   142: 
   143:     //タイマー
   144:     spvTimer = 0;
   145: 
   146:   }  //spvInit
   147: 
   148:   //spvSetBankNumber (number)
   149:   //  バンク番号を設定する
   150:   static void spvSetBankNumber (int number) {
   151:     spvBankNumber = number;
   152:     spvCellOffset = (spvBankNumber < 16 ? 256 * spvBankNumber :
   153:                      spvBankNumber < 20 ? 1024 * (spvBankNumber - 16) :
   154:                      0);
   155:     spvCellLength = (spvBankNumber < 16 ? 256 :
   156:                      spvBankNumber < 20 ? 1024 :
   157:                      4096);
   158:     spvCols =
   159:       spvRows = (spvBankNumber < 16 ? 16 :
   160:                  spvBankNumber < 20 ? 32 :
   161:                  64);
   162:     spvWidth = spvColToX (spvCols) - SPV_LATTICE_WIDTH;
   163:     spvHeight = spvRowToY (spvRows) - SPV_LATTICE_HEIGHT;
   164:     if (spvCanvas != null) {
   165:       spvDrawRuler ();
   166:       spvCanvas.setImage (spvWidth, spvHeight);
   167:     }
   168:   }  //spvSetBankNumber
   169: 
   170:   //x = spvColToX (col)
   171:   //  セル桁からX座標を求める
   172:   static int spvColToX (int col) {
   173:     return SPV_RULER_WIDTH + (SPV_CELL_WIDTH * 8 + SPV_LATTICE_WIDTH) * (col >> 3) + SPV_CELL_WIDTH * (col & 7);
   174:   }  //spvColToX
   175: 
   176:   //col = spvXToCol (x)
   177:   //  X座標からセル桁を求める。-1=範囲外
   178:   static int spvXToCol (int x) {
   179:     x -= SPV_RULER_WIDTH;
   180:     if (x < 0 || (SPV_CELL_WIDTH * 8 + SPV_LATTICE_WIDTH) * (spvCols >> 3) <= x) {
   181:       return -1;
   182:     }
   183:     int q = x / (SPV_CELL_WIDTH * 8 + SPV_LATTICE_WIDTH);
   184:     x -= (SPV_CELL_WIDTH * 8 + SPV_LATTICE_WIDTH) * q;
   185:     if (SPV_CELL_WIDTH * 8 <= x) {
   186:       return -1;
   187:     }
   188:     return 8 * q + x / SPV_CELL_WIDTH;
   189:   }  //spvXToCol
   190: 
   191:   //y = spvRowToY (row)
   192:   //  セル行からY座標を求める
   193:   static int spvRowToY (int row) {
   194:     return SPV_RULER_HEIGHT + (SPV_CELL_HEIGHT * 8 + SPV_LATTICE_HEIGHT) * (row >> 3) + SPV_CELL_HEIGHT * (row & 7);
   195:   }  //spvRowToY
   196: 
   197:   //row = spvYToRow (y)
   198:   //  Y座標からセル行を求める。-1=範囲外
   199:   static int spvYToRow (int y) {
   200:     y -= SPV_RULER_HEIGHT;
   201:     if (y < 0 || (SPV_CELL_HEIGHT * 8 + SPV_LATTICE_HEIGHT) * (spvRows >> 3) <= y) {
   202:       return -1;
   203:     }
   204:     int q = y / (SPV_CELL_HEIGHT * 8 + SPV_LATTICE_HEIGHT);
   205:     y -= (SPV_CELL_HEIGHT * 8 + SPV_LATTICE_HEIGHT) * q;
   206:     if (SPV_CELL_HEIGHT * 8 <= y) {
   207:       return -1;
   208:     }
   209:     return 8 * q + y / SPV_CELL_HEIGHT;
   210:   }  //spvYToRow
   211: 
   212:   //spvStart ()
   213:   //  開始
   214:   public static void spvStart () {
   215:     if (RestorableFrame.rfmGetOpened (Settings.SGS_SPV_FRAME_KEY)) {
   216:       spvOpen ();
   217:     }
   218:   }  //spvStart
   219: 
   220:   //spvOpen ()
   221:   //  開く
   222:   public static void spvOpen () {
   223:     if (spvFrame == null) {
   224:       spvMakeFrame ();
   225:     } else {
   226:       spvUpdateFrame ();
   227:     }
   228:     XEiJ.dbgVisibleMask |= XEiJ.DBG_SPV_VISIBLE_MASK;
   229:     XEiJ.pnlExitFullScreen (false);
   230:     spvFrame.setVisible (true);
   231:   }  //spvOpen
   232: 
   233:   //spvMakeFrame ()
   234:   //  作る
   235:   //  ここでは開かない
   236:   static void spvMakeFrame () {
   237: 
   238:     //イメージ
   239:     spvBufferedImage = new BufferedImage (spvImageWidth, spvImageHeight, BufferedImage.TYPE_INT_ARGB);
   240:     spvDrawRuler ();
   241: 
   242:     //ビットマップ
   243:     spvBitmap = ((DataBufferInt) spvBufferedImage.getRaster ().getDataBuffer ()).getData ();
   244: 
   245:     //キャンバス
   246:     spvCanvas = new ScrollCanvas (spvBufferedImage, spvWidth, spvHeight);
   247:     spvCanvas.setMatColor (new Color (SPV_BACKGROUND_RGB));
   248: 
   249:     //アクションリスナー
   250:     ActionListener listener = new ActionListener () {
   251:       @Override public void actionPerformed (ActionEvent ae) {
   252:         Object source = ae.getSource ();
   253:         String command = ae.getActionCommand ();
   254:         switch (command) {
   255:         case "Bank":
   256:           spvSetBankNumber (spvBankComboBox.getSelectedIndex ());
   257:           if (XEiJ.mpuTask == null) {
   258:             spvUpdateFrame ();
   259:           }
   260:           break;
   261:         case "Size":
   262:           spvSizeNumber = spvSizeComboBox.getSelectedIndex () - 1;
   263:           if (XEiJ.mpuTask == null) {
   264:             spvUpdateFrame ();
   265:           }
   266:           break;
   267:         case "Block":
   268:           spvBlockNumber = spvBlockComboBox.getSelectedIndex () - 1;
   269:           if (XEiJ.mpuTask == null) {
   270:             spvUpdateFrame ();
   271:           }
   272:           break;
   273:         case "Flip":
   274:           spvFlipNumber = spvFlipComboBox.getSelectedIndex () - 1;
   275:           if (XEiJ.mpuTask == null) {
   276:             spvUpdateFrame ();
   277:           }
   278:           break;
   279:         case "Stop":
   280:           spvStoppedRequest = (((JCheckBox) source).isSelected ());
   281:           if (XEiJ.mpuTask == null) {
   282:             spvUpdateFrame ();
   283:           }
   284:           break;
   285:         case "Scale":
   286:           spvCanvas.setScaleShift (spvScaleComboBox.getSelectedIndex () - 4);
   287:           break;
   288:         case "Copy as hexadecimal":
   289:           spvCopyPattern ();
   290:           break;
   291:         default:
   292:           System.out.println ("unknown action command " + command);
   293:         }
   294:       }
   295:     };
   296: 
   297:     //テキストフィールド
   298:     spvTextField = new JTextField ();
   299:     spvTextField.setEditable (false);
   300:     spvTextField.setHorizontalAlignment (JTextField.CENTER);
   301: 
   302:     //ウインドウ
   303:     spvFrame = Multilingual.mlnTitle (
   304:       ComponentFactory.createRestorableSubFrame (
   305:         Settings.SGS_SPV_FRAME_KEY,
   306:         "Sprite Pattern Viewer",
   307:         null,
   308:         ComponentFactory.createBorderPanel (
   309:           0, 0,
   310:           //CENTER
   311:           spvCanvas,
   312:           //NORTH
   313:           ComponentFactory.createVerticalBox (
   314:             //1行目
   315:             ComponentFactory.createHorizontalBox (
   316:               Box.createHorizontalGlue (),
   317:               //
   318:               Multilingual.mlnText (
   319:                 ComponentFactory.createLabel ("Bank "),
   320:                 "ja", "バンク "),
   321:               spvBankComboBox = ComponentFactory.createComboBox (
   322:                 spvBankNumber,
   323:                 "Bank",
   324:                 listener,
   325:                 "0", "1", "2", "3", "4", "5", "6", "7", "8",
   326:                 "9", "10", "11", "12", "13", "14", "15",
   327:                 "0-3", "4-7", "8-11", "12-15", "0-15"),
   328:               //
   329:               Multilingual.mlnText (
   330:                 ComponentFactory.createLabel (" Size "),
   331:                 "ja", " サイズ "),
   332:               spvSizeComboBox = ComponentFactory.createComboBox (
   333:                 spvSizeNumber + 1,
   334:                 "Size",
   335:                 listener,
   336:                 4,
   337:                 Multilingual.mlnJapanese ? "自動" : "Auto",
   338:                 "8x8", "16x16"),
   339:               //
   340:               Multilingual.mlnText (
   341:                 ComponentFactory.createLabel (" Block "),
   342:                 "ja", " ブロック "),
   343:               spvBlockComboBox = ComponentFactory.createComboBox (
   344:                 spvBlockNumber + 1,
   345:                 "Block",
   346:                 listener,
   347:                 4,
   348:                 Multilingual.mlnJapanese ? "自動" : "Auto",
   349:                 "0", "1", "2", "3", "4", "5", "6", "7", "8",
   350:                 "9", "10", "11", "12", "13", "14", "15"),
   351:               //
   352:               Multilingual.mlnText (
   353:                 ComponentFactory.createLabel (" Flip "),
   354:                 "ja", " 反転 "),
   355:               spvFlipComboBox = ComponentFactory.createComboBox (
   356:                 spvFlipNumber + 1,
   357:                 "Flip",
   358:                 listener,
   359:                 4,
   360:                 Multilingual.mlnJapanese ? "自動" : "Auto",
   361:                 Multilingual.mlnJapanese ? "なし" : "-",
   362:                 Multilingual.mlnJapanese ? "左右" : "H",
   363:                 Multilingual.mlnJapanese ? "上下" : "V",
   364:                 Multilingual.mlnJapanese ? "上下左右" : "H&V"),
   365:               //
   366:               Box.createHorizontalGlue ()
   367:               ),  //HorizontalBox
   368:             //2行目
   369:             ComponentFactory.createHorizontalBox (
   370:               Box.createHorizontalStrut (10),
   371:               //
   372:               spvTextField,
   373:               //
   374:               Box.createHorizontalStrut (10),
   375:               Multilingual.mlnText (
   376:                 ComponentFactory.createCheckBox (spvStoppedRequest, "Stop", listener),
   377:                 "ja", "停止"),
   378:               //
   379:               Box.createHorizontalStrut (10),
   380:               //
   381:               Multilingual.mlnText (
   382:                 ComponentFactory.createLabel (" Scale "),
   383:                 "ja", " 倍率 "),
   384:               spvScaleComboBox = ComponentFactory.createComboBox (
   385:                 spvCanvas.getScaleShift () + 4,
   386:                 "Scale",
   387:                 listener,
   388:                 "1/8", "1/4", "1/2", "1", "2", "4", "8", "16", "32"),
   389:               //
   390:               Box.createHorizontalStrut (10)
   391:               )  //HorizontalBox
   392:             )  //VerticalBox
   393:           )  //BorderPanel
   394:         ),  //SubFrame
   395:       "ja", "スプライトパターンビュア");
   396: 
   397:     //スケールシフトリスナー
   398:     spvCanvas.addScaleShiftListener (new ScrollCanvas.ScaleShiftListener () {
   399:       @Override public void scaleShiftChanged (int scaleShift) {
   400:         spvScaleComboBox.setSelectedIndex (scaleShift + 4);
   401:       }
   402:     });
   403: 
   404:     //ウインドウリスナー
   405:     ComponentFactory.addListener (
   406:       spvFrame,
   407:       new WindowAdapter () {
   408:         @Override public void windowClosing (WindowEvent we) {
   409:           XEiJ.dbgVisibleMask &= ~XEiJ.DBG_SPV_VISIBLE_MASK;
   410:         }
   411:       });
   412: 
   413:     //ポップアップメニュー
   414:     spvPopupMenu = ComponentFactory.createPopupMenu (
   415:       Multilingual.mlnText (
   416:         ComponentFactory.createMenuItem ("Copy as hexadecimal", 'C', listener),
   417:         "ja", "16進数でコピー")
   418:       );
   419: 
   420:     //マウスリスナー
   421:     MouseAdapter ma = new MouseAdapter () {
   422:       @Override public void mouseClicked (MouseEvent me) {
   423:         spvTextLocked = !spvTextLocked;  //テキストフィールドのロックを反転する
   424:       }  //mouseClicked
   425:       @Override public void mouseMoved (MouseEvent me) {
   426:         if (!spvTextLocked) {  //テキストフィールドがロックされていないとき
   427:           MouseEvent2D me2D = (MouseEvent2D) me;
   428:           int x = (int) me2D.getX2D ();
   429:           int y = (int) me2D.getY2D ();
   430:           String s = spvGetPixel (x, y);
   431:           spvTextField.setText (s == null ? "" : s);  //テキストフィールドに表示する
   432:         }
   433:       }  //mouseMoved
   434:       @Override public void mousePressed (MouseEvent me) {
   435:         spvShowPopup (me);
   436:       }  //mousePressed
   437:       @Override public void mouseReleased (MouseEvent me) {
   438:         spvShowPopup (me);
   439:       }  //mouseReleased
   440:     };
   441:     spvCanvas.addMouseListener (ma);
   442:     spvCanvas.addMouseMotionListener (ma);
   443: 
   444:   }  //spvMakeFrame
   445: 
   446:   //spvDrawRuler ()
   447:   //  目盛りと格子を描く
   448:   static void spvDrawRuler () {
   449:     Graphics2D g2 = spvBufferedImage.createGraphics ();
   450:     //背景
   451:     g2.setColor (new Color (SPV_BACKGROUND_RGB));
   452:     g2.fillRect (0, 0, spvWidth, spvHeight);
   453:     //格子
   454:     g2.setColor (new Color (SPV_LATTICE_RGB));
   455:     g2.fillRect (SPV_RULER_WIDTH,
   456:                  SPV_RULER_HEIGHT,
   457:                  spvWidth - SPV_RULER_WIDTH,
   458:                  spvHeight - SPV_RULER_HEIGHT);
   459:     //目盛り
   460:     g2.setColor (new Color (SPV_RULER_RGB));
   461:     g2.setFont (SPV_RULER_FONT);
   462:     FontRenderContext frc = g2.getFontRenderContext ();
   463:     for (int col = 0; col < spvCols; col++) {
   464:       String s = String.format ("%d", col);
   465:       TextLayout tl = new TextLayout (s, SPV_RULER_FONT, frc);
   466:       Rectangle2D r = tl.getBounds ();
   467:       int rx = (int) Math.round (r.getX ());
   468:       int ry = (int) Math.round (r.getY ());
   469:       int rw = (int) Math.round (r.getWidth ());
   470:       int rh = (int) Math.round (r.getHeight ());
   471:       int bx = spvColToX (col) + SPV_CELL_WIDTH / 2;  //下辺の中央を合わせる位置
   472:       int by = spvRowToY (0) - SPV_RULER_SIZE / 12;
   473:       g2.drawString (s, bx - rx - rw / 2, by - ry - rh);
   474:     }  //for col
   475:     for (int row = 0; row < spvRows; row++) {
   476:       String s = String.format ("%d", spvCellOffset + spvCols * row);
   477:       TextLayout tl = new TextLayout (s, SPV_RULER_FONT, frc);
   478:       Rectangle2D r = tl.getBounds ();
   479:       int rx = (int) Math.round (r.getX ());
   480:       int ry = (int) Math.round (r.getY ());
   481:       int rw = (int) Math.round (r.getWidth ());
   482:       int rh = (int) Math.round (r.getHeight ());
   483:       int bx = spvColToX (0) - SPV_RULER_SIZE / 6;  //右辺の中央を合わせる位置
   484:       int by = spvRowToY (row) + SPV_CELL_HEIGHT / 2;
   485:       g2.drawString (s, bx - rx - rw, by - ry - rh / 2);
   486:     }  //for row
   487:   }  //spvDrawRuler
   488: 
   489:   //spvShowPopup (me)
   490:   //  ポップアップメニューを表示する
   491:   static void spvShowPopup (MouseEvent me) {
   492:     if (me.isPopupTrigger ()) {
   493:       MouseEvent2D me2D = (MouseEvent2D) me;
   494:       int x = (int) me2D.getX2D ();
   495:       int y = (int) me2D.getY2D ();
   496:       if (spvGetPatternToCopy (x, y)) {
   497:         Point p = spvCanvas.getPopupPoint (me2D);
   498:         spvPopupMenu.show (spvCanvas, p.x, p.y);
   499:       }
   500:     }
   501:   }  //spvShowPopup
   502: 
   503:   //f = spvGetPatternToCopy (x, y)
   504:   //  座標からspvPatのコピー範囲を求めてspvOffsetToCopyとspvLengthToCopyを設定する
   505:   //  1セルの長さは32、1/4セルの長さは8
   506:   static boolean spvGetPatternToCopy (int x, int y) {
   507:     int cCol = spvXToCol (x);  //セル桁
   508:     int cRow = spvYToRow (y);  //セル行
   509:     if (0 <= cCol && 0 <= cRow) {  //セル範囲内
   510:       x -= spvColToX (cCol);  //セル桁端数
   511:       y -= spvRowToY (cRow);  //セル行端数
   512:       int n = (spvCellOffset + cCol + spvCols * cRow) << 2;  //8x8パターン番号
   513:       int size = spvSizeArray[n >> 2];  //サイズ
   514:       if (size < 0) {
   515:         size = spvLastSizeArray[n >> 2];
   516:       }
   517:       if (size == 0) {  //8x8
   518:         int qCol = x / (SPV_CELL_WIDTH / 2);  //1/4セル桁
   519:         int qRow = y / (SPV_CELL_HEIGHT / 2);  //1/4セル行
   520:         x -= (SPV_CELL_WIDTH / 2) * qCol;  //1/4セル桁端数
   521:         y -= (SPV_CELL_HEIGHT / 2) * qRow;  //1/4セル行端数
   522:         n += qRow + 2 * qCol;  //8x8パターン番号
   523:         spvOffsetToCopy = n << 3;
   524:         spvLengthToCopy = 1 << 3;
   525:         return true;
   526:       } else {  //16x16
   527:         spvOffsetToCopy = n << 3;
   528:         spvLengthToCopy = 4 << 3;
   529:         return true;
   530:       }
   531:     }  //if セル範囲内
   532:     spvOffsetToCopy = 0;
   533:     spvLengthToCopy = 0;
   534:     return false;
   535:   }  //spvGetPatternToCopy
   536: 
   537:   //spvCopyPattern ()
   538:   //  設定された範囲のパターンを16進数でコピーする
   539:   static void spvCopyPattern () {
   540:     if (spvLengthToCopy != 0) {
   541:       StringBuilder sb = new StringBuilder ();
   542:       for (int i = 0; i < spvLengthToCopy; i++) {
   543:         sb.append (String.format ("%08X\n", spvPat[spvOffsetToCopy + i]));
   544:       }
   545:       XEiJ.clpCopy (sb.toString ());
   546:     }
   547:   }  //spvCopyPattern
   548: 
   549:   //s = spvGetPixel (x, y)
   550:   //  座標からピクセルの情報を求める
   551:   static String spvGetPixel (int x, int y) {
   552:     int cCol = spvXToCol (x);  //セル桁
   553:     int cRow = spvYToRow (y);  //セル行
   554:     if (0 <= cCol && 0 <= cRow) {  //セル範囲内
   555:       x -= spvColToX (cCol);  //セル桁端数
   556:       y -= spvRowToY (cRow);  //セル行端数
   557:       int n = (spvCellOffset + cCol + spvCols * cRow) << 2;  //8x8パターン番号
   558:       int size = spvSizeArray[n >> 2];  //サイズ
   559:       if (size < 0) {
   560:         size = spvLastSizeArray[n >> 2];
   561:       }
   562:       if (size == 0) {  //8x8
   563:         int qCol = x / (SPV_CELL_WIDTH / 2);  //1/4セル桁
   564:         int qRow = y / (SPV_CELL_HEIGHT / 2);  //1/4セル行
   565:         x -= (SPV_CELL_WIDTH / 2) * qCol;  //1/4セル桁端数
   566:         y -= (SPV_CELL_HEIGHT / 2) * qRow;  //1/4セル行端数
   567:         n += qRow + 2 * qCol;  //8x8パターン番号
   568:         x -= 1;
   569:         y -= 1;
   570:         if (0 <= x && x < 2 * 8 &&
   571:             0 <= y && y < 2 * 8) {
   572:           x >>= 1;  //8x8パターン内X座標
   573:           y >>= 1;  //8x8パターン内Y座標
   574:           int pb = spvBlockArray[n];  //パレットブロック
   575:           if (pb < 0) {
   576:             pb = spvLastBlockArray[n];
   577:           }
   578:           int vh = spvFlipArray[n];  //反転
   579:           if (vh < 0) {
   580:             vh = spvLastFlipArray[n];
   581:           }
   582:           int v = vh >> 1;
   583:           int h = vh & 1;
   584:           if (h != 0) {  //水平反転
   585:             x ^= 7;
   586:           }
   587:           if (v != 0) {  //垂直反転
   588:             y ^= 7;
   589:           }
   590:           int pc = (spvPat[(n << 3) + y] >> ((7 - x) << 2)) & 15;  //パレットコード
   591:           int p = pb << 4 | pc;
   592:           int c = spvPal16TS[p];
   593:           return String.format ("8x8 n=%d x=%d y=%d p=0x%02X c=0x%04X(%d,%d,%d,%d)",
   594:                                 n, x, y, p, c,
   595:                                 c >> 11, (c >> 6) & 31, (c >> 1) & 31, c & 1);
   596:         }
   597:       } else {  //16x16
   598:         x -= 2;
   599:         y -= 2;
   600:         if (0 <= x && x < 2 * 16 &&
   601:             0 <= y && y < 2 * 16) {
   602:           x >>= 1;  //16x16パターン内X座標
   603:           y >>= 1;  //16x16パターン内Y座標
   604:           int pb = spvBlockArray[n];  //パレットブロック
   605:           if (pb < 0) {
   606:             pb = spvLastBlockArray[n];
   607:           }
   608:           int vh = spvFlipArray[n];  //反転
   609:           if (vh < 0) {
   610:             vh = spvLastFlipArray[n];
   611:           }
   612:           int v = vh >> 1;
   613:           int h = vh & 1;
   614:           if ((vh & 1) != 0) {  //水平反転
   615:             x ^= 15;
   616:           }
   617:           if ((vh & 2) != 0) {  //垂直反転
   618:             y ^= 15;
   619:           }
   620:           int pc = (spvPat[(n << 3) + y + ((x & 8) << 1)] >> ((7 - (x & 7)) << 2)) & 15;  //パレットコード
   621:           int p = pb << 4 | pc;
   622:           int c = spvPal16TS[p];
   623:           return String.format ("16x16 n=%d x=%d y=%d p=0x%02X c=0x%04X(%d,%d,%d,%d)",
   624:                                 n >> 2, x, y, p, c,
   625:                                 c >> 11, (c >> 6) & 31, (c >> 1) & 31, c & 1);
   626:         }
   627:       }  //if 8x8 else 16x16
   628:     }  //if セル範囲内
   629:     return null;
   630:   }  //spvGetPixel
   631: 
   632:   //spvUpdateFrame ()
   633:   //  更新する
   634:   static void spvUpdateFrame () {
   635:     if (spvFrame == null) {  //未初期化
   636:       return;
   637:     }
   638: 
   639:     //停止/動作
   640:     if (spvStopped != spvStoppedRequest) {
   641:       if (spvStoppedRequest) {  //動作→停止
   642:         spvSetStoppedOn ();
   643:       } else {  //停止→動作
   644:         spvSetStoppedOff ();
   645:       }
   646:     }
   647: 
   648:     //パレットテーブルを作る
   649:     for (int i = 0; i < 256; i++) {
   650:       spvPal32TS[i] = spvPalTbl[spvPal16TS[i]];
   651:     }
   652: 
   653:     //情報を集める
   654:     Arrays.fill (spvSizeArray, spvSizeNumber);
   655:     Arrays.fill (spvBlockArray, spvBlockNumber);
   656:     Arrays.fill (spvFlipArray, spvFlipNumber);
   657:     //自動の要素があるときだけ集める
   658:     if (spvSizeNumber < 0 ||
   659:         spvBlockNumber < 0 ||
   660:         spvFlipNumber < 0) {  //自動の要素がある
   661:       //スプライトから集める
   662:       for (int sn = 0; sn < SpriteScreen.sprNumberOfSprites; sn++) {  //スプライト番号
   663:         if ((256 <= sn && sn < 256 + 8) ||  //欠番
   664:             spvPrw[sn] == 0) {  //表示されていない
   665:           continue;
   666:         }
   667:         int n = spvNum[sn] << 2;  //8x8パターン番号
   668:         if (spvSizeArray[n >> 2] < 0) {
   669:           spvSizeArray[n >> 2] =
   670:             spvLastSizeArray[n >> 2] = 1;  //16x16
   671:         }
   672:         int pb = spvCol[sn] >> 4;  //パレットブロック
   673:         int vh = ((spvV[sn] ? 2 : 0) |
   674:                   (spvH[sn] ? 1 : 0));  //反転
   675:         for (int k = 0; k < 4; k++) {
   676:           if (spvBlockArray[n + k] < 0) {
   677:             spvBlockArray[n + k] =
   678:               spvLastBlockArray[n + k] = pb;
   679:           }
   680:           if (spvFlipArray[n + k] < 0) {
   681:             spvFlipArray[n + k] =
   682:               spvLastFlipArray[n + k] = vh;
   683:           }
   684:         }
   685:       }  //for sn
   686:       //バックグラウンドから集める
   687:       int tm = 0;  //表示されているテキストページのマスク
   688:       if ((SpriteScreen.sprReg4BgCtrlPort & 1) != 0) {  //BG0が表示されている
   689:         tm |= 1 << ((SpriteScreen.sprReg4BgCtrlPort >> 1) & 3);  //BG0に割り当てられているテキストページ
   690:       }
   691:       if (((SpriteScreen.sprReg8ResoPort & 3) == 0 ||  //256x256または
   692:            SpriteScreen.spr512bg1) &&  //512x512でBG1を表示かつ
   693:           (SpriteScreen.sprReg4BgCtrlPort & 8) != 0) {  //BG1が表示されている
   694:         tm |= 1 << ((SpriteScreen.sprReg4BgCtrlPort >> 4) & 3);  //BG1に割り当てられているテキストページ
   695:       }
   696:       if ((SpriteScreen.sprReg8ResoPort & 3) == 0) {  //256x256
   697:         for (int tp = 0; tp < 4; tp++) {  //テキストページ
   698:           if ((tm & (1 << tp)) == 0) {  //表示されていない
   699:             continue;
   700:           }
   701:           int o = 4096 * tp;  //テキスト配列インデックス開始位置
   702:           for (int i = 0; i < 4096; i++) {  //テキスト配列インデックス
   703:             int n = spvTNum[o + i] >> 3;  //8x8パターン番号
   704:             if (spvSizeArray[n >> 2] < 0) {
   705:               spvSizeArray[n >> 2] =
   706:                 spvLastSizeArray[n >> 2] = 0;  //8x8
   707:             }
   708:             int pb = spvTCol[o + i] >> 4;  //パレットブロック
   709:             int vh = ((spvTV[o + i] ? 2 : 0) |
   710:                       (spvTH[o + i] ? 1 : 0));  //反転
   711:             if (spvBlockArray[n] < 0) {
   712:               spvBlockArray[n] =
   713:                 spvLastBlockArray[n] = pb;
   714:             }
   715:             if (spvFlipArray[n] < 0) {
   716:               spvFlipArray[n] =
   717:                 spvLastFlipArray[n] = vh;
   718:             }
   719:           }  //for i
   720:         }  //for tp
   721:       } else {  //512x512
   722:         for (int tp = 0; tp < 4; tp++) {  //テキストページ
   723:           if ((tm & (1 << tp)) == 0) {  //表示されていない
   724:             continue;
   725:           }
   726:           int o = 4096 * tp;  //テキスト配列インデックス開始位置
   727:           for (int i = 0; i < 4096; i++) {  //テキスト配列インデックス
   728:             int n = spvTNum[o + i] >> 1;  //8x8パターン番号
   729:             if (spvSizeArray[n >> 2] < 0) {
   730:               spvSizeArray[n >> 2] =
   731:                 spvLastSizeArray[n >> 2] = 1;  //16x16
   732:             }
   733:             int pb = spvTCol[o + i] >> 4;  //パレットブロック
   734:             int vh = ((spvTV[o + i] ? 2 : 0) |
   735:                       (spvTH[o + i] ? 1 : 0));  //反転
   736:             for (int k = 0; k < 4; k++) {
   737:               if (spvBlockArray[n + k] < 0) {
   738:                 spvBlockArray[n + k] =
   739:                   spvLastBlockArray[n + k] = pb;
   740:               }
   741:               if (spvFlipArray[n + k] < 0) {
   742:                 spvFlipArray[n + k] =
   743:                   spvLastFlipArray[n + k] = vh;
   744:               }
   745:             }
   746:           }  //for i
   747:         }  //for tp
   748:       }  //if 256x256 else 512x512
   749:       //集められなかったものは前回と同じにする
   750:       for (int n = 0; n < 4096; n++) {  //16x16パターン番号
   751:         if (spvSizeArray[n] < 0) {
   752:           spvSizeArray[n] = spvLastSizeArray[n];
   753:         }
   754:       }
   755:       for (int n = 0; n < 4 * 4096; n++) {  //8x8パターン番号
   756:         if (spvBlockArray[n] < 0) {
   757:           spvBlockArray[n] = spvLastBlockArray[n];
   758:         }
   759:         if (spvFlipArray[n] < 0) {
   760:           spvFlipArray[n] = spvLastFlipArray[n];
   761:         }
   762:       }
   763:     }  //if 自動の要素がある
   764: 
   765:     //パターンを表示する
   766:     int a = 32 * spvCellOffset;  //パターン配列インデックス
   767:     for (int cRow = 0; cRow < spvRows; cRow++) {  //セル行
   768:       int cY = spvRowToY (cRow);  //セル左上(0,0)Y座標
   769:       for (int cCol = 0; cCol < spvCols; cCol++) {  //セル桁
   770:         int cX = spvColToX (cCol);  //セル左上(0,0)X座標
   771:         int cIndex = cX + spvImageWidth * cY;  //セル左上ビットマップインデックス
   772:         //背景色で塗り潰す
   773:         //  サイズが変わるときゴミが残らないようにする
   774:         int i = cIndex;
   775:         for (int v = 0; v < SPV_CELL_HEIGHT; v++) {
   776:           for (int h = 0; h < SPV_CELL_WIDTH; h++) {
   777:             spvBitmap[i + h] = SPV_BACKGROUND_RGB;
   778:           }
   779:           i += spvImageWidth;  //(+0,+1)
   780:         }
   781:         int n = (spvCellOffset + cCol + spvCols * cRow) << 2;  //8x8パターン番号
   782:         if (spvSizeArray[n >> 2] == 0) {  //8x8
   783:           for (int qCol = 0; qCol < 2; qCol++) {  //1/4セル桁
   784:             int qX = cX + SPV_CELL_WIDTH / 2 * qCol;  //1/4セル左上X座標
   785:             for (int qRow = 0; qRow < 2; qRow++) {  //1/4セル行
   786:               int qY = cY + SPV_CELL_HEIGHT / 2 * qRow;  //1/4セル左上Y座標
   787:               int qIndex = qX + spvImageWidth * qY;  //1/4セル左上ビットマップインデックス
   788:               int pb = spvBlockArray[n];  //パレットブロック
   789:               int vh = spvFlipArray[n];  //反転
   790:               int hMask = (vh & 1) != 0 ? 7 : 0;  //左右反転マスク
   791:               int vMask = (vh & 2) != 0 ? 15 : 0;  //上下反転マスク
   792:               //灰色の線とパレットブロックを表す点
   793:               i = qIndex + (hMask == 0 ? 0 + spvImageWidth * 1 :
   794:                             1 + 2 * 8 + spvImageWidth * 1);  //左上ビットマップインデックス
   795:               for (int v = 0; v <= 15; v++) {  //描画方向。上0→下15
   796:                 int vm = v ^ vMask;  //参照方向。上0→下15または下15→上0
   797:                 if (vm == pb) {  //パレットブロックの点
   798:                   spvBitmap[i] = SPV_BLOCK_DOT_RGB;
   799:                 } else if (vm < 8) {  //灰色の線
   800:                   spvBitmap[i] = SPV_GRAY_LINE_RGB;
   801:                 }
   802:                 i += spvImageWidth * 1;  //(+0,+1)
   803:               }
   804:               //パターン
   805:               pb <<= 4;  //パレットブロック<<4
   806:               vMask >>= 1;
   807:               i = qIndex + 1 + spvImageWidth * 1;  //左上(1,1)ビットマップインデックス
   808:               for (int v = 0; v <= 7; v++) {  //描画方向。上0→下7
   809:                 int vm = v ^ vMask;  //参照方向。上0→下7または下7→上0
   810:                 int d = spvPat[a + vm];
   811:                 for (int h = 7; 0 <= h; h--) {  //描画方向。左7→右0
   812:                   int hm = h ^ hMask;  //参照方向。左7→右0または右0→左7
   813:                   spvBitmap[i] =
   814:                     spvBitmap[i + 1] =
   815:                       spvBitmap[i + spvImageWidth] =
   816:                         spvBitmap[i + spvImageWidth + 1] = spvPal32TS[pb + ((d >> (hm << 2)) & 15)];
   817:                   i += 2;  //(+2,+0)
   818:                 }  //for h
   819:                 i += spvImageWidth * 2 - 2 * 8;  //(-2*8,+2)
   820:               }  //for v
   821:               a += 8;
   822:               n++;
   823:             }  //for qRow
   824:           }  //for qCol
   825:         } else {  //16x16
   826:           int pb = spvBlockArray[n];  //パレットブロック
   827:           int vh = spvFlipArray[n];  //反転
   828:           int hMask = (vh & 1) != 0 ? 15 : 0;  //左右反転マスク
   829:           int vMask = (vh & 2) != 0 ? 15 : 0;  //上下反転マスク
   830:           //灰色の線とパレットブロックの点
   831:           i = cIndex + (hMask == 0 ? 0 + spvImageWidth * 2 :
   832:                         2 + 2 * 16 + spvImageWidth * 2);  //左上ビットマップインデックス
   833:           for (int v = 0; v <= 15; v++) {  //描画方向。上0→下15
   834:             int vm = v ^ vMask;  //参照方向。上0→下15または下15→上0
   835:             if (vm == pb) {  //パレットブロックの点
   836:               spvBitmap[i] =
   837:                 spvBitmap[i + 1] =
   838:                   spvBitmap[i + spvImageWidth] =
   839:                     spvBitmap[i + spvImageWidth + 1] = SPV_BLOCK_DOT_RGB;
   840:             } else if (vm < 8) {  //灰色の線
   841:               spvBitmap[i] =
   842:                 spvBitmap[i + 1] =
   843:                   spvBitmap[i + spvImageWidth] =
   844:                     spvBitmap[i + spvImageWidth + 1] = SPV_GRAY_LINE_RGB;
   845:             }
   846:             i += spvImageWidth * 2;  //(+0,+2)
   847:           }
   848:           //パターン
   849:           pb <<= 4;  //パレットブロック<<4
   850:           i = cIndex + 2 + spvImageWidth * 2;  //左上(2,2)ビットマップインデックス
   851:           for (int v = 0; v <= 15; v++) {  //描画方向。上0→下15
   852:             int vm = v ^ vMask;  //参照方向。上0→下15または下15→上0
   853:             long d = ((long) spvPat[a + vm] << 32 |
   854:                       (long) spvPat[a + vm + 16] & 0xffffffffL);
   855:             for (int h = 15; 0 <= h; h--) {  //描画方向。左15→右0
   856:               int hm = h ^ hMask;  //参照方向。左15→右0または右0→左15
   857:               spvBitmap[i] =
   858:                 spvBitmap[i + 1] =
   859:                   spvBitmap[i + spvImageWidth] =
   860:                     spvBitmap[i + spvImageWidth + 1] = spvPal32TS[pb + ((int) (d >> (hm << 2)) & 15)];
   861:               i += 2;  //(+2,+0)
   862:             }  //for h
   863:             i += spvImageWidth * 2 - 2 * 16;  //(-2*16,+2)
   864:           }  //for v
   865:           a += 32;
   866:         }  //if 8x8 else 16x16
   867:       }  //for cCol
   868:     }  //for cRow
   869:     spvCanvas.repaint ();
   870: 
   871:   }  //spvUpdateFrame
   872: 
   873:   //停止
   874:   static boolean spvStopped;  //true=停止中
   875:   static boolean spvStoppedRequest;
   876: 
   877:   static final short[] spvCopiedNum = new short[SpriteScreen.SPR_COUNT];
   878:   static final short[] spvCopiedCol = new short[SpriteScreen.SPR_COUNT];
   879:   static final byte[] spvCopiedPrw = new byte[SpriteScreen.SPR_COUNT];
   880:   static final boolean[] spvCopiedH = new boolean[SpriteScreen.SPR_COUNT];
   881:   static final boolean[] spvCopiedV = new boolean[SpriteScreen.SPR_COUNT];
   882:   static final int[] spvCopiedPat = new int[32 * 4096];
   883:   static final short[] spvCopiedTNum = new short[4096 * 4];
   884:   static final short[] spvCopiedTCol = new short[4096 * 4];
   885:   static final boolean[] spvCopiedTH = new boolean[4096 * 4];
   886:   static final boolean[] spvCopiedTV = new boolean[4096 * 4];
   887:   static final int[] spvCopiedPalTbl = new int[65536];
   888:   static final int[] spvCopiedPal16TS = new int[256];
   889: 
   890:   static short[] spvNum;
   891:   static short[] spvCol;
   892:   static byte[] spvPrw;
   893:   static boolean[] spvH;
   894:   static boolean[] spvV;
   895:   static int[] spvPat;
   896:   static short[] spvTNum;
   897:   static short[] spvTCol;
   898:   static boolean[] spvTH;
   899:   static boolean[] spvTV;
   900:   static int[] spvPalTbl;
   901:   static int[] spvPal16TS;
   902: 
   903:   static final int[] spvPal32TS = new int[256];
   904: 
   905:   static void spvSetStoppedOn () {
   906:     spvStopped = true;
   907:     spvStoppedRequest = true;
   908:     System.arraycopy (SpriteScreen.sprNumPort, 0, spvCopiedNum, 0, SpriteScreen.SPR_COUNT);
   909:     System.arraycopy (SpriteScreen.sprColPort, 0, spvCopiedCol, 0, SpriteScreen.SPR_COUNT);
   910:     System.arraycopy (SpriteScreen.sprPrw, 0, spvCopiedPrw, 0, SpriteScreen.SPR_COUNT);
   911:     System.arraycopy (SpriteScreen.sprH, 0, spvCopiedH, 0, SpriteScreen.SPR_COUNT);
   912:     System.arraycopy (SpriteScreen.sprV, 0, spvCopiedV, 0, SpriteScreen.SPR_COUNT);
   913:     System.arraycopy (SpriteScreen.sprPatPort, 0, spvCopiedPat, 0, 32 * 4096);
   914:     System.arraycopy (SpriteScreen.sprTNum, 0, spvCopiedTNum, 0, 4096 * 4);
   915:     System.arraycopy (SpriteScreen.sprTColPort, 0, spvCopiedTCol, 0, 4096 * 4);
   916:     System.arraycopy (SpriteScreen.sprTH, 0, spvCopiedTH, 0, 4096 * 4);
   917:     System.arraycopy (SpriteScreen.sprTV, 0, spvCopiedTV, 0, 4096 * 4);
   918:     System.arraycopy (VideoController.vcnPalBase[VideoController.VCN_CONTRAST_SCALE * 15], 0, spvCopiedPalTbl, 0, 65536);
   919:     System.arraycopy (VideoController.vcnPal16TS, 0, spvCopiedPal16TS, 0, 256);
   920:     spvNum = spvCopiedNum;
   921:     spvCol = spvCopiedCol;
   922:     spvPrw = spvCopiedPrw;
   923:     spvH = spvCopiedH;
   924:     spvV = spvCopiedV;
   925:     spvPat = spvCopiedPat;
   926:     spvTNum = spvCopiedTNum;
   927:     spvTCol = spvCopiedTCol;
   928:     spvTH = spvCopiedTH;
   929:     spvTV = spvCopiedTV;
   930:     spvPalTbl = spvCopiedPalTbl;
   931:     spvPal16TS = spvCopiedPal16TS;
   932:   }  //spvSetStoppedOn
   933: 
   934:   static void spvSetStoppedOff () {
   935:     spvStopped = false;
   936:     spvStoppedRequest = false;
   937:     spvNum = SpriteScreen.sprNumPort;
   938:     spvCol = SpriteScreen.sprColPort;
   939:     spvPrw = SpriteScreen.sprPrw;
   940:     spvH = SpriteScreen.sprH;
   941:     spvV = SpriteScreen.sprV;
   942:     spvPat = SpriteScreen.sprPatPort;
   943:     spvTNum = SpriteScreen.sprTNum;
   944:     spvTCol = SpriteScreen.sprTColPort;
   945:     spvTH = SpriteScreen.sprTH;
   946:     spvTV = SpriteScreen.sprTV;
   947:     spvPalTbl = VideoController.vcnPalBase[VideoController.VCN_CONTRAST_SCALE * 15];
   948:     spvPal16TS = VideoController.vcnPal16TS;
   949:   }  //spvSetStoppedOff
   950: 
   951: }  //class SpritePatternViewer