/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
// TODO: Move this class to KetGUI or similar.
package ket.treeDiff;
import java.io.*;
import java.util.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.Graphics2D;
import ket.math.*;
import geom.Offset;
import geom.Position;
import java.awt.Color;
import ket.display.box.Box;
import ketUI.panel.KetPanel;
import ket.display.HTMLTools;
import ket.display.Transition;
import ket.display.ImageTools;
import ket.display.ColourScheme;
import ket.display.ColourSchemeDecorator;
import ketUI.Ket;
/**
* This paints the animated transition from one argument to another and saves
* each frame to file. It is run as a separate thread as once created is
* independent of the rest of the Ket GUI's behaviour.
*/
public class Painter extends Thread {
public static final int START_NUMBER = 5;
public static final int TRANSITION_NUMBER = 15;
public static final int END_NUMBER = 5;
public static final int FRAME_NUMBER = START_NUMBER + TRANSITION_NUMBER + END_NUMBER;
public static final int PAUSE_TIME = 10; // milliseconds
static final int WIDTH = 800;
static final int HEIGHT = 600;
static final int FONT_SIZE = Box.DEFAULT_BOX_FONT_SIZE;
static final ColourScheme COLOUR_SCHEME = ColourScheme.WHITEBOARD;
int initialFrameNumber;
TreeDiff treeDiff;
String filename;
BufferedImage image;
Graphics2D g2D;
public Painter(Argument before, Argument after, String filename, int initialFrameNumber) {
Argument beforeClone = Argument.cloneArgument(before.getRoot());
Argument afterClone = Argument.cloneArgument(after.getRoot());
treeDiff = new TreeDiff(beforeClone, afterClone);
this.filename = filename;
this.initialFrameNumber = initialFrameNumber;
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g2D = (Graphics2D) image.getGraphics();
setDaemon(false); // Don't exit until all animations finish.
}
/**
* Differences between two arguments may be animated and saved to file.
* To separate this process from the running of the GUI, this can be
* run as a separate daemon process; it is run effectively as a
* separate process, terminating once FRAME_NUMBER of frames has been
* generated to demonstrate the animation.
*/
@Override
public void run() {
try {
paintAndSaveFrames();
Ket.out.println("[done animating]");
} catch (InterruptedException ie) {
Ket.out.println(" !!! A thread responsible for saving frames was interupted !!! ");
}
}
private Offset getWindowWithoutLabel(Box afterRootBox) {
double minimumHeight = afterRootBox.getInnerRectangle().height;
double shapeWidth = WIDTH - KetPanel.BORDER_OFFSET.width;
return new Offset(shapeWidth, minimumHeight);
}
private void paintAndSaveFrames() throws InterruptedException {
Box afterRootBox = treeDiff.getAfter().toBox(0L, COLOUR_SCHEME);
afterRootBox.setupInnerRectangle(FONT_SIZE);
Offset windowWithoutLabel = getWindowWithoutLabel(afterRootBox);
Box beforeRootBox = getBeforeRootBox(windowWithoutLabel);
afterRootBox.setupOuterRectangle(windowWithoutLabel);
afterRootBox.calcRootOffset();
int animationStart = initialFrameNumber + START_NUMBER;
int animationEnd = animationStart + TRANSITION_NUMBER;
Position topLeft = calcTopLeft(afterRootBox);
Vector<Transition> transitions = treeDiff.getTransitions(beforeRootBox, afterRootBox); // Done once!
for (int frame=initialFrameNumber; frame<animationEnd+END_NUMBER; frame++) {
g2D.setColor(COLOUR_SCHEME.getBackgroundColour());
g2D.fillRect(0, 0, WIDTH, HEIGHT);
if (frame<animationStart) { // Draw the 'before' box.
beforeRootBox.paintAt(g2D, topLeft, COLOUR_SCHEME);
} else if (frame>=animationEnd) { // Draw the 'after' box.
afterRootBox.paintAt(g2D, topLeft, COLOUR_SCHEME);
} else { // Animate between 'before' and 'after' boxes.
double fractionalTime = frame-animationStart;
fractionalTime /= animationEnd - animationStart - 1.0;
for (Transition t : transitions) {
t.animate(g2D, COLOUR_SCHEME, fractionalTime, topLeft);
}
}
ImageTools.saveImage(image, getImageName(frame));
Thread.sleep(PAUSE_TIME);
}
}
private Box getBeforeRootBox(Offset windowWithoutLabel) {
Box beforeRootBox = treeDiff.getBefore().toBox(0L, COLOUR_SCHEME);
beforeRootBox.setupInnerRectangle(FONT_SIZE);
beforeRootBox.setupOuterRectangle(windowWithoutLabel);
beforeRootBox.calcRootOffset();
return beforeRootBox;
}
// Move this method and use it in chordEventHandler where you only save one image.
private String getImageName(int frame) {
String imageName = filename.replace("\\.png$", "");
return String.format(imageName+"%05d.png", frame);
}
private Position calcTopLeft(Box afterRootBox) {
double centre = (HEIGHT-KetPanel.BORDER_OFFSET.height) / 2.0;
double heightAboveCentre = afterRootBox.getOuterRectangle().height / 2.0;
double boxHeight = centre - heightAboveCentre;
return new Position(KetPanel.LEFT_BORDER_SIZE, boxHeight);
}
}