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 int SPV_COLS = 16;
    31:   static final int SPV_ROWS = 16;
    32:   static final int SPV_LEFT_MARGIN = 20;
    33:   static final int SPV_TOP_MARGIN = 20;
    34:   static final int SPV_SPACE = 4;
    35:   static final int SPV_WIDTH = SPV_LEFT_MARGIN + (32 + SPV_SPACE) * SPV_COLS;
    36:   static final int SPV_HEIGHT = SPV_TOP_MARGIN + (32 + SPV_SPACE) * SPV_ROWS;
    37:   static final int SPV_BACKGROUND_RGB = 0xff333333;  //背景色
    38:   static final int SPV_FOREGROUND_RGB = 0xffffffff;  //文字色
    39:   static final String SPV_FONT_NAME = "Dialog";
    40:   static final int SPV_FONT_STYLE = Font.PLAIN;
    41:   static final int SPV_FONT_SIZE = 12;
    42: 
    43:   //イメージ
    44:   static BufferedImage spvImage;  //イメージ
    45: 
    46:   //ビットマップ
    47:   static int[] spvBitmap;  //ビットマップ
    48: 
    49:   //キャンバス
    50:   static JPanel spvCanvas;  //キャンバス
    51:   static JScrollPane spvScrollPane;  //スクロールペイン
    52: 
    53:   //バンク
    54:   static int spvBankNumber;  //バンク
    55:   static JSpinner spvBankSpinner;  //バンクスピナー
    56:   static SpinnerNumberModel spvBankModel;  //バンクスピナーのスピナーモデル
    57: 
    58:   //パレットブロック
    59:   //  スプライトは16x16だがBGが8x8で使用する場合があるので8x8毎にパレットブロックを持たせる
    60:   static final int[] spvLastBlockArray = new int[4 * 4096];  //パターン→前回のパレットブロック<<4
    61:   static final int[] spvBlockArray = new int[4 * 4096];  //パターン→パレットブロック<<4
    62:   static boolean spvAutoBlock;  //true=自動,false=固定
    63:   static int spvFixedBlock;  //固定パレットブロック。0~15
    64:   static JSpinner spvBlockSpinner;  //パレットブロックスピナー
    65:   static SpinnerNumberModel spvBlockModel;  //パレットブロックスピナーのスピナーモデル
    66: 
    67:   //停止
    68:   static boolean spvStopped;  //true=停止中
    69: 
    70:   //ウインドウ
    71:   static JFrame spvFrame;
    72: 
    73:   //タイマー
    74:   public static final int SPV_INTERVAL = 10;
    75:   public static int spvTimer;
    76: 
    77:   //spvInit ()
    78:   //  初期化
    79:   public static void spvInit () {
    80: 
    81:     //バンク
    82:     spvBankNumber = 0;
    83: 
    84:     //パレットブロック
    85:     for (int n = 0; n < 4 * 4096; n++) {  //8x8パターン番号
    86:       spvLastBlockArray[n] = 1 << 4;  //8x8パレットブロック1<<4
    87:     }
    88:     spvAutoBlock = true;
    89:     spvFixedBlock = 1;
    90: 
    91:     //停止
    92:     spvStopped = false;
    93: 
    94:     //ウインドウ
    95:     spvFrame = null;
    96: 
    97:     //タイマー
    98:     spvTimer = 0;
    99: 
   100:   }  //spvInit
   101: 
   102:   //spvStart ()
   103:   //  開始
   104:   public static void spvStart () {
   105:     if (RestorableFrame.rfmGetOpened (Settings.SGS_SPV_FRAME_KEY)) {
   106:       spvOpen ();
   107:     }
   108:   }  //spvStart
   109: 
   110:   //spvOpen ()
   111:   //  開く
   112:   public static void spvOpen () {
   113:     if (spvFrame == null) {
   114:       spvMakeFrame ();
   115:     } else {
   116:       spvUpdateFrame ();
   117:     }
   118:     XEiJ.dbgVisibleMask |= XEiJ.DBG_SPV_VISIBLE_MASK;
   119:     XEiJ.pnlExitFullScreen (false);
   120:     spvFrame.setVisible (true);
   121:   }  //spvOpen
   122: 
   123:   //spvMakeFrame ()
   124:   //  作る
   125:   //  ここでは開かない
   126:   static void spvMakeFrame () {
   127: 
   128:     //イメージ
   129:     spvImage = new BufferedImage (SPV_WIDTH, SPV_HEIGHT, BufferedImage.TYPE_INT_ARGB);
   130:     Graphics2D g2 = spvImage.createGraphics ();
   131:     g2.setColor (new Color (SPV_BACKGROUND_RGB));
   132:     g2.fillRect (0, 0, SPV_WIDTH, SPV_HEIGHT);
   133:     g2.setColor (new Color (SPV_FOREGROUND_RGB));
   134:     Font font = new Font (SPV_FONT_NAME, SPV_FONT_STYLE, SPV_FONT_SIZE);
   135:     g2.setFont (font);
   136:     FontRenderContext frc = g2.getFontRenderContext ();
   137:     for (int i = 0; i < 16; i++) {
   138:       String s = String.format ("x%X", i);
   139:       TextLayout tl = new TextLayout (s, font, frc);
   140:       Rectangle2D r = tl.getBounds ();
   141:       int rx = (int) Math.round (r.getX ());
   142:       int ry = (int) Math.round (r.getY ());
   143:       int rw = (int) Math.round (r.getWidth ());
   144:       int rh = (int) Math.round (r.getHeight ());
   145:       int bx = SPV_LEFT_MARGIN + (32 + SPV_SPACE) * i + 32 / 2;  //下辺の中央を合わせる位置
   146:       int by = SPV_TOP_MARGIN - SPV_SPACE;
   147:       g2.drawString (s, bx - rx - rw / 2, by - ry - rh);
   148:     }
   149:     for (int i = 0; i < 16; i++) {
   150:       String s = String.format ("%Xx", i);
   151:       TextLayout tl = new TextLayout (s, font, frc);
   152:       Rectangle2D r = tl.getBounds ();
   153:       int rx = (int) Math.round (r.getX ());
   154:       int ry = (int) Math.round (r.getY ());
   155:       int rw = (int) Math.round (r.getWidth ());
   156:       int rh = (int) Math.round (r.getHeight ());
   157:       int bx = SPV_LEFT_MARGIN - SPV_SPACE;  //右辺の中央を合わせる位置
   158:       int by = SPV_TOP_MARGIN + (32 + SPV_SPACE) * i + 32 / 2;
   159:       g2.drawString (s, bx - rx - rw, by - ry - rh / 2);
   160:     }
   161: 
   162:     //ビットマップ
   163:     spvBitmap = ((DataBufferInt) spvImage.getRaster ().getDataBuffer ()).getData ();
   164: 
   165:     //キャンバス
   166:     spvCanvas = ComponentFactory.setFixedSize (
   167:       new JPanel () {
   168:         @Override protected void paintComponent (Graphics g) {
   169:           super.paintComponent (g);
   170:           g.drawImage (spvImage, 0, 0, null);
   171:         }
   172:         @Override protected void paintBorder (Graphics g) {
   173:         }
   174:         @Override protected void paintChildren (Graphics g) {
   175:         }
   176:       },
   177:       SPV_WIDTH, SPV_HEIGHT);
   178:     spvCanvas.setBackground (new Color (SPV_BACKGROUND_RGB));
   179:     spvCanvas.setOpaque (true);
   180:     spvScrollPane = ComponentFactory.setPreferredSize (
   181:       new JScrollPane (spvCanvas),
   182:       SPV_WIDTH + 20, SPV_HEIGHT + 20);
   183: 
   184:     //アクションリスナー
   185:     ActionListener listener = new ActionListener () {
   186:       @Override public void actionPerformed (ActionEvent ae) {
   187:         Object source = ae.getSource ();
   188:         String command = ae.getActionCommand ();
   189:         switch (command) {
   190:         case "Stop":
   191:           spvStopped = ((JCheckBox) source).isSelected ();
   192:           break;
   193:         case "Auto ":
   194:           spvAutoBlock = true;
   195:           break;
   196:         case "Fixed ":
   197:           spvAutoBlock = false;
   198:           break;
   199:         default:
   200:           System.out.println ("unknown action command " + command);
   201:         }
   202:       }
   203:     };
   204: 
   205:     //ウインドウ
   206:     ButtonGroup paletGroup = new ButtonGroup ();
   207:     spvFrame = Multilingual.mlnTitle (
   208:       ComponentFactory.createRestorableSubFrame (
   209:         Settings.SGS_SPV_FRAME_KEY,
   210:         "Sprite Pattern Viewer",
   211:         null,
   212:         ComponentFactory.createBorderPanel (
   213:           0, 0,
   214:           //CENTER
   215:           spvScrollPane,
   216:           //NORTH
   217:           ComponentFactory.createHorizontalBox (
   218:             Box.createHorizontalGlue (),
   219:             Multilingual.mlnText (
   220:               ComponentFactory.createLabel ("Bank "),
   221:               "ja", "バンク "),
   222:             spvBankSpinner = ComponentFactory.createNumberSpinner (
   223:               spvBankModel = new SpinnerNumberModel (spvBankNumber, 0, 15, 1),
   224:               2,
   225:               new ChangeListener () {
   226:                 @Override public void stateChanged (ChangeEvent ce) {
   227:                   spvBankNumber = spvBankModel.getNumber ().intValue ();
   228:                 }
   229:               }
   230:               ),
   231:             Box.createHorizontalStrut (20),
   232:             Multilingual.mlnText (
   233:               ComponentFactory.createLabel ("Palet "),
   234:               "ja", "パレット "),
   235:             Multilingual.mlnText (
   236:               ComponentFactory.createRadioButton (paletGroup, spvAutoBlock, "Auto ", listener),
   237:               "ja", "自動 "),
   238:             Multilingual.mlnText (
   239:               ComponentFactory.createRadioButton (paletGroup, !spvAutoBlock, "Fixed ", listener),
   240:               "ja", "固定 "),
   241:             spvBlockSpinner = ComponentFactory.createNumberSpinner (
   242:               spvBlockModel = new SpinnerNumberModel (spvFixedBlock, 0, 15, 1),
   243:               2,
   244:               new ChangeListener () {
   245:                 @Override public void stateChanged (ChangeEvent ce) {
   246:                   spvFixedBlock = spvBlockModel.getNumber ().intValue ();
   247:                 }
   248:               }
   249:               ),
   250:             Box.createHorizontalStrut (20),
   251:             Multilingual.mlnText (
   252:               ComponentFactory.createCheckBox (spvStopped, "Stop", listener),
   253:               "ja", "停止"),
   254:             Box.createHorizontalGlue ()
   255:             )  //HorizontalBox
   256:           )  //BorderPanel
   257:         ),  //SubFrame
   258:       "ja", "スプライトパターンビュア");
   259: 
   260:     //ウインドウリスナー
   261:     ComponentFactory.addListener (
   262:       spvFrame,
   263:       new WindowAdapter () {
   264:         @Override public void windowClosing (WindowEvent we) {
   265:           XEiJ.dbgVisibleMask &= ~XEiJ.DBG_SPV_VISIBLE_MASK;
   266:         }
   267:       });
   268: 
   269:   }  //spvMakeFrame
   270: 
   271:   //spvUpdateFrame ()
   272:   //  更新する
   273:   static void spvUpdateFrame () {
   274:     if (spvFrame == null) {  //未初期化
   275:       return;
   276:     }
   277:     if (spvStopped) {  //停止中
   278:       return;
   279:     }
   280: 
   281:     if (spvAutoBlock) {  //自動
   282:       //パターンに割り当てられたパレットブロックを集める
   283:       //  一旦すべて消す
   284:       Arrays.fill (spvBlockArray, -1);
   285:       //  スプライトから集める
   286:       for (int sn = 0; sn < SpriteScreen.sprNumberOfSprites; sn++) {  //スプライト番号
   287:         if (256 <= sn && sn < 256 + 8) {  //欠番
   288:           continue;
   289:         }
   290:         if (SpriteScreen.sprPrw[sn] != 0) {  //表示されている
   291:           int n = 4 * SpriteScreen.sprNumPort[sn];  //8x8パターン番号
   292:           int p = SpriteScreen.sprColPort[sn];  //パレットブロック<<4
   293:           for (int cr = 0; cr < 4; cr++) {  //16x16内8x8桁行
   294:             if (spvBlockArray[n] < 0) {
   295:               spvBlockArray[n] =
   296:                 spvLastBlockArray[n] = p;
   297:             }
   298:             n++;
   299:           }
   300:         }
   301:       }
   302:       //  バックグラウンドから集める
   303:       int tm = 0;  //表示されているテキストページのマスク
   304:       if ((SpriteScreen.sprReg4BgCtrlPort & 1) != 0) {  //BG0が表示されている
   305:         tm |= 1 << ((SpriteScreen.sprReg4BgCtrlPort >> 1) & 3);  //BG0に割り当てられているテキストページ
   306:       }
   307:       if (((SpriteScreen.sprReg8ResoPort & 3) == 0 ||  //256x256または
   308:            SpriteScreen.spr512bg1) &&  //512x512でBG1を表示かつ
   309:           (SpriteScreen.sprReg4BgCtrlPort & 8) != 0) {  //BG1が表示されている
   310:         tm |= 1 << ((SpriteScreen.sprReg4BgCtrlPort >> 4) & 3);  //BG1に割り当てられているテキストページ
   311:       }
   312:       if ((SpriteScreen.sprReg8ResoPort & 3) == 0) {  //256x256
   313:         for (int tp = 0; tp < 4; tp++) {  //テキストページ
   314:           if ((tm & (1 << tp)) != 0) {  //表示されている
   315:             int o = 4096 * tp;  //テキスト配列インデックス開始位置
   316:             for (int i = 0; i < 4096; i++) {  //テキスト配列インデックス
   317:               int n = SpriteScreen.sprTNum[o + i] >> 3;  //8x8パターン番号
   318:               int p = SpriteScreen.sprTColPort[o + i];  //パレットブロック<<4
   319:               if (spvBlockArray[n] < 0) {
   320:                 spvBlockArray[n] =
   321:                   spvLastBlockArray[n] = p;
   322:               }
   323:             }
   324:           }
   325:         }
   326:       } else {  //512x512
   327:         for (int tp = 0; tp < 4; tp++) {  //テキストページ
   328:           if ((tm & (1 << tp)) != 0) {  //表示されている
   329:             int o = 4096 * tp;  //テキスト配列インデックス開始位置
   330:             for (int i = 0; i < 4096; i++) {  //テキスト配列インデックス
   331:               int n = SpriteScreen.sprTNum[o + i] >> 1;  //8x8パターン番号
   332:               int p = SpriteScreen.sprTColPort[o + i];  //パレットブロック<<4
   333:               for (int cr = 0; cr < 4; cr++) {  //16x16内8x8桁行
   334:                 if (spvBlockArray[n] < 0) {
   335:                   spvBlockArray[n] =
   336:                     spvLastBlockArray[n] = p;
   337:                 }
   338:                 n++;
   339:               }
   340:             }
   341:           }
   342:         }
   343:       }
   344:       //  集められなかったものは前回と同じにする
   345:       for (int n = 0; n < 4 * 4096; n++) {  //8x8パターン番号
   346:         if (spvBlockArray[n] < 0) {
   347:           spvBlockArray[n] = spvLastBlockArray[n];
   348:         }
   349:       }
   350:     } else {  //固定
   351:       Arrays.fill (spvBlockArray, spvFixedBlock << 4);
   352:     }
   353: 
   354:     //パターンを表示する
   355:     int a = 32 * SPV_COLS * SPV_ROWS * spvBankNumber;  //パターン配列インデックス
   356:     for (int rr = 0; rr < SPV_ROWS; rr++) {  //16x16行
   357:       int yy = SPV_TOP_MARGIN + (32 + SPV_SPACE) * rr;  //16x16パターン左上Y座標
   358:       for (int cc = 0; cc < SPV_COLS; cc++) {  //16x16桁
   359:         int xx = SPV_LEFT_MARGIN + (32 + SPV_SPACE) * cc;  //16x16パターン左上X座標
   360:         int ii = xx + SPV_WIDTH * yy;  //16x16パターン左上ビットマップインデックス
   361:         int jj = ii - 2;
   362:         for (int vv = 0; vv < 16; vv++) {
   363:           spvBitmap[jj] =
   364:             spvBitmap[jj + 1] =
   365:               spvBitmap[jj + SPV_WIDTH] =
   366:                 spvBitmap[jj + SPV_WIDTH + 1] = SPV_BACKGROUND_RGB;
   367:           jj += SPV_WIDTH * 2;
   368:         }
   369:         int n = 4 * (SPV_COLS * SPV_ROWS * spvBankNumber + cc + SPV_COLS * rr);  //8x8パターン番号
   370:         for (int c = 0; c < 2; c++) {  //16x16内8x8桁
   371:           int x = xx + 16 * c;  //8x8パターン左上X座標
   372:           for (int r = 0; r < 2; r++) {  //16x16内8x8行
   373:             int y = yy + 16 * r;  //8x8パターン左上Y座標
   374:             int i = x + SPV_WIDTH * y;  //8x8パターン左上ビットマップインデックス
   375:             int p = spvBlockArray[n];  //8x8パレットブロック<<4
   376:             int j = ii - 2 + SPV_WIDTH * 2 * (p >> 4);
   377:             spvBitmap[j] =
   378:               spvBitmap[j + 1] =
   379:                 spvBitmap[j + SPV_WIDTH] =
   380:                   spvBitmap[j + SPV_WIDTH + 1] = SPV_FOREGROUND_RGB;
   381:             for (int v = 0; v < 8; v++) {  //8x8内1x1行
   382:               int d = SpriteScreen.sprPatPort[a++];  //8x1データ
   383:               for (int u = 0; u < 8; u++) {  //8x8内1x1桁
   384:                 spvBitmap[i] =
   385:                   spvBitmap[i + 1] =
   386:                     spvBitmap[i + SPV_WIDTH] =
   387:                       spvBitmap[i + SPV_WIDTH + 1] = VideoController.vcnPal32TS[p + ((d >> ((~u & 7) << 2)) & 15)];
   388:                 i += 2;
   389:               }
   390:               i += 2 * SPV_WIDTH - 2 * 8;
   391:             }
   392:             n++;
   393:           }
   394:         }
   395:       }
   396:     }
   397:     spvCanvas.repaint ();
   398: 
   399:   }  //spvUpdateFrame
   400: 
   401: }  //class SpritePatternViewer
   402: 
   403: 
   404: