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: gifTimer.cancel ();
200: Settings.sgsPutInt ("gifwaitingtime", gifWaitingTime);
201: Settings.sgsPutInt ("gifrecordingtime", gifRecordingTime);
202: Settings.sgsPutInt ("gifmagnification", gifMagnification);
203: Settings.sgsPutString ("gifinterpolation",
204: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ? "nearest" :
205: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_BILINEAR ? "bilinear" :
206: gifInterpolation == RenderingHints.VALUE_INTERPOLATION_BICUBIC ? "bicubic" :
207: "bilinear");
208: }
209:
210:
211:
212: public static void gifStartRecording () {
213: if (gifNowRecording) {
214: return;
215: }
216:
217: gifScreenWidth = XEiJ.pnlScreenWidth;
218: gifScreenHeight = XEiJ.pnlScreenHeight;
219: gifStretchWidth = XEiJ.pnlStretchWidth;
220: gifStretchHeight = XEiJ.pnlStretchHeight;
221: gifStereoscopicFactor = XEiJ.pnlStereoscopicFactor;
222: gifStereoscopicOn = XEiJ.pnlStereoscopicOn;
223: gifStereoscopicMethod = XEiJ.pnlStereoscopicMethod;
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238: gifDelayTime = Math.max (2.0, (double) CRTC.crtTotalLength * 1e-1 + (double) CRTC.crtTotalLengthMNP * 1e-10);
239:
240:
241:
242:
243:
244: gifWaitingFrames = (int) Math.floor ((double) gifWaitingTime * 100.0 / gifDelayTime + 0.5);
245: gifRecordingFrames = (int) Math.floor ((double) gifRecordingTime * 100.0 / gifDelayTime + 0.5);
246:
247: int fullSize = gifScreenWidth * gifScreenHeight * gifStereoscopicFactor;
248: int maxNumberOfFrames = 0x7fffffff / fullSize;
249: if (maxNumberOfFrames < gifRecordingFrames) {
250: return;
251: }
252:
253: int bufferSize = fullSize * gifRecordingFrames;
254: try {
255: gifBuffer = new int[bufferSize];
256: } catch (OutOfMemoryError oome) {
257: oome.printStackTrace ();
258: return;
259: }
260:
261: gifStartRecordingMenuItem.setEnabled (false);
262: gifPointer = 0;
263: gifWaitingCounter = 0;
264: gifRecordingCounter = 0;
265: gifNowRecording = true;
266: CRTC.crtCaptureClock = XEiJ.mpuClockTime;
267: CRTC.crtFrameTaskClock = Math.min (CRTC.crtContrastClock, CRTC.crtCaptureClock);
268: }
269:
270:
271:
272: public static void gifCaptureFrame () {
273: if (gifWaitingCounter < gifWaitingFrames) {
274: gifWaitingCounter++;
275: return;
276: }
277:
278: if (XEiJ.PNL_USE_THREAD) {
279: int[] bitmap = XEiJ.pnlBMLeftArray[XEiJ.pnlBMWrite & 3];
280: for (int y = 0; y < gifScreenHeight; y++) {
281: System.arraycopy (bitmap, XEiJ.PNL_BM_WIDTH * y,
282: gifBuffer, gifPointer,
283: gifScreenWidth);
284: gifPointer += gifScreenWidth;
285: }
286: if (gifStereoscopicFactor == 2) {
287: bitmap = XEiJ.pnlBMRightArray[XEiJ.pnlBMWrite & 3];
288: for (int y = 0; y < gifScreenHeight; y++) {
289: System.arraycopy (bitmap, XEiJ.PNL_BM_WIDTH * y,
290: gifBuffer, gifPointer,
291: gifScreenWidth);
292: gifPointer += gifScreenWidth;
293: }
294: }
295: } else {
296: for (int y = 0; y < gifScreenHeight; y++) {
297: System.arraycopy (XEiJ.pnlBMLeft, XEiJ.PNL_BM_WIDTH * y,
298: gifBuffer, gifPointer,
299: gifScreenWidth);
300: gifPointer += gifScreenWidth;
301: }
302: if (gifStereoscopicFactor == 2) {
303: for (int y = 0; y < gifScreenHeight; y++) {
304: System.arraycopy (XEiJ.pnlBMRight, XEiJ.PNL_BM_WIDTH * y,
305: gifBuffer, gifPointer,
306: gifScreenWidth);
307: gifPointer += gifScreenWidth;
308: }
309: }
310: }
311: gifRecordingCounter++;
312: if (gifRecordingCounter == gifRecordingFrames) {
313:
314: CRTC.crtCaptureClock = XEiJ.FAR_FUTURE;
315: CRTC.crtFrameTaskClock = Math.min (CRTC.crtContrastClock, CRTC.crtCaptureClock);
316:
317: gifTimer.schedule (new TimerTask () {
318: @Override public void run () {
319: gifOutput ();
320: }
321: }, 0L);
322: }
323: }
324:
325:
326:
327: public static void gifOutput () {
328: System.out.println (Multilingual.mlnJapanese ? "画像を圧縮しています" : "Compressing images");
329:
330: double zoomRatio = (double) gifMagnification / 100.0;
331: int zoomWidth = (int) Math.floor ((double) gifStretchWidth * zoomRatio + 0.5);
332: int zoomHeight = (int) Math.floor ((double) gifStretchHeight * zoomRatio + 0.5);
333:
334: BufferedImage imageLeft = new BufferedImage (XEiJ.PNL_BM_WIDTH, gifScreenHeight, BufferedImage.TYPE_INT_RGB);
335: BufferedImage imageRight = new BufferedImage (XEiJ.PNL_BM_WIDTH, gifScreenHeight, BufferedImage.TYPE_INT_RGB);
336: int[] bmLeft = ((DataBufferInt) imageLeft.getRaster ().getDataBuffer ()).getData ();
337: int[] bmRight = ((DataBufferInt) imageRight.getRaster ().getDataBuffer ()).getData ();
338:
339: BufferedImage image = new BufferedImage (zoomWidth * gifStereoscopicFactor, zoomHeight, BufferedImage.TYPE_INT_RGB);
340: Graphics2D g2 = image.createGraphics ();
341: g2.setRenderingHint (RenderingHints.KEY_INTERPOLATION, gifInterpolation);
342:
343: ImageWriter imageWriter = ImageIO.getImageWritersBySuffix ("gif").next ();
344: ImageWriteParam writeParam = imageWriter.getDefaultWriteParam ();
345: try {
346:
347: String dirName = "capture";
348: File dir = new File (dirName);
349: if (dir.exists () ? !dir.isDirectory () : !dir.mkdir ()) {
350: gifBuffer = null;
351: gifNowRecording = false;
352: gifStartRecordingMenuItem.setEnabled (true);
353: return;
354: }
355: HashSet<String> nameSet = new HashSet<String> ();
356: for (String name : dir.list ()) {
357: nameSet.add (name);
358: }
359: int number = 0;
360: String name;
361: do {
362: number++;
363: name = number + ".gif";
364: } while (!nameSet.add (name));
365: name = dirName + "/" + name;
366:
367: File file = new File (name);
368: if (file.exists ()) {
369: file.delete ();
370: }
371: ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream (file);
372: imageWriter.setOutput (imageOutputStream);
373: imageWriter.prepareWriteSequence (null);
374:
375: int halfSize = gifScreenWidth * gifScreenHeight;
376: int fullSize = halfSize * gifStereoscopicFactor;
377:
378: double lastTime = 0.0;
379: double lastIntTime = 0.0;
380:
381: for (int counter = 0; counter < gifRecordingFrames; ) {
382: int pointer = fullSize * counter;
383:
384: int span = 1;
385: while (counter + span < gifRecordingFrames &&
386: Arrays.equals (gifBuffer, pointer, pointer + fullSize,
387: gifBuffer, pointer + fullSize * span, pointer + fullSize * (span + 1))) {
388: span++;
389: }
390: counter += span;
391:
392: double time = lastTime + gifDelayTime * (double) span;
393: double intTime = Math.floor (time + 0.5);
394:
395: IIOMetadata metadata = makeMetadata (imageWriter,
396: writeParam,
397: image,
398:
399:
400:
401: String.valueOf ((int) (intTime - lastIntTime)));
402: lastTime = time;
403: lastIntTime = intTime;
404:
405:
406: for (int y = 0; y < gifScreenHeight; y++) {
407: System.arraycopy (gifBuffer, pointer,
408: bmLeft, XEiJ.PNL_BM_WIDTH * y,
409: gifScreenWidth);
410: pointer += gifScreenWidth;
411: }
412: if (gifStereoscopicFactor == 2) {
413: for (int y = 0; y < gifScreenHeight; y++) {
414: System.arraycopy (gifBuffer, pointer,
415: bmRight, XEiJ.PNL_BM_WIDTH * y,
416: gifScreenWidth);
417: pointer += gifScreenWidth;
418: }
419: }
420:
421: g2.setColor (Color.black);
422: g2.fillRect (0, 0, zoomWidth * gifStereoscopicFactor, zoomHeight);
423: if (XEiJ.PNL_STEREOSCOPIC_ON && gifStereoscopicOn) {
424: if (gifStereoscopicMethod == XEiJ.PNL_NAKED_EYE_CROSSING) {
425: g2.drawImage (imageRight,
426: 0, 0, zoomWidth, zoomHeight,
427: 0, 0, gifScreenWidth, gifScreenHeight,
428: null);
429: g2.drawImage (imageLeft,
430: zoomWidth, 0, zoomWidth * 2, zoomHeight,
431: 0, 0, gifScreenWidth, gifScreenHeight,
432: null);
433: } else if (gifStereoscopicMethod == XEiJ.PNL_NAKED_EYE_PARALLEL ||
434: gifStereoscopicMethod == XEiJ.PNL_SIDE_BY_SIDE) {
435: g2.drawImage (imageLeft,
436: 0, 0, zoomWidth, zoomHeight,
437: 0, 0, gifScreenWidth, gifScreenHeight,
438: null);
439: g2.drawImage (imageRight,
440: zoomWidth, 0, zoomWidth * 2, zoomHeight,
441: 0, 0, gifScreenWidth, gifScreenHeight,
442: null);
443: } else {
444: g2.drawImage (imageLeft,
445: 0, 0, zoomWidth, zoomHeight >> 1,
446: 0, 0, gifScreenWidth, gifScreenHeight,
447: null);
448: g2.drawImage (imageRight,
449: 0, zoomHeight >> 1, zoomWidth, zoomHeight,
450: 0, 0, gifScreenWidth, gifScreenHeight,
451: null);
452: }
453: } else {
454: g2.drawImage (imageLeft,
455: 0, 0, zoomWidth, zoomHeight,
456: 0, 0, gifScreenWidth, gifScreenHeight,
457: null);
458: }
459:
460: imageWriter.writeToSequence (new IIOImage (image, null, metadata), writeParam);
461: }
462:
463: imageWriter.endWriteSequence ();
464: imageOutputStream.close ();
465: System.out.println (Multilingual.mlnJapanese ? name + " を更新しました" : name + " was updated");
466: } catch (IOException ioe) {
467: ioe.printStackTrace ();
468: }
469: gifBuffer = null;
470: gifNowRecording = false;
471: gifStartRecordingMenuItem.setEnabled (true);
472: }
473:
474: public static IIOMetadata makeMetadata (ImageWriter imageWriter, ImageWriteParam writeParam, BufferedImage image, String delayTime) {
475: IIOMetadata metadata = imageWriter.getDefaultImageMetadata (new ImageTypeSpecifier (image), writeParam);
476: String metaFormat = metadata.getNativeMetadataFormatName ();
477: Node root = metadata.getAsTree (metaFormat);
478: IIOMetadataNode gce = new IIOMetadataNode ("GraphicControlExtension");
479: gce.setAttribute ("delayTime", delayTime);
480: gce.setAttribute ("disposalMethod", "none");
481: gce.setAttribute ("transparentColorFlag", "FALSE");
482: gce.setAttribute ("transparentColorIndex", "0");
483: gce.setAttribute ("userInputFlag", "FALSE");
484: root.appendChild (gce);
485: try {
486: metadata.setFromTree (metaFormat, root);
487: } catch (IIOInvalidTreeException ite) {
488: ite.printStackTrace ();
489: }
490: return metadata;
491: }
492:
493: }