FlashingLights.java
     1: //========================================================================================
     2: //  FlashingLights.java
     3: //    en:Flashing lights reduction
     4: //    ja:点滅光の軽減
     5: //  Copyright (C) 2003-2026 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.event.*;  //ActionListener
    16: import java.util.*;  //Arrays
    17: import javax.swing.*;  //JMenu
    18: import javax.swing.event.*;  //ChangeListener
    19: 
    20: //点滅光の軽減
    21: 
    22: //class FlashingLights
    23: public class FlashingLights {
    24: 
    25:   public static final boolean FLR_ON = true;
    26:   public static final boolean FLR_DEBUG = false;
    27: 
    28:   //デフォルト
    29:   public static final boolean FLR_ENABLED = false;
    30:   public static final boolean FLR_PALETTE = true;
    31:   public static final boolean FLR_CONTRAST = true;
    32:   public static final boolean FLR_SCREEN = true;
    33:   public static final int FLR_LIMIT = 13;
    34:   public static final int FLR_WAVELENGTH = 20;
    35:   public static final int FLR_DELAY = 20;
    36:   public static final boolean FLR_INFORMED = false;
    37: 
    38:   //設定
    39:   //  メニューで変更できるもの
    40:   public static boolean flrRequestEnabled;  //点滅光を軽減する
    41:   public static boolean flrRequestPalette;  //パレットによる点滅を止める
    42:   public static boolean flrContrast;  //コントラストを制限する
    43:   public static boolean flrRequestScreen;  //画面を徐々に明るくする
    44:   public static int flrWavelength;  //パレットによる点滅の波長
    45:   public static int flrLimit;  //コントラストの上限
    46:   public static int flrDelay;  //画面が明るくなるまでの時間
    47:   public static boolean flrInformed;  //案内済み
    48:   //  flrVsyncで反映させるもの
    49:   //  変更するとき初期化が必要なものなど
    50:   public static boolean flrEnabled;
    51:   public static boolean flrPalette;
    52:   public static boolean flrScreen;
    53:   //  flrVsyncを駆動させる条件
    54:   //  flrEnabled || flrRequestEnabled
    55:   public static boolean flrActive;
    56: 
    57:   //デバッグ
    58:   public static boolean flrDebugLocation;  //観測点を表示する
    59: 
    60:   //時刻
    61:   //  リングバッファの次に書き込む位置
    62:   //  パレットが変化した時刻
    63:   //  flrVsyncでインクリメントする
    64:   public static int flrTime;
    65: 
    66:   //パレット
    67:   //  ゴースト
    68:   public static final int[] flrGhostG8 = new int[256];
    69:   public static final int[] flrGhostTS = new int[256];
    70:   //  前回と前々回のデータと時刻
    71:   //  0=前回のgrbi,1=前回のtime,1=前々回のgrbi,2=前々回のtime
    72:   public static final int[] flrHistoryG8 = new int[4 * 256];
    73:   public static final int[] flrHistoryTS = new int[4 * 256];
    74: 
    75:   //画面
    76:   //  観測点の座標
    77:   public static final int[] flrIndex = new int[256];
    78:   //  観測点のデータを保存するリングバッファ
    79:   //  データは7*R+6*G+B
    80:   //  有効なデータの位置はflrTime-flrDelay~flrTime-1
    81:   public static final int FLR_RING_SIZE = 64;  //2の累乗
    82:   public static final int[] flrRing = new int[FLR_RING_SIZE];
    83:   //  リングバッファにあるデータの合計
    84:   public static int flrTotal;
    85:   //  画面が有効になった
    86:   //  観測点の配置が変わった
    87:   //  flrVsyncで画面の情報をリセットする
    88:   public static boolean flrResetScreen;
    89:   //  画面の明るさ
    90:   public static float[] flrBrightness = new float[4];
    91: 
    92:   //メニュー
    93:   public static JCheckBoxMenuItem flrMenuEnabled;
    94:   public static JCheckBoxMenuItem flrMenuPalette;
    95:   public static JCheckBoxMenuItem flrMenuContrast;
    96:   public static JCheckBoxMenuItem flrMenuScreen;
    97:   public static DecimalSpinner flrMenuWavelength;
    98:   public static DecimalSpinner flrMenuLimit;
    99:   public static DecimalSpinner flrMenuDelay;
   100: 
   101:   //flrInit ()
   102:   //  初期化
   103:   public static void flrInit () {
   104:     //設定
   105:     flrRequestEnabled = Settings.sgsGetOnOff ("flrenabled", FLR_ENABLED);
   106:     flrRequestPalette = Settings.sgsGetOnOff ("flrpalette", FLR_PALETTE);
   107:     flrContrast = Settings.sgsGetOnOff ("flrcontrast", FLR_CONTRAST);
   108:     flrRequestScreen = Settings.sgsGetOnOff ("flrscreen", FLR_SCREEN);
   109:     flrWavelength = Settings.sgsGetInt ("flrwavelength", FLR_WAVELENGTH, 1, 30);
   110:     flrLimit = Settings.sgsGetInt ("flrlimit", FLR_LIMIT, 0, 15);
   111:     flrDelay = Settings.sgsGetInt ("flrdelay", FLR_DELAY, 1, 60);
   112:     flrInformed = Settings.sgsGetOnOff ("flrinformed", FLR_INFORMED);
   113:     flrEnabled = false;
   114:     flrPalette = false;
   115:     flrScreen = false;
   116:     flrActive = flrEnabled || flrRequestEnabled;
   117:     //デバッグ
   118:     flrDebugLocation = false;
   119:     //時刻
   120:     flrTime = 0;
   121:     //パレット
   122:     Arrays.fill (flrGhostG8, 0);
   123:     Arrays.fill (flrGhostTS, 0);
   124:     Arrays.fill (flrHistoryG8, 0);
   125:     Arrays.fill (flrHistoryTS, 0);
   126:     //画面
   127:     Arrays.fill (flrIndex, 0);
   128:     Arrays.fill (flrRing, 0);
   129:     flrTotal = 0;
   130:     flrResetScreen = false;
   131:     Arrays.fill (flrBrightness, 1.0F);
   132:     //観測点の座標を作る
   133:     //  pnlInitより後
   134:     flrMakeIndex ();
   135:   }  //flrInit
   136: 
   137:   //flrInform ()
   138:   //  案内を表示する
   139:   public static void flrInform () {
   140:     if (!flrInformed) {
   141:       int result = JOptionPane.showConfirmDialog (
   142:         XEiJ.frmFrame,
   143:         Multilingual.mlnJapanese ?
   144:         "光過敏性発作予防のための点滅光の軽減を有効にしますか?\n" +
   145:         "この機能を有効にすると、エミュレーションの正確さよりも安全性が優先されます。\n" +
   146:         "設定はアクセシビリティメニューから変更できます。" :
   147:         "Do you want to enable reducing flashing lights to prevent photosensitive seizures?\n" +
   148:         "Enabling this feature prioritizes safety over emulation accuracy.\n" +
   149:         "You can change the settings in the accessibility menu.",
   150:         Multilingual.mlnJapanese ? "点滅光の軽減" : "Flashing lights reduction",
   151:         JOptionPane.YES_NO_OPTION,
   152:         JOptionPane.PLAIN_MESSAGE);
   153:       if (result == JOptionPane.YES_OPTION) {
   154:         flrInitialize (true);
   155:         flrInformed = true;
   156:       } else if (result == JOptionPane.NO_OPTION) {
   157:         flrInitialize (false);
   158:         flrInformed = true;
   159:       }
   160:     }
   161:   }  //flrInform
   162: 
   163:   //flrTini ()
   164:   //  後始末
   165:   public static void flrTini () {
   166:     Settings.sgsPutOnOff ("flrenabled", flrRequestEnabled);
   167:     Settings.sgsPutOnOff ("flrpalette", flrRequestPalette);
   168:     Settings.sgsPutOnOff ("flrcontrast", flrContrast);
   169:     Settings.sgsPutOnOff ("flrscreen", flrRequestScreen);
   170:     Settings.sgsPutInt ("flrwavelength", flrWavelength);
   171:     Settings.sgsPutInt ("flrlimit", flrLimit);
   172:     Settings.sgsPutInt ("flrdelay", flrDelay);
   173:     Settings.sgsPutOnOff ("flrinformed", flrInformed);
   174:   }  //flrTini
   175: 
   176:   //flrMakeIndex ()
   177:   //  観測点の座標を作る
   178:   //  細い直線を検出しないように格子点からずらす
   179:   //  XEiJ.pnlScreenWidth,XEiJ.pnlScreenHeightを使う
   180:   //  flrInitと、flrEnabledのときXEiJ.pnlUpdateArrangementが呼ぶ
   181:   public static void flrMakeIndex () {
   182:     int w = XEiJ.pnlScreenWidth;
   183:     int h = XEiJ.pnlScreenHeight;
   184:     int r = 26389;
   185:     for (int n = 0; n < 256; n++) {  //YX
   186:       r = (char) (15625 * r + 1);
   187:       int o = r >> 8;
   188:       int x = (w >> 9) + (((((n & 15) << 4) | (o & 15)) * w) >> 8);
   189:       int y = (h >> 9) + ((((n & 240) | (o >> 4)) * h) >> 8);
   190:       flrIndex[n] = x + (y << 10);
   191:     }  //for n
   192:     flrResetScreen = true;
   193:   }  //flrMakeIndex
   194: 
   195:   //リスク
   196:   //  輝度
   197:   //    y = 0.2126*r + 0.7152*g + 0.0722*b
   198:   //    Rec.709  https://ja.wikipedia.org/wiki/Rec._709
   199:   //  輝度の係数は大雑把に言うと3:10:1くらい
   200:   //    y = (3*r + 10*g + b)/14
   201:   //  今回の目的は光過敏性発作対策なので赤の危険度を高く見積もって7:6:1とする
   202:   //    risk = (7*r + 6*g + b)/14
   203:   //  grbiのとき
   204:   //    risk = (7*(2*r+i) + 6*(2*g+i) + 2*b+i)/14
   205:   //         = (7*r + 6*g + b + 7*i)/7
   206:   //  (iが使われない可能性は考慮しない)
   207:   //
   208:   //  矩形波の山と谷はriskによって明確に区別できなければならない
   209:   //  どちらも大きくないと判断して止めないと矩形波が素通りしてしまい
   210:   //  どちらも大きいと判断して止めると逆位相の矩形波が出力されてしまう
   211:   //  条件分岐を増やしたくないので係数を大きくしてriskの衝突を避ける
   212:   //  雑に探したらすぐ見つかった
   213:   //  以下の式の分子はr,g,bそれぞれ8bitの組み合わせ16777216通りで異なる値になる
   214:   //    risk = (70000*r + 60037*g + 10001*b)/140038
   215:   //    perl -e "$v=qq(\0)x(255*140039+1);for$r(0..255){for$g(0..255){for$b(0..255){$i=70000*$r+60037*$g+10001*$b;vec($v,$i,8)and die;vec($v,$i,8)=1;}}}"
   216:   //  grbiのときも6bitなので同じ式が使える
   217:   //    risk = (70000*(2*r+i) + 60037*(2*g+i) + 10001*(2*b+i))/140038
   218:   //         = (70000*r + 60037*g + 10001*b + 70019*i)/70019
   219: 
   220:   //flrRiskOfGRBI (grbi)
   221:   //  grbiのrisk
   222:   //  矩形波の条件が先にあるときは使われる機会が少ない
   223:   //  任意の波形を対象にするときはテーブルにする
   224:   public static int flrRiskOfGRBI (int grbi) {
   225:     return (70000 * ((grbi >> 6) & 31) +  //R
   226:             60037 * ((grbi >> 11) & 31) +  //G
   227:             10001 * ((grbi >> 1) & 31) +  //B
   228:             70019 * (grbi & 1));  //I
   229:   }  //flrRiskOfGRBI
   230: 
   231:   //flrWriteByteG8 (a, grbi)
   232:   //flrWriteWordG8 (a, grbi)
   233:   //  グラフィックパレットに書き込む
   234:   //  High→Low→Highの書き込みを矩形波とみなして2回目のHighの出力を抑制する
   235:   //  memmoveなどでバイト転送で書き込まれると検出できない
   236:   //  MemoryMappedDevice.MMD_VCNが呼ぶ
   237:   public static void flrWriteByteG8 (int a, int d) {
   238:     int t = VideoController.vcnPal16G8Port[(a >> 1) & 255];
   239:     if ((a & 1) == 0) {
   240:       flrWriteWordG8 (a, (d << 8) | (t & 255));
   241:     } else {
   242:       flrWriteWordG8 (a - 1, (t & 65280) | (d & 255));
   243:     }
   244:   }  //flrWriteByteG8
   245:   public static void flrWriteWordG8 (int a, int d) {
   246:     int n = (a >> 1) & 255;
   247:     int grbi = (char) d;
   248:     VideoController.vcnPal16G8Port[n] = grbi;
   249:     if (flrPalette) {
   250:       int grbi1 = flrHistoryG8[4 * n];  //前回
   251:       if (grbi1 != grbi) {  //前回と今回のgrbiが違う
   252:         int time1 = flrHistoryG8[4 * n + 1];
   253:         int grbi2 = flrHistoryG8[4 * n + 2];  //前々回
   254:         int time2 = flrHistoryG8[4 * n + 3];
   255:         int grbiOut = grbi;  //今回のgrbiを出力する
   256:         if (grbi2 == grbi &&  //前々回と今回のgrbiが同じ
   257:             (flrTime - time2) <= flrWavelength &&  //前々回から今回までの時間が波長以下
   258:             flrRiskOfGRBI (grbi1) < flrRiskOfGRBI (grbi)) {  //前回より今回の方がriskが大きい
   259:           grbiOut = grbi1;  //前回のgrbiを出力する
   260:         }
   261:         flrHistoryG8[4 * n] = grbi;  //今回
   262:         flrHistoryG8[4 * n + 1] = flrTime;
   263:         flrHistoryG8[4 * n + 2] = grbi1;  //前回
   264:         flrHistoryG8[4 * n + 3] = time1;
   265:         VideoController.vcnPal16G8[n] = grbiOut;
   266:         if ((n & 1) == 0) {  //a=0,4,8,12 n=0,2,4,6 n+1=1,3,5,7
   267:           VideoController.vcnPal8G16L[n] = grbiOut >> 8;
   268:           VideoController.vcnPal8G16L[n + 1] = grbiOut & 255;
   269:         } else {  //a=2,4,6,8 n-1=0,2,4,6 n=1,3,5,7
   270:           VideoController.vcnPal8G16H[n - 1] = grbiOut & 65280;
   271:           VideoController.vcnPal8G16H[n] = (grbiOut & 255) << 8;
   272:         }
   273:       }
   274:     }
   275:     VideoController.vcnPal32G8[n] = VideoController.vcnPalTbl[VideoController.vcnPal16G8[n]];
   276:     if ((VideoController.vcnReg3Curr & 0x001f) != 0) {  //グラフィックが表示されている
   277:       CRTC.crtAllStamp += 2;
   278:     }
   279:   }  //flrWriteWordG8
   280: 
   281:   //flrWriteByteTS (a, grbi)
   282:   //flrWriteWordTS (a, grbi)
   283:   //  テキストスプライトパレットに書き込む
   284:   //  High→Low→Highの書き込みを矩形波とみなして2回目のHighの出力を抑制する
   285:   //  memmoveなどでバイト転送で書き込まれると検出できない
   286:   //  MemoryMappedDevice.MMD_VCNが呼ぶ
   287:   public static void flrWriteByteTS (int a, int d) {
   288:     int t = VideoController.vcnPal16G8Port[(a >> 1) & 255];
   289:     if ((a & 1) == 0) {
   290:       flrWriteWordTS (a, (d << 8) | (t & 255));
   291:     } else {
   292:       flrWriteWordTS (a - 1, (t & 65280) | (d & 255));
   293:     }
   294:   }  //flrWriteByteTS
   295:   public static void flrWriteWordTS (int a, int d) {
   296:     int n = (a >> 1) & 255;
   297:     int grbi = (char) d;
   298:     VideoController.vcnPal16TSPort[n] = grbi;
   299:     if (flrPalette) {
   300:       int grbi1 = flrHistoryTS[4 * n];  //前回
   301:       if (grbi1 != grbi) {  //前回と今回のgrbiが違う
   302:         int time1 = flrHistoryTS[4 * n + 1];
   303:         int grbi2 = flrHistoryTS[4 * n + 2];  //前々回
   304:         int time2 = flrHistoryTS[4 * n + 3];
   305:         int grbiOut = grbi;  //今回のgrbiを出力する
   306:         if (grbi2 == grbi &&  //前々回と今回のgrbiが同じ
   307:             (flrTime - time2) <= flrWavelength &&  //前々回から今回までの時間が波長以下
   308:             flrRiskOfGRBI (grbi1) < flrRiskOfGRBI (grbi)) {  //前回より今回の方がriskが大きい
   309:           grbiOut = grbi1;  //前回のgrbiを出力する
   310:         }
   311:         flrHistoryTS[4 * n] = grbi;  //今回
   312:         flrHistoryTS[4 * n + 1] = flrTime;
   313:         flrHistoryTS[4 * n + 2] = grbi1;  //前回
   314:         flrHistoryTS[4 * n + 3] = time1;
   315:         VideoController.vcnPal16TS[n] = grbiOut;
   316:       }
   317:     }
   318:     VideoController.vcnPal32TS[n] = VideoController.vcnPalTbl[VideoController.vcnPal16TS[n]];
   319:     if (n < 16 ?
   320:         (VideoController.vcnReg3Curr & 0x0020) != 0 :  //テキスト画面が表示されている
   321:         (VideoController.vcnReg3Curr & 0x0040) != 0 &&
   322:         (SpriteScreen.sprReg4BgCtrlCurr & 0x0200) != 0) {  //スプライト画面が表示されている
   323:       CRTC.crtAllStamp += 2;
   324:     }
   325:   }  //flrWriteWordTS
   326: 
   327:   //flrUpdateContrast ()
   328:   //  コントラストを更新する
   329:   //  点滅光を軽減するが変更された
   330:   //  コントラストを制限するが変更された
   331:   //  コントラストの上限が変更された
   332:   //  設定が初期化された
   333:   //  表示モードテストでコントラストが変更された
   334:   //  コントラストポートに書き込まれた
   335:   public static void flrUpdateContrast () {
   336:     int curr = (VideoController.vcnTargetContrastMask != 0 ?
   337:                 VideoController.vcnTargetContrastTest :  //表示モードテストが優先
   338:                 flrEnabled && flrContrast ?
   339:                 Math.min (flrLimit, VideoController.vcnTargetContrastPort) :
   340:                 VideoController.vcnTargetContrastPort);
   341:     if (VideoController.vcnTargetContrastCurr != curr) {
   342:       VideoController.vcnTargetContrastCurr = curr;
   343:       VideoController.vcnTargetScaledContrast = VideoController.VCN_CONTRAST_SCALE * VideoController.vcnTargetContrastCurr;
   344:       CRTC.crtContrastClock = XEiJ.mpuClockTime;
   345:       CRTC.crtFrameTaskClock = Math.min (CRTC.crtContrastClock, CRTC.crtCaptureClock);
   346:     }
   347:   }  //flrUpdateContrast
   348: 
   349:   //flrVsync ()
   350:   //  垂直帰線期間の開始
   351:   //  flrActiveのときCRTCが呼ぶ
   352:   public static void flrVsync () {
   353:     //時刻を進める
   354:     flrTime++;
   355:     //パレットの設定を反映する
   356:     if (flrPalette != (flrRequestEnabled && flrRequestPalette)) {
   357:       flrPalette = (flrRequestEnabled && flrRequestPalette);
   358:       //グラフィックパレット
   359:       if (flrPalette) {
   360:         //パレットをゴーストにコピーする
   361:         System.arraycopy (VideoController.vcnPal16G8Port, 0,  //from
   362:                           flrGhostG8, 0,  //to
   363:                           256);  //length
   364:         //ゴーストを見せる
   365:         VideoController.vcnPal16G8 = flrGhostG8;
   366:       } else {
   367:         //元に戻す
   368:         VideoController.vcnPal16G8 = VideoController.vcnPal16G8Port;
   369:       }
   370:       for (int n = 0; n < 256; n++) {
   371:         int grbi = VideoController.vcnPal16G8[n];
   372:         VideoController.vcnPal32G8[n] = VideoController.vcnPalTbl[grbi];
   373:         if ((n & 1) == 0) {  //a=0,4,8,12 n=0,2,4,6 n+1=1,3,5,7
   374:           VideoController.vcnPal8G16L[n] = grbi >> 8;
   375:           VideoController.vcnPal8G16L[n + 1] = grbi & 255;
   376:         } else {  //a=2,4,6,8 n-1=0,2,4,6 n=1,3,5,7
   377:           VideoController.vcnPal8G16H[n - 1] = grbi & 65280;
   378:           VideoController.vcnPal8G16H[n] = (grbi & 255) << 8;
   379:         }
   380:       }
   381:       //テキストスプライトパレット
   382:       if (flrPalette) {
   383:         //パレットをゴーストにコピーする
   384:         System.arraycopy (VideoController.vcnPal16TSPort, 0,  //from
   385:                           flrGhostTS, 0,  //to
   386:                           256);  //length
   387:         if (!ScreenModeTest.smtPatternTestOn) {  //表示モードテストが優先
   388:           //ゴーストを見せる
   389:           VideoController.vcnPal16TS = flrGhostTS;
   390:         }
   391:       } else {
   392:         //元に戻す
   393:         if (!ScreenModeTest.smtPatternTestOn) {  //表示モードテストが優先
   394:           VideoController.vcnPal16TS = VideoController.vcnPal16TSPort;
   395:         }
   396:       }
   397:       for (int n = 0; n < 256; n++) {
   398:         VideoController.vcnPal32TS[n] = VideoController.vcnPalTbl[VideoController.vcnPal16TS[n]];
   399:       }
   400:       if ((VideoController.vcnReg3Curr & 0x007f) != 0) {  //グラフィックまたはテキストスプライトが表示されている
   401:         CRTC.crtAllStamp += 2;
   402:       }
   403:       //前回と前々回のデータと時刻
   404:       Arrays.fill (flrHistoryG8, 0);
   405:       Arrays.fill (flrHistoryTS, 0);
   406:     }
   407:     //画面の設定を反映する
   408:     if (flrScreen != (flrRequestEnabled && flrRequestScreen)) {
   409:       flrScreen = (flrRequestEnabled && flrRequestScreen);
   410:       if (flrScreen) {
   411:         flrResetScreen = true;
   412:       }
   413:     }
   414:     //設定を反映する
   415:     if (flrEnabled != flrRequestEnabled) {
   416:       flrEnabled = flrRequestEnabled;
   417:       flrActive = flrEnabled || flrRequestEnabled;
   418:       flrUpdateContrast ();
   419:     }
   420:     if (!flrEnabled) {
   421:       return;
   422:     }
   423:     //画面をリセットする
   424:     if (flrResetScreen) {
   425:       flrResetScreen = false;
   426:       int sum = 0;
   427:       for (int n = 0; n < 256; n++) {
   428:         int argb = XEiJ.pnlBM[flrIndex[n]];
   429:         sum += (7 * (((argb >> 16) & 255)) +
   430:                 6 * ((argb >> 8) & 255) +
   431:                 (argb & 255));
   432:       }
   433:       Arrays.fill (flrRing, sum);
   434:       flrTotal = sum * flrDelay;
   435:     }
   436:     //画面を更新する
   437:     if (flrScreen) {
   438:       //観測点のデータを集める
   439:       int sum = 0;
   440:       for (int n = 0; n < 256; n++) {
   441:         int argb = XEiJ.pnlBM[flrIndex[n]];
   442:         if (FLR_DEBUG &&
   443:             flrDebugLocation) {
   444:           XEiJ.pnlBM[flrIndex[n]] = 0xff00ff00;
   445:         }
   446:         sum += (7 * (((argb >> 16) & 255)) +
   447:                 6 * ((argb >> 8) & 255) +
   448:                 (argb & 255));
   449:       }
   450:       //データの合計から最古のデータを引いて最新のデータを加える
   451:       flrTotal += sum - flrRing[(flrTime - flrDelay) & (FLR_RING_SIZE - 1)];
   452:       //最新のデータを保存する
   453:       flrRing[flrTime & (FLR_RING_SIZE - 1)] = sum;
   454:       //平均を求める
   455:       float average = (float) flrTotal / (float) flrDelay;
   456:       //明るさを調整する
   457:       flrBrightness[XEiJ.pnlBMWrite & 3] = (float) sum <= average ? 1.0F : average / (float) sum;
   458:     }
   459:   }  //flrVsync
   460: 
   461:   //flrMakeMenu ()
   462:   //  メニューを作る
   463:   public static JMenu flrMakeMenu () {
   464:     return Multilingual.mlnText (
   465:       ComponentFactory.createMenu (
   466:         "Flashing lights reduction",
   467:         //点滅光を軽減する
   468:         flrMenuEnabled = Multilingual.mlnText (
   469:           ComponentFactory.createCheckBoxMenuItem (
   470:             flrRequestEnabled, "Reduce flashing lights",
   471:             new ActionListener () {
   472:               @Override public void actionPerformed (ActionEvent ae) {
   473:                 flrRequestEnabled = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
   474:                 flrActive = flrEnabled || flrRequestEnabled;
   475:               }}),
   476:           "ja", "点滅光を軽減する"),
   477:         //パレットによる点滅を止める
   478:         flrMenuPalette = Multilingual.mlnText (
   479:           ComponentFactory.createCheckBoxMenuItem (
   480:             flrRequestPalette, "Stop flashing with palette",
   481:             new ActionListener () {
   482:               @Override public void actionPerformed (ActionEvent ae) {
   483:                 flrRequestPalette = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
   484:               }}),
   485:           "ja", "パレットによる点滅を止める"),
   486:         //コントラストを制限する
   487:         flrMenuContrast = Multilingual.mlnText (
   488:           ComponentFactory.createCheckBoxMenuItem (
   489:             flrContrast, "Limit contrast",
   490:             new ActionListener () {
   491:               @Override public void actionPerformed (ActionEvent ae) {
   492:                 flrContrast = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
   493:                 flrUpdateContrast ();
   494:               }}),
   495:           "ja", "コントラストを制限する"),
   496:         //画面を徐々に明るくする
   497:         Multilingual.mlnText (
   498:           flrMenuScreen = ComponentFactory.createCheckBoxMenuItem (
   499:             flrRequestScreen, "Brighten screen slowly",
   500:             new ActionListener () {
   501:               @Override public void actionPerformed (ActionEvent ae) {
   502:                 flrRequestScreen = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
   503:               }}),
   504:           "ja", "画面を徐々に明るくする"),
   505:         //パレットによる点滅の波長
   506:         ComponentFactory.createHorizontalBox (
   507:           Box.createHorizontalStrut (20),
   508:           flrMenuWavelength = ComponentFactory.createDecimalSpinner (
   509:             flrWavelength, 1, 30, 1, 0,
   510:             new ChangeListener () {
   511:               @Override public void stateChanged (ChangeEvent ce) {
   512:                 flrWavelength = ((DecimalSpinner) ce.getSource ()).getIntValue ();
   513:               }}),
   514:           Multilingual.mlnText (
   515:             ComponentFactory.createLabel (" Wavelength of flashing with palette"),
   516:             "ja", "パレットによる点滅の波長"),
   517:           Box.createHorizontalGlue ()),
   518:         //コントラストの上限
   519:         ComponentFactory.createHorizontalBox (
   520:           Box.createHorizontalStrut (20),
   521:           flrMenuLimit = ComponentFactory.createDecimalSpinner (
   522:             flrLimit, 0, 15, 1, 0,
   523:             new ChangeListener () {
   524:               @Override public void stateChanged (ChangeEvent ce) {
   525:                 flrLimit = ((DecimalSpinner) ce.getSource ()).getIntValue ();
   526:                 flrUpdateContrast ();
   527:               }}),
   528:           Multilingual.mlnText (
   529:             ComponentFactory.createLabel (" Upper limit of contrast"),
   530:             "ja", "コントラストの上限"),
   531:           Box.createHorizontalGlue ()),
   532:         //画面が明るくなるまでの時間
   533:         ComponentFactory.createHorizontalBox (
   534:           Box.createHorizontalStrut (20),
   535:           flrMenuDelay = ComponentFactory.createDecimalSpinner (
   536:             flrDelay, 1, 60, 1, 0,
   537:             new ChangeListener () {
   538:               @Override public void stateChanged (ChangeEvent ce) {
   539:                 flrDelay = ((DecimalSpinner) ce.getSource ()).getIntValue ();
   540:                 flrResetScreen = true;
   541:               }}),
   542:           Multilingual.mlnText (
   543:             ComponentFactory.createLabel (" Time until the screen brighten"),
   544:             "ja", " 画面が明るくなるまでの時間"),
   545:           Box.createHorizontalGlue ()),
   546:         //初期値に戻す
   547:         ComponentFactory.createHorizontalSeparator (),
   548:         Multilingual.mlnText (
   549:           ComponentFactory.createMenuItem (
   550:             "Reset to default",
   551:             new ActionListener () {
   552:               @Override public void actionPerformed (ActionEvent ae) {
   553:                 flrResetToDefault ();
   554:               }}),
   555:           "ja", "初期値に戻す"),
   556:         //デバッグ
   557:         !FLR_DEBUG ? null :
   558:         ComponentFactory.createHorizontalSeparator (),
   559:         !FLR_DEBUG ? null :
   560:         Multilingual.mlnText (
   561:           ComponentFactory.createMenu (
   562:             "Debug",
   563:             Multilingual.mlnText (
   564:               ComponentFactory.createCheckBoxMenuItem (
   565:                 flrDebugLocation, "Display locations",
   566:                 new ActionListener () {
   567:                   @Override public void actionPerformed (ActionEvent ae) {
   568:                     flrDebugLocation = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
   569:                   }}),
   570:               "ja", "観測点を表示する")
   571:             ),
   572:           "ja", "デバッグ")
   573:         ),
   574:       "ja", "点滅光の軽減");
   575:   }  //flrMakeMenu
   576: 
   577:   //flrResetToDefault ()
   578:   //  初期値に戻す
   579:   public static void flrResetToDefault () {
   580:     int result = JOptionPane.showConfirmDialog (
   581:       XEiJ.frmFrame,
   582:       Multilingual.mlnJapanese ?
   583:       "点滅光の軽減の設定を初期値に戻しますか?\n" +
   584:       "この機能は一旦無効になります。":
   585:       "Do you want to reset the flashing lights reduction settings to default?" +
   586:       "This feature will be temporarily disabled.",
   587:       Multilingual.mlnJapanese ? "点滅光の軽減" : "Flashing lights reduction",
   588:       JOptionPane.YES_NO_OPTION,
   589:       JOptionPane.PLAIN_MESSAGE);
   590:     if (result == JOptionPane.YES_OPTION) {
   591:       flrInitialize (FLR_ENABLED);
   592:     }
   593:   }  //flrResetToDefault
   594: 
   595:   //flrInitialize (enabled)
   596:   //  初期化する
   597:   public static void flrInitialize (boolean enabled) {
   598:     flrMenuEnabled.setSelected (flrRequestEnabled = enabled);
   599:     flrActive = flrEnabled || flrRequestEnabled;
   600:     flrMenuPalette.setSelected (flrRequestPalette = FLR_PALETTE);
   601:     flrMenuContrast.setSelected (flrContrast = FLR_CONTRAST);
   602:     flrMenuScreen.setSelected (flrRequestScreen = FLR_SCREEN);
   603:     flrMenuWavelength.setIntValue (flrWavelength = FLR_WAVELENGTH);
   604:     flrMenuLimit.setIntValue (flrLimit = FLR_LIMIT);
   605:     flrUpdateContrast ();
   606:     flrMenuDelay.setIntValue (flrDelay = FLR_DELAY);
   607:     flrInformed = false;
   608:   }  //flrInitialize
   609: 
   610: }  //class FlashingLights