GIFAnimation.java
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13: package xeij;
14:
15: import java.awt.*;
16: import java.awt.event.*;
17: import java.awt.image.*;
18: import java.io.*;
19: import java.util.*;
20: import javax.imageio.*;
21: import javax.imageio.metadata.*;
22: import javax.imageio.stream.*;
23: import javax.swing.*;
24: import javax.swing.event.*;
25: import org.w3c.dom.*;
26:
27: public class GIFAnimation {
28:
29:
30:
31:
32:
33:
34: public static final int GIF_WAITING_TIME_MIN = 0;
35: public static final int GIF_WAITING_TIME_MAX = 30;
36: public static final int GIF_RECORDING_TIME_MIN = 1;
37: public static final int GIF_RECORDING_TIME_MAX = 30;
38: public static final int GIF_MAGNIFICATION_MIN = 10;
39: public static final int GIF_MAGNIFICATION_MAX = 200;
40:
41: public static int gifWaitingTime;
42: public static int gifRecordingTime;
43: public static int gifMagnification;
44: public static Object gifInterpolation;
45:
46: public static SpinnerNumberModel gifWaitingTimeModel;
47: public static SpinnerNumberModel gifRecordingTimeModel;
48: public static SpinnerNumberModel gifMagnificationModel;
49:
50: public static JMenuItem gifStartRecordingMenuItem;
51: public static JMenu gifSettingsMenu;
52:
53: public static java.util.Timer gifTimer;
54:
55: public static int gifScreenWidth;
56: public static int gifScreenHeight;
57: public static int gifStretchWidth;
58: public static int gifStereoscopicFactor;
59: public static boolean gifStereoscopicOn;
60: public static int gifStereoscopicMethod;
61:
62: public static double gifDelayTime;
63: public static int gifWaitingFrames;
64: public static int gifRecordingFrames;
65: public static int[] gifBuffer;
66: public static int gifPointer;
67: public static int gifWaitingCounter;
68: public static int gifRecordingCounter;
69: public static boolean gifNowRecording;
70:
71:
72:
73: public static void gifInit () {
74:
75: gifWaitingTime = Math.max (GIF_WAITING_TIME_MIN, Math.min (GIF_WAITING_TIME_MAX, Settings.sgsGetInt ("gifwaitingtime")));
76: gifRecordingTime = Math.max (GIF_RECORDING_TIME_MIN, Math.min (GIF_RECORDING_TIME_MAX, Settings.sgsGetInt ("gifrecordingtime")));
77: gifMagnification = Math.max (GIF_MAGNIFICATION_MIN, Math.min (GIF_MAGNIFICATION_MAX, Settings.sgsGetInt ("gifmagnification")));
78: switch (Settings.sgsGetString ("gifinterpolation").toLowerCase ()) {
79: case "nearest":
80: gifInterpolation = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
81: break;
82: case "bilinear":
83: gifInterpolation = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
84: break;
85: case "bicubic":
86: gifInterpolation = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
87: break;
88: default:
89: gifInterpolation = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
90: }
91:
92: gifWaitingTimeModel = new SpinnerNumberModel (gifWaitingTime, GIF_WAITING_TIME_MIN, GIF_WAITING_TIME_MAX, 1);
93: gifRecordingTimeModel = new SpinnerNumberModel (gifRecordingTime, GIF_RECORDING_TIME_MIN, GIF_RECORDING_TIME_MAX, 1);
94: gifMagnificationModel = new SpinnerNumberModel (gifMagnification, GIF_MAGNIFICATION_MIN, GIF_MAGNIFICATION_MAX, 1);
95:
96: ButtonGroup interpolationGroup = new ButtonGroup ();
97:
98: gifStartRecordingMenuItem =
99: Multilingual.mlnText (
100: ComponentFactory.createMenuItem (
101: "Start recording",
102: new ActionListener () {
103: @Override public void actionPerformed (ActionEvent ae) {
104: gifStartRecording ();
105: }
106: }),
107: "ja", "録画開始");
108:
109: gifSettingsMenu =
110: Multilingual.mlnText (
111: ComponentFactory.createMenu (
112: "GIF animation recording settings",
113: ComponentFactory.createHorizontalBox (
114: Multilingual.mlnText (ComponentFactory.createLabel ("Waiting time"), "ja", "待ち時間"),
115: Box.createHorizontalGlue ()
116: ),
117: ComponentFactory.createHorizontalBox (
118: Box.createHorizontalStrut (20),
119: ComponentFactory.createNumberSpinner (gifWaitingTimeModel, 4, new ChangeListener () {
120: @Override public void stateChanged (ChangeEvent ce) {
121: gifWaitingTime = gifWaitingTimeModel.getNumber ().intValue ();
122: }
123: }),
124: Multilingual.mlnText (ComponentFactory.createLabel ("seconds"), "ja", "秒"),
125: Box.createHorizontalGlue ()
126: ),
127: ComponentFactory.createHorizontalBox (
128: Multilingual.mlnText (ComponentFactory.createLabel ("Recording time"), "ja", "録画時間"),
129: Box.createHorizontalGlue ()
130: ),
131: ComponentFactory.createHorizontalBox (
132: Box.createHorizontalStrut (20),
133: ComponentFactory.createNumberSpinner (gifRecordingTimeModel, 4, new ChangeListener () {
134: @Override public void stateChanged (ChangeEvent ce) {
135: gifRecordingTime = gifRecordingTimeModel.getNumber ().intValue ();
136: }
137: }),
138: Multilingual.mlnText (ComponentFactory.createLabel ("seconds"), "ja", "秒"),
139: Box.createHorizontalGlue ()
140: ),
141: ComponentFactory.createHorizontalBox (
142: Multilingual.mlnText (ComponentFactory.createLabel ("Magnification"), "ja", "倍率"),
143: Box.createHorizontalGlue ()
144: ),
145: ComponentFactory.createHorizontalBox (
146: Box.createHorizontalStrut (20),
147: ComponentFactory.createNumberSpinner (gifMagnificationModel, 4, new ChangeListener () {
148: @Override public void stateChanged (ChangeEvent ce) {
149: gifMagnification = gifMagnificationModel.getNumber ().intValue ();
150: }
151: }),
152: ComponentFactory.createLabel ("%"),
153: Box.createHorizontalGlue ()
154: ),
155: ComponentFactory.createHorizontalSeparator (),
156: Multilingual.mlnText (
157: ComponentFactory.createRadioButtonMenuItem (
158: interpolationGroup,
159: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR,
160: "Nearest neighbor",
161: new ActionListener () {
162: @Override public void actionPerformed (ActionEvent ae) {
163: gifInterpolation = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
164: }
165: }),
166: "ja", "最近傍補間"),
167: Multilingual.mlnText (
168: ComponentFactory.createRadioButtonMenuItem (
169: interpolationGroup,
170: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_BILINEAR,
171: "Bilinear",
172: new ActionListener () {
173: @Override public void actionPerformed (ActionEvent ae) {
174: gifInterpolation = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
175: }
176: }),
177: "ja", "線形補間"),
178: Multilingual.mlnText (
179: ComponentFactory.createRadioButtonMenuItem (
180: interpolationGroup,
181: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_BICUBIC,
182: "Bicubic",
183: new ActionListener () {
184: @Override public void actionPerformed (ActionEvent ae) {
185: gifInterpolation = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
186: }
187: }),
188: "ja", "三次補間")
189: ),
190: "ja", "GIF アニメーション録画設定");
191:
192: gifTimer = new java.util.Timer ();
193: }
194:
195:
196:
197: public static void gifTini () {
198: Settings.sgsPutInt ("gifwaitingtime", gifWaitingTime);
199: Settings.sgsPutInt ("gifrecordingtime", gifRecordingTime);
200: Settings.sgsPutInt ("gifmagnification", gifMagnification);
201: Settings.sgsPutString ("gifinterpolation",
202: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ? "nearest" :
203: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_BILINEAR ? "bilinear" :
204: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_BICUBIC ? "bicubic" :
205: "bilinear");
206: }
207:
208:
209:
210: public static void gifStartRecording () {
211: if (gifNowRecording) {
212: return;
213: }
214:
215: gifScreenWidth = XEiJ.pnlScreenWidth;
216: gifScreenHeight = XEiJ.pnlScreenHeight;
217: gifStretchWidth = XEiJ.pnlStretchWidth;
218: gifStereoscopicFactor = XEiJ.pnlStereoscopicFactor;
219: gifStereoscopicOn = XEiJ.pnlStereoscopicOn;
220: gifStereoscopicMethod = XEiJ.pnlStereoscopicMethod;
221:
222: int htotal = CRTC.crtR00HFrontEndCurr + 1;
223: int vtotal = CRTC.crtR04VFrontEndCurr + 1;
224: if (htotal <= 0 || vtotal <= 0) {
225: return;
226: }
227: int k = CRTC.crtHRLCurr << 3 | CRTC.crtHighResoCurr << 2 | CRTC.crtHResoCurr;
228: double osc = (double) CRTC.crtFreqs[CRTC.CRT_OSCS[k]];
229: int ratio = CRTC.CRT_DIVS[k];
230: double hfreq = osc / (ratio * htotal << 3);
231: double vfreq = hfreq / vtotal;
232: gifDelayTime = 100.0 / vfreq;
233:
234: gifWaitingFrames = (int) Math.floor (vfreq * (double) gifWaitingTime + 0.5);
235: gifRecordingFrames = (int) Math.floor (vfreq * (double) gifRecordingTime + 0.5);
236: int fullSize = gifScreenWidth * gifScreenHeight * gifStereoscopicFactor;
237: int maxNumberOfFrames = 0x7fffffff / fullSize;
238: if (maxNumberOfFrames < gifRecordingFrames) {
239: return;
240: }
241:
242: int bufferSize = fullSize * gifRecordingFrames;
243: try {
244: gifBuffer = new int[bufferSize];
245: } catch (OutOfMemoryError oome) {
246: oome.printStackTrace ();
247: return;
248: }
249:
250: gifStartRecordingMenuItem.setEnabled (false);
251: gifPointer = 0;
252: gifWaitingCounter = 0;
253: gifRecordingCounter = 0;
254: gifNowRecording = true;
255: CRTC.crtCaptureClock = XEiJ.mpuClockTime;
256: CRTC.crtFrameTaskClock = Math.min (CRTC.crtContrastClock, CRTC.crtCaptureClock);
257: }
258:
259:
260:
261: public static void gifCaptureFrame () {
262: if (gifWaitingCounter < gifWaitingFrames) {
263: gifWaitingCounter++;
264: return;
265: }
266:
267: for (int y = 0; y < gifScreenHeight; y++) {
268: System.arraycopy (XEiJ.pnlBMLeft, XEiJ.PNL_BM_WIDTH * y,
269: gifBuffer, gifPointer,
270: gifScreenWidth);
271: gifPointer += gifScreenWidth;
272: }
273: if (gifStereoscopicFactor == 2) {
274: for (int y = 0; y < gifScreenHeight; y++) {
275: System.arraycopy (XEiJ.pnlBMRight, XEiJ.PNL_BM_WIDTH * y,
276: gifBuffer, gifPointer,
277: gifScreenWidth);
278: gifPointer += gifScreenWidth;
279: }
280: }
281: gifRecordingCounter++;
282: if (gifRecordingCounter == gifRecordingFrames) {
283:
284: CRTC.crtCaptureClock = XEiJ.FAR_FUTURE;
285: CRTC.crtFrameTaskClock = Math.min (CRTC.crtContrastClock, CRTC.crtCaptureClock);
286:
287: gifTimer.schedule (new TimerTask () {
288: @Override public void run () {
289: gifOutput ();
290: }
291: }, 0L);
292: }
293: }
294:
295:
296:
297: public static void gifOutput () {
298: System.out.println (Multilingual.mlnJapanese ? "画像を圧縮しています" : "Compressing images");
299:
300: double zoomRatio = (double) gifMagnification / 100.0;
301: int zoomWidth = (int) Math.floor ((double) gifStretchWidth * zoomRatio + 0.5);
302: int zoomHeight = (int) Math.floor ((double) gifScreenHeight * zoomRatio + 0.5);
303:
304: BufferedImage imageLeft = new BufferedImage (XEiJ.PNL_BM_WIDTH, gifScreenHeight, BufferedImage.TYPE_INT_RGB);
305: BufferedImage imageRight = new BufferedImage (XEiJ.PNL_BM_WIDTH, gifScreenHeight, BufferedImage.TYPE_INT_RGB);
306: int[] bmLeft = ((DataBufferInt) imageLeft.getRaster ().getDataBuffer ()).getData ();
307: int[] bmRight = ((DataBufferInt) imageRight.getRaster ().getDataBuffer ()).getData ();
308:
309: BufferedImage image = new BufferedImage (zoomWidth * gifStereoscopicFactor, zoomHeight, BufferedImage.TYPE_INT_RGB);
310: Graphics2D g2 = image.createGraphics ();
311: g2.setRenderingHint (RenderingHints.KEY_INTERPOLATION, gifInterpolation);
312:
313: ImageWriter imageWriter = ImageIO.getImageWritersBySuffix ("gif").next ();
314: ImageWriteParam writeParam = imageWriter.getDefaultWriteParam ();
315: try {
316:
317: String dirName = "capture";
318: File dir = new File (dirName);
319: if (dir.exists () ? !dir.isDirectory () : !dir.mkdir ()) {
320: gifBuffer = null;
321: gifNowRecording = false;
322: gifStartRecordingMenuItem.setEnabled (true);
323: return;
324: }
325: HashSet<String> nameSet = new HashSet<String> ();
326: for (String name : dir.list ()) {
327: nameSet.add (name);
328: }
329: int number = 0;
330: String name;
331: do {
332: number++;
333: name = number + ".gif";
334: } while (!nameSet.add (name));
335: name = dirName + "/" + name;
336:
337: File file = new File (name);
338: if (file.exists ()) {
339: file.delete ();
340: }
341: ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream (file);
342: imageWriter.setOutput (imageOutputStream);
343: imageWriter.prepareWriteSequence (null);
344:
345: int halfSize = gifScreenWidth * gifScreenHeight;
346: int fullSize = halfSize * gifStereoscopicFactor;
347: for (int counter = 0; counter < gifRecordingFrames; ) {
348: int pointer = fullSize * counter;
349:
350: int span = 1;
351: while (counter + span < gifRecordingFrames &&
352: Arrays.equals (gifBuffer, pointer, pointer + fullSize,
353: gifBuffer, pointer + fullSize * span, pointer + fullSize * (span + 1))) {
354: span++;
355: }
356: counter += span;
357: IIOMetadata metadata = makeMetadata (imageWriter,
358: writeParam,
359: image,
360: String.valueOf ((int) Math.floor (gifDelayTime * (double) span + 0.5)));
361:
362: for (int y = 0; y < gifScreenHeight; y++) {
363: System.arraycopy (gifBuffer, pointer,
364: bmLeft, XEiJ.PNL_BM_WIDTH * y,
365: gifScreenWidth);
366: pointer += gifScreenWidth;
367: }
368: if (gifStereoscopicFactor == 2) {
369: for (int y = 0; y < gifScreenHeight; y++) {
370: System.arraycopy (gifBuffer, pointer,
371: bmRight, XEiJ.PNL_BM_WIDTH * y,
372: gifScreenWidth);
373: pointer += gifScreenWidth;
374: }
375: }
376:
377: g2.setColor (Color.black);
378: g2.fillRect (0, 0, zoomWidth * gifStereoscopicFactor, zoomHeight);
379: if (XEiJ.PNL_STEREOSCOPIC_ON && gifStereoscopicOn) {
380: if (gifStereoscopicMethod == XEiJ.PNL_NAKED_EYE_CROSSING) {
381: g2.drawImage (imageRight,
382: 0, 0, zoomWidth, zoomHeight,
383: 0, 0, gifScreenWidth, gifScreenHeight,
384: null);
385: g2.drawImage (imageLeft,
386: zoomWidth, 0, zoomWidth * 2, zoomHeight,
387: 0, 0, gifScreenWidth, gifScreenHeight,
388: null);
389: } else if (gifStereoscopicMethod == XEiJ.PNL_NAKED_EYE_PARALLEL ||
390: gifStereoscopicMethod == XEiJ.PNL_SIDE_BY_SIDE) {
391: g2.drawImage (imageLeft,
392: 0, 0, zoomWidth, zoomHeight,
393: 0, 0, gifScreenWidth, gifScreenHeight,
394: null);
395: g2.drawImage (imageRight,
396: zoomWidth, 0, zoomWidth * 2, zoomHeight,
397: 0, 0, gifScreenWidth, gifScreenHeight,
398: null);
399: } else {
400: g2.drawImage (imageLeft,
401: 0, 0, zoomWidth, zoomHeight >> 1,
402: 0, 0, gifScreenWidth, gifScreenHeight,
403: null);
404: g2.drawImage (imageRight,
405: 0, zoomHeight >> 1, zoomWidth, zoomHeight,
406: 0, 0, gifScreenWidth, gifScreenHeight,
407: null);
408: }
409: } else {
410: g2.drawImage (imageLeft,
411: 0, 0, zoomWidth, zoomHeight,
412: 0, 0, gifScreenWidth, gifScreenHeight,
413: null);
414: }
415:
416: imageWriter.writeToSequence (new IIOImage (image, null, metadata), writeParam);
417: }
418:
419: imageWriter.endWriteSequence ();
420: imageOutputStream.close ();
421: System.out.println (Multilingual.mlnJapanese ? name + " を更新しました" : name + " was updated");
422: } catch (IOException ioe) {
423: ioe.printStackTrace ();
424: }
425: gifBuffer = null;
426: gifNowRecording = false;
427: gifStartRecordingMenuItem.setEnabled (true);
428: }
429:
430: public static IIOMetadata makeMetadata (ImageWriter imageWriter, ImageWriteParam writeParam, BufferedImage image, String delayTime) {
431: IIOMetadata metadata = imageWriter.getDefaultImageMetadata (new ImageTypeSpecifier (image), writeParam);
432: String metaFormat = metadata.getNativeMetadataFormatName ();
433: Node root = metadata.getAsTree (metaFormat);
434: IIOMetadataNode gce = new IIOMetadataNode ("GraphicControlExtension");
435: gce.setAttribute ("delayTime", delayTime);
436: gce.setAttribute ("disposalMethod", "none");
437: gce.setAttribute ("transparentColorFlag", "FALSE");
438: gce.setAttribute ("transparentColorIndex", "0");
439: gce.setAttribute ("userInputFlag", "FALSE");
440: root.appendChild (gce);
441: try {
442: metadata.setFromTree (metaFormat, root);
443: } catch (IIOInvalidTreeException ite) {
444: ite.printStackTrace ();
445: }
446: return metadata;
447: }
448:
449: }