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