/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2014 Andreas Maschke
This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This software 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this software;
if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jwildfire.create.tina.animate;
import java.util.ArrayList;
import java.util.List;
import org.jwildfire.base.Prefs;
import org.jwildfire.base.Tools;
import org.jwildfire.create.tina.base.Flame;
import org.jwildfire.create.tina.io.FlameWriter;
import org.jwildfire.create.tina.render.FlameRenderer;
import org.jwildfire.create.tina.render.RenderInfo;
import org.jwildfire.create.tina.render.RenderMode;
import org.jwildfire.create.tina.render.RenderedFlame;
import org.jwildfire.image.Pixel;
import org.jwildfire.image.SimpleImage;
import org.jwildfire.io.ImageWriter;
public class SWFAnimationRenderThread implements Runnable {
private final SWFAnimationRenderThreadController controller;
private final String outputFilename;
private boolean cancelSignalled;
private FlameMovie flameMovie;
private Throwable lastError;
private final List<SimpleImage> renderedImages = new ArrayList<SimpleImage>();
public SWFAnimationRenderThread(SWFAnimationRenderThreadController pController, FlameMovie pAnimation, String pOutputFilename) {
controller = pController;
flameMovie = pAnimation;
outputFilename = pOutputFilename;
}
@Override
public void run() {
try {
try {
cancelSignalled = false;
lastError = null;
renderedImages.clear();
controller.getProgressUpdater().initProgress(flameMovie.getFrameCount());
int startFrame = 1;
int endFrame = flameMovie.getFrameCount();
for (int i = startFrame; i <= endFrame; i++) {
if (cancelSignalled) {
break;
}
Flame currFlame = createFlame(i);
processFlame(currFlame, i);
controller.getProgressUpdater().updateProgress(i);
}
finishSequence();
}
catch (Throwable ex) {
lastError = ex;
throw new RuntimeException(ex);
}
}
finally {
controller.onRenderFinished();
}
}
private void finishSequence() throws Exception {
switch (flameMovie.getSequenceOutputType()) {
case ANB:
createANB();
break;
default: // nothing to do
break;
}
}
private void createANB() throws Exception {
int width = renderedImages.get(0).getImageWidth();
int height = renderedImages.get(0).getImageHeight();
int frameCount = renderedImages.size();
int size = (width + 1) * (height + 1);
int totalSize = 16 + 20 + 4 * frameCount * size; // header + (r + g + b + alpha) * frameCount
int direction = 1;
int endBehaviour = 0;
int step = 16;
int reserved1 = 0;
int reserved2 = 0;
byte buffer[] = new byte[totalSize];
int offset = 0;
buffer[offset++] = 'A';
buffer[offset++] = 'N';
buffer[offset++] = 'B';
buffer[offset++] = 'R';
buffer[offset++] = (byte) (width );
buffer[offset++] = (byte) (width >> 8);
buffer[offset++] = (byte) (width >> 16);
buffer[offset++] = (byte) (width >> 24);
buffer[offset++] = (byte) (height);
buffer[offset++] = (byte) (height >> 8);
buffer[offset++] = (byte) (height >> 16);
buffer[offset++] = (byte) (height >> 24);
buffer[offset++] = (byte) ((frameCount - 1));
buffer[offset++] = (byte) ((frameCount - 1) >> 8);
buffer[offset++] = (byte) ((frameCount - 1) >> 16);
buffer[offset++] = (byte) ((frameCount - 1) >> 24);
buffer[offset++] = (byte) (direction);
buffer[offset++] = (byte) (direction >> 8);
buffer[offset++] = (byte) (direction >> 16);
buffer[offset++] = (byte) (direction >> 24);
buffer[offset++] = (byte) (endBehaviour);
buffer[offset++] = (byte) (endBehaviour >> 8);
buffer[offset++] = (byte) (endBehaviour >> 16);
buffer[offset++] = (byte) (endBehaviour >> 24);
buffer[offset++] = (byte) (step);
buffer[offset++] = (byte) (step >> 8);
buffer[offset++] = (byte) (step >> 16);
buffer[offset++] = (byte) (step >> 24);
buffer[offset++] = (byte) (reserved1);
buffer[offset++] = (byte) (reserved1 >> 8);
buffer[offset++] = (byte) (reserved1 >> 16);
buffer[offset++] = (byte) (reserved1 >> 24);
buffer[offset++] = (byte) (reserved2);
buffer[offset++] = (byte) (reserved2 >> 8);
buffer[offset++] = (byte) (reserved2 >> 16);
buffer[offset++] = (byte) (reserved2 >> 24);
for (int channel = 0; channel < 4; channel++) {
for (int i = 0; i < frameCount; i++) {
fillBuffer(buffer, offset, renderedImages.get(i), channel);
offset += size;
}
}
String filename = outputFilename;
if (!filename.endsWith(Tools.FILEEXT_ANB)) {
filename = filename + "." + Tools.FILEEXT_ANB;
}
Tools.writeFile(filename, buffer);
}
private void fillBuffer(byte[] pBuffer, int pOffset, SimpleImage pImage, int pChannel) {
Pixel p = new Pixel();
int offset = pOffset;
int lineWidth = pImage.getImageWidth() + 1;
for (int i = 0; i < pImage.getImageHeight(); i++) {
for (int j = 0; j < pImage.getImageWidth(); j++) {
p.setARGBValue(pImage.getARGBValue(j, i));
switch (pChannel) {
case 0:
pBuffer[offset + j] = (byte) p.r;
break;
case 1:
pBuffer[offset + j] = (byte) p.g;
break;
case 2:
pBuffer[offset + j] = (byte) p.b;
break;
default:
pBuffer[offset + j] = (byte) p.a;
break;
}
}
offset += lineWidth;
}
}
private void processFlame(Flame pCurrFlame, int pFrame) throws Exception {
switch (flameMovie.getSequenceOutputType()) {
case FLAMES:
saveFlame(pCurrFlame, pFrame);
break;
case PNG_IMAGES:
saveImage(renderFlame(pCurrFlame), pFrame);
break;
case ANB:
pCurrFlame.setBGTransparency(true);
renderedImages.add(renderFlame(pCurrFlame));
break;
}
}
private void saveImage(SimpleImage pImage, int pFrame) throws Exception {
String filename = generateFilename(pFrame, Tools.FILEEXT_PNG);
new ImageWriter().saveAsPNG(pImage, filename);
}
private SimpleImage renderFlame(Flame pFlame) {
RenderInfo info = new RenderInfo(flameMovie.getFrameWidth(), flameMovie.getFrameHeight(), RenderMode.PRODUCTION);
double wScl = (double) info.getImageWidth() / (double) pFlame.getWidth();
double hScl = (double) info.getImageHeight() / (double) pFlame.getHeight();
pFlame.setPixelsPerUnit((wScl + hScl) * 0.5 * pFlame.getPixelsPerUnit());
pFlame.setWidth(info.getImageWidth());
pFlame.setHeight(info.getImageHeight());
FlameRenderer renderer = new FlameRenderer(pFlame, Prefs.getPrefs(), pFlame.isBGTransparency(), false);
renderer.setProgressUpdater(null);
pFlame.setSampleDensity(flameMovie.getQuality());
RenderedFlame res = renderer.renderFlame(info);
return res.getImage();
}
private Flame createFlame(int pFrame) throws Exception {
Flame flame1 = flameMovie.getFlame(pFrame);
Flame res = flameMovie.createAnimatedFlame(flame1, pFrame);
return res;
}
private void saveFlame(Flame pFlame, int pFrame) throws Exception {
String filename = generateFilename(pFrame, Tools.FILEEXT_FLAME);
new FlameWriter().writeFlame(pFlame, filename);
}
private String generateFilename(int pFrame, String pFileExt) {
String filename = outputFilename;
{
int pSlash = filename.lastIndexOf("/");
int pSlash2 = filename.lastIndexOf("\\");
if (pSlash2 > pSlash) {
pSlash = pSlash2;
}
int pDot = filename.lastIndexOf(".");
if (pDot > pSlash) {
filename = filename.substring(0, pDot);
}
}
String hs = String.valueOf(pFrame);
int length = calcFrameNumberLength();
while (hs.length() < length) {
hs = "0" + hs;
}
filename += hs + "." + pFileExt;
return filename;
}
private int calcFrameNumberLength() {
return Math.max(4, String.valueOf(flameMovie.getFrameCount()).length());
}
public void setCancelSignalled(boolean cancelSignalled) {
this.cancelSignalled = cancelSignalled;
}
public Throwable getLastError() {
return lastError;
}
public boolean isCancelSignalled() {
return cancelSignalled;
}
}