/*
* Copyright 2012 Uwe Krueger.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mandelsoft.mand.movie;
import com.mandelsoft.mand.Environment;
import com.mandelsoft.mand.IllegalConfigurationException;
import com.mandelsoft.mand.MandIter;
import com.mandelsoft.mand.MandelData;
import com.mandelsoft.mand.MandelInfo;
import com.mandelsoft.mand.MandelName;
import com.mandelsoft.mand.MandelSpec;
import com.mandelsoft.mand.PixelIterator;
import com.mandelsoft.mand.QualifiedMandelName;
import com.mandelsoft.mand.calc.AreaCalculator;
import com.mandelsoft.mand.calc.MandelRasterCalculationContext;
import com.mandelsoft.mand.calc.OptimizedAreaCalculator;
import com.mandelsoft.mand.calc.SimpleAreaCalculator;
import com.mandelsoft.mand.scan.FolderMandelScanner;
import com.mandelsoft.mand.scan.MandelHandle;
import com.mandelsoft.mand.scan.MandelScanner;
import com.mandelsoft.mand.tools.Command;
import com.mandelsoft.mand.util.MandArith;
import com.mandelsoft.mand.util.MandUtils;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
/**
*
* @author Uwe Krueger
*/
public class AreaInterpolation extends MandArith {
static public final double zoombase=0.1;
public static class ZoomHandler {
private boolean simulate;
private MandelScanner scanner;
private MandelName next;
private boolean matched;
public ZoomHandler(boolean simulate, MandelScanner scanner)
{
this.simulate=simulate;
this.scanner=scanner;
}
public boolean hasMatched()
{
return matched;
}
public boolean isSimulate()
{
return simulate;
}
public void setNext(MandelName next)
{
this.next=next;
matched=false;
}
public MandelName getNext()
{
return next;
}
public MandelScanner getScanner()
{
return scanner;
}
public MandelData getNext(AreaInterpolation i)
{
if (next==null || scanner==null) return null;
MandelHandle h=scanner.getMandelData(next);
if (h==null) return null;
try {
MandelData md=h.getData();
if (md!=null) {
if (i.getCurrent().getInfo().getDX().equals(md.getInfo().getDX())) {
System.out.println("found "+getNext());
matched=true;
}
else {
System.out.println("found "+getNext()+" does not match current zoom");
md=null;
}
}
return md;
}
catch (IOException ex) {
return null;
}
}
}
/////////////////////////////////////////////////////////////////////////
static public class SlowDown {
public double sdt;
public double sd;
public double z1;
public double v1;
public double ve;
public double a;
public double t;
public double c;
public double orig;
public double sda;
public SlowDown(double zpf, double sdt, double sd)
{
this.sdt=sdt;
this.sd=sd;
z1=Math.log(sdt)/Math.log(zoombase);
v1=Math.log(zpf)/Math.log(zoombase);
ve=sd*v1;
a=v1*v1*(1-sd*sd)/2/z1;
t=2*z1/v1/(1+sd);
orig=Math.log(sdt)/Math.log(zpf);
sda=Math.pow(zoombase, a);
c=Math.pow(zoombase, Math.sqrt(a));
}
public void print(String msg)
{
System.out.println("*** "+msg);
System.out.println("slow down threashold: "+sdt+" ("+z1+")");
System.out.println("zoom speed: "+v1);
System.out.println("final speed: "+ve);
System.out.println("a: "+a);
System.out.println("slow down factor: "+(1/sda));
System.out.println("correction factor: "+c);
System.out.println("number of frames: "+t);
System.out.println("number of original frames: "+orig);
System.out.println("additional frames: "+(t-orig));
}
}
/////////////////////////////////////////////////////////////////////////
public static class Area {
private QualifiedMandelName name;
private MandelAccess access;
public Area(QualifiedMandelName name, MandelAccess access)
{
this.name=name;
this.access=access;
}
public MandelAccess getAccess()
{
return access;
}
public QualifiedMandelName getName()
{
return name;
}
public MandelName getMandelName()
{
return name.getMandelName();
}
public MandelData getMandelData()
{
return access.getMandelData();
}
public MandelInfo getInfo()
{
return access.getInfo();
}
public int getIter()
{
return access.getIter();
}
public void setY(BigDecimal y)
{
access.setY(y);
}
public void setX(BigDecimal x)
{
access.setX(x);
}
public double getY(BigDecimal y)
{
return access.getY(y);
}
public double getX(BigDecimal x)
{
return access.getX(x);
}
public boolean containsY(BigDecimal y)
{
return access.containsY(y);
}
public boolean containsX(BigDecimal x)
{
return access.containsX(x);
}
public boolean contains(BigDecimal x, BigDecimal y)
{
return access.contains(x, y);
}
}
/////////////////////////////////////////////////////////////////////////
static boolean debug=false;
static final AreaCalculator calc_opt=new OptimizedAreaCalculator();
static final AreaCalculator calc_simple=new SimpleAreaCalculator();
private BigDecimal dx;
private Area top;
private MandelInfo info;
private Area bottom;
private int intercnt;
private MandelData current;
private File intermediate_dir;
private MandelScanner intermediate_scanner;
private ZoomHandler handler;
private Area intermediate_start;
private Area intermediate_end;
private double intermediate_factor;
private int intermediate_count;
private int intermediate_current;
// slow down data
private BigDecimal end_zoom;
private BigDecimal threshold;
private double sdt;
private double sd;
private double sda; // slow down correction
private double limit;
private MandelName target;
private MandelScanner scanner;
private BigDecimal zoom;
private int count=0;
public AreaInterpolation(MandelName target, Double zoom,
MandelScanner scanner, ZoomHandler handler)
throws IOException
{
setup(target,zoom,scanner,handler);
}
private void setup(MandelName target, Double zoom,
MandelScanner scanner, ZoomHandler handler)
{
this.target=target;
this.scanner=scanner;
this.handler=handler;
this.zoom=new BigDecimal(zoom);
}
public AreaInterpolation(MandelName target, Double zoom,
MandelScanner scanner, ZoomHandler handler,
double limit, File intermediate)
throws IOException
{
this.limit=limit;
this.intermediate_dir=intermediate;
if (intermediate!=null) {
intermediate_scanner=new FolderMandelScanner(intermediate);
}
System.out.println("intermediate "+limit+" ("+intermediate_dir+")");
setup(target,zoom,scanner, handler);
}
private double total_zoom;
private double total_frames;
public void check() throws IOException
{
MandelHandle h=scanner.getMandelInfo(MandelName.ROOT);
if (h==null) throw new IOException("no root area found");
MandelData start=h.getInfo();
h=scanner.getMandelInfo(target);
if (h==null) throw new IOException("target area not found");
MandelData end=h.getInfo();
end_zoom=end.getInfo().getDX();
if (sdt>0) {
threshold=div(end_zoom,sdt);
}
else {
threshold=BigDecimal.ZERO;
}
total_zoom=div(end.getInfo().getDX(),start.getInfo().getDX()).doubleValue();
total_frames=Math.log(total_zoom)/Math.log(zoom.doubleValue());
}
public void setSlowDownThreshold(double sdt)
{
this.sdt=sdt;
}
public void setFinalSpeedFactor(double sd)
{
this.sd=sd;
}
public double getTotalFrames()
{
return total_frames;
}
public double getTotalZoom()
{
return total_zoom;
}
private boolean isIntermediateRoot()
{
return intermediate_end!=null;
}
private void finishIntermediate()
{
intermediate_end=null;
}
private boolean continueWithNextSubArea(Area next) throws IOException
{
System.out.println("start next interpolation root "+next.getName());
top=next;
intercnt=0;
info=new MandelInfo(top.getInfo());
bottom=createIntermediate();
if (bottom!=null) {
// futher intermediate bottom areas to use
System.out.println("next sub is "+bottom.getName());
return true;
}
if (isIntermediateRoot()) {
// no more intermediate subs
// so finally continue with original intermediate target area
System.out.println("reuse intermediate target area "+
intermediate_end.getName()+
" as next sub area for "+
next.getName());
bottom=intermediate_end;
finishIntermediate();
return true;
}
System.out.println("next original main area is "+next.getName());
if (next.getMandelName().equals(target)) {
bottom=null;
System.out.println("target reached");
return false;
}
else {
// try next area in tree path down to target
MandelName n=next.getMandelName().sub(target);
QualifiedMandelName qn=new QualifiedMandelName(n,"adjust");
MandelHandle h=scanner.getMandelData(qn);
if (h==null) {
qn=new QualifiedMandelName(n,"zoom");
h=scanner.getMandelData(qn);
}
if (h==null) {
qn=new QualifiedMandelName(n);
h=scanner.getMandelData(n);
}
if (h==null) throw new IOException("cannot find "+n);
bottom=new Area(qn,new MandelAccess(h.getData()));
// check for interpolation limit
// if zoom too large inject explicitly calculated intermediate area
double z=div(bottom.getInfo().getDX(), info.getDX()).doubleValue();
if (z<limit) {
// zpf to next bottom rea too large calculate intermediate zpf variations
// save official bottom area
next=bottom;
// calculate number of required intermediate area
double num=Math.ceil(Math.log(z)/Math.log(limit));
System.out.println("requires "+((int)(num-1))
+" intermedite zooms to meet limit of "+limit);
double t=Math.pow(z, 1/num);
intermediate_start=top;
intermediate_end=bottom;
intermediate_factor=t;
intermediate_count=(int)num-1;
intermediate_current=0;
bottom=createIntermediate();
}
return true;
}
}
private Area createIntermediate()
{
if (intermediate_current == intermediate_count) {
return null;
}
intermediate_current++;
String label="zoom-"+intermediate_start.getMandelName().sub(target).getSubAreaName()+
"-"+intermediate_current;
QualifiedMandelName iname=new QualifiedMandelName(intermediate_start.getMandelName(),label);
BigDecimal dx=mul(info.getDX(),intermediate_factor);
MandelData md=createZoom(intermediate_start.getInfo(),
intermediate_end.getInfo(),dx);
if (intermediate_scanner!=null) {
MandelHandle h=intermediate_scanner.getMandelData(iname);
if (h!=null) {
try {
MandelData me=h.getData();
if (me.getInfo().getDX().equals(md.getInfo().getDX())) {
System.out.println("reusing intermediate "+iname+"...");
return new Area(iname,new MandelAccess(me));
}
System.out.println("existing intermediate "+iname+" does not match: dx "+
me.getInfo().getDX()+" differs from expected "+md.getInfo().getDX());
}
catch (IOException ex) {
// continue calculating
}
}
}
System.out.println("calculating intermediate "+iname+"...");
calc(md,null,calc_opt);
if (intermediate_dir!=null) {
System.out.println("saving "+iname);
md.getInfo().setLocation("intermediate zoom "+iname+
" for interpolation limit "+limit);
save(intermediate_dir,iname,md);
}
return new Area(iname,new MandelAccess(md));
}
private void calc(MandelData md,
PixelIterator pi, AreaCalculator calc)
{
MandelRasterCalculationContext ctx=new MandelRasterCalculationContext(md.getInfo());
if (pi!=null) ctx.setPixelIterator(pi);
calc.calc(ctx);
ctx.setInfoTo(md.getInfo());
md.getInfo().setRasterCreationTime(System.currentTimeMillis());
md.setRaster(ctx.getRaster());
}
private MandelData createZoom(MandelInfo start, MandelInfo end,
BigDecimal dx)
{
MandelSpec spec=new MandelSpec(start.getSpec());
BigDecimal f=div(sub(dx,start.getDX()),
sub(end.getDX(),start.getDX()));
spec.setXM(approach(start.getXM(), end.getXM(),f));
spec.setYM(approach(start.getYM(), end.getYM(),f));
spec.setDX(dx);
spec.setDY(div(mul(dx,start.getRY()),start.getRX()));
spec.setLimitIt(Math.max(start.getLimitIt(), end.getLimitIt()));
MandelInfo next=new MandelInfo(spec,start);
MandUtils.round(next);
return new MandelData(next);
}
private boolean prepareNext() throws IOException
{
System.out.println("using zoom factor "+zoom);
dx=mul(dx,zoom);
if (dx.compareTo(threshold)<0) {
// in slow down mode
if (sda==0) {
SlowDown s=new SlowDown(zoom.doubleValue(),div(end_zoom,dx).doubleValue(),sd);
s.print("starting slow down");
sda=s.sda;
// zoom=mul(zoom,s.c); // correction factor for first slow down
}
BigDecimal next=div(zoom,sda);
if (next.compareTo(BigDecimal.ONE)<0) {
zoom=next;
}
else {
System.out.println("stop slow down");
}
}
while (dx.compareTo(bottom.getInfo().getDX())<=0) {
if (!continueWithNextSubArea(bottom)) {
return false;
}
}
current=createZoom(info,bottom.getInfo(),dx);
intercnt++;
return true;
}
private BigDecimal approach(BigDecimal s, BigDecimal d, BigDecimal f)
{
return add(s, mul(sub(d,s),f) );
}
public MandelName getTarget()
{
return target;
}
public MandelName getTopAreaName()
{
return top.getMandelName();
}
public QualifiedMandelName getTopName()
{
return top.getName();
}
public MandelAccess getTopAccess()
{
return top.getAccess();
}
public MandelAccess getBottomAccess()
{
if (bottom==null) return null;
return bottom.getAccess();
}
public int getInterpolationCount()
{
return intercnt;
}
public MandelData getCurrent()
{
return current;
}
public int getCount()
{
return count;
}
public boolean hasNext()
{
return top==null || !top.getMandelName().equals(target);
}
public MandelData getNext() throws IOException
{
if (!hasNext()) return null;
if (top==null) {
MandelHandle h=scanner.getMandelData(MandelName.ROOT);
Area root=new Area(QualifiedMandelName.ROOT,new MandelAccess(h.getData()));
continueWithNextSubArea(root);
dx=info.getDX();
current=top.getMandelData();
}
else {
if (!prepareNext()) {
current=top.getMandelData();
}
else {
Interpolator i=new Interpolator(current.getInfo());
if (handler!=null) {
if (handler.isSimulate()) {
return current;
}
MandelData md=handler.getNext(this);
if (md!=null) {
current=md;
}
}
if (current.getRaster()==null) {
System.out.println("interpolating "+top.getName()+
"("+intercnt+") ...");
calc(current, i, calc_simple);
current.getInfo().setLocation("interpolation "+intercnt+" for "+
top.getName());
}
// MandelRasterCalculator c=new SimpleMandelRasterCalculator(current.getInfo());
// c.setPixelIterator(i);
// c.calc();
// c.setInfoTo(current.getInfo());
// current.getInfo().setRasterCreationTime(System.currentTimeMillis());
// current.setRaster(c.getRaster());
}
}
count++;
return current;
}
/////////////////////////////////////////////////////////////////////////
private class Interpolator implements PixelIterator {
private PixelIterator pi;
private int limit;
public Interpolator(MandelSpec spec)
{
pi=MandIter.createPixelIterator(spec);
limit=spec.getLimitIt()+1;
if (debug) {
System.out.println(" start: x="+spec.getXMin()+"<->"+spec.getXMax());
System.out.println(" y="+spec.getYMin()+"<->"+spec.getYMax());
System.out.println(" main: x="+info.getXMin()+"<->"+info.getXMax());
System.out.println(" y="+info.getYMin()+"<->"+info.getYMax());
System.out.println(" sub: x="+bottom.getInfo().getXMin()+"<->"+bottom.
getInfo().getXMax());
System.out.println(" y="+bottom.getInfo().getYMin()+"<->"+bottom.
getInfo().getYMax());
}
}
private BigDecimal cx;
private BigDecimal cy;
private boolean subx;
private boolean suby;
private int x,y;
public int iter()
{ int it;
if (subx && suby) {
//System.out.println(" iter bottom "+cx+", "+cy);
it=bottom.getIter();
if (it>bottom.getInfo().getLimitIt()) it=limit;
}
else {
//System.out.println(" iter top "+cx+", "+cy);
it=top.getIter();
if (it>top.getInfo().getLimitIt()) it=limit;
}
return it;
}
public void setY(int y)
{
pi.setY(y);
cy=pi.getCY();
top.setY(cy);
bottom.setY(cy);
suby=bottom.containsY(cy);
this.x=x;
this.y=y;
}
public void setX(int x)
{
pi.setX(x);
cx=pi.getCX();
top.setX(cx);
bottom.setX(cx);
subx=bottom.containsX(cx);
}
public BigDecimal getCY()
{
return cy;
}
public BigDecimal getCX()
{
return cx;
}
public boolean isFast()
{
return pi.isFast();
}
public double getY(BigDecimal y)
{
return pi.getY(y);
}
public double getX(BigDecimal x)
{
return pi.getX(x);
}
public int getPrecision()
{
return pi.getPrecision();
}
public int getMagnification()
{
return pi.getMagnification();
}
}
/////////////////////////////////////////////////////////////////////////
public static void print(int cnt, AreaInterpolation i)
{
MandelInfo info=i.getCurrent().getInfo();
System.out.println(""+cnt+": "+i.getTopName()+
"("+i.getInterpolationCount()+"); "+
i.getTopAccess().getInfo().getXM()+" -> "+
(i.getBottomAccess()==null?"end":i.getBottomAccess().getInfo().getXM())+" = "+
info.getXM()+" ("+info.getDX()+")");
}
public static void save(File dir, MandelName n, MandelData md)
{
save(dir,new QualifiedMandelName(n),md);
}
public static void save(File dir, QualifiedMandelName n, MandelData md)
{
File file=new File(dir, n.toString()+".mr");
try {
System.out.println("writing "+file);
md.write(file);
}
catch (IOException ex) {
System.out.println("cannot write "+file+": "+ex);
}
}
/////////////////////////////////////////////////////////////////////////
private static class Handler extends ZoomHandler {
private MandelName name=MandelName.ROOT;
private MandelName last;
private File dir;
public Handler(boolean simulate, File dir) throws IOException
{
super(simulate,new FolderMandelScanner(dir));
setNext(name);
this.dir=dir;
}
public void prepareNext()
{
last=name;
name=name.sub('z');
setNext(name);
}
public MandelName getLast()
{
return last;
}
public void save(AreaInterpolation i)
{
if (!isSimulate() && !hasMatched()) {
AreaInterpolation.save(dir,getNext(),i.getCurrent());
}
}
}
public static double getDoubleArg(String arg, String name)
{
double d=0;
try {
d=Double.valueOf(arg);
}
catch (NumberFormatException nfe) {
Command.Error("double value as "+name+" expected");
}
return d;
}
public static void main(String[] args)
{
int c=0;
double fps=25; // frames per secons
double speed=0; // time per zoombase factor 0.1
double limit=0.5;
double zpf=0; // zoombase per frame
double sd=0.1; // final speed slow down factor
double sdt=0.1; // slow down threshold
MandelName target=null;
File dbroot=new File(".");
File dir=new File("movies");
boolean vflag=false;
boolean iflag=false;
if (args.length==0) {
System.out.println("interpolation sequence [<options>] <target area>");
System.out.println(" -i show info only");
System.out.println(" -v verbose/simulate only");
System.out.println(" -s <speed> time per zoom factor "+zoombase);
System.out.println(" -f <fps> frames per second");
System.out.println(" -z <zpf> zoom per frame");
System.out.println(" -n no intermediate area calculation");
System.out.println(" -l <limit> interpolation limit (default "+limit+")");
System.out.println(" -r <dbroot> image database (default "+dbroot+")");
System.out.println(" -d <dir> target root folder (default "+dir+")");
System.exit(0);
}
while (args.length>c&&args[c].charAt(0)=='-') {
String arg=args[c++];
for (int i=1; i<arg.length(); i++) {
char opt;
switch (opt=arg.charAt(i)) {
case 'v':
vflag=true; // simulation
break;
case 'i':
iflag=true;
break;
case 'n':
limit=0; // only linear interpolation among existing areas
break;
case 'f':
if (args.length>c) {
fps=getDoubleArg(args[c++], "frames per seconds");
}
else Command.Error("frames per second missing");
break;
case 's':
if (zpf!=0) {
Command.Error("only zoom or speed");
}
if (args.length>c) {
speed=getDoubleArg(args[c++], "seconds per zoom");
}
else Command.Error("seconds per zoom missing");
break;
case 'z':
if (speed!=0) {
Command.Error("only zoom or speed");
}
if (args.length>c) {
zpf=getDoubleArg(args[c++], "zoom factor");
}
else Command.Error("zoom factor missing");
break;
case 'l':
if (args.length>c) {
limit=getDoubleArg(args[c++], "limit");
}
else Command.Error("interpolation limit missing");
break;
case 'r':
if (args.length>c) {
dbroot=new File(args[c++]);
if (!dbroot.isDirectory()) {
Command.Error("root does not exist");
}
}
else Command.Error("root folder missing");
break;
case 'd':
if (args.length>c) {
dir=new File(args[c++]);
}
else Command.Error("target folder missing");
break;
default:
Command.Error("illegal option '"+opt+"'");
}
}
}
if (!(args.length>c)) {
Command.Error("target area missing");
}
try {
target=MandelName.create(args[c++]);
}
catch (IllegalArgumentException iae) {
Command.Error("illegal area name "+args[c-1]);
}
double fpz; // frames per zoombase;
if (speed!=0) { // speed selecte
fpz=speed*fps;
zpf=Math.pow(zoombase, 1/fpz);
}
else { // zpf selected
if (zpf==0) zpf=0.98;
fpz=Math.log(zoombase)/Math.log(zpf);
speed=fpz/fps;
}
try {
Environment env=new Environment("interpol",args, dbroot);
dir=new File(dir,target.getName());
File intermediate=new File(dir,"intermediate");
intermediate.mkdirs();
Handler handler=new Handler(vflag,dir);
AreaInterpolation i=new AreaInterpolation(target,zpf,
env.getImageDataScanner(),
handler,
limit,intermediate);
i.setFinalSpeedFactor(sd);
i.setSlowDownThreshold(sdt);
i.check();
double time=i.getTotalFrames()/fps;
System.out.println("target area: "+target);
System.out.println("target folder: "+dir);
System.out.println("interpolation limit: "+limit);
System.out.println("frame rate (frames per second): "+fps);
System.out.println("zoom per frame: "+zpf+" ("+(1/fpz)+")");
System.out.println("speed (seconds per zoom factor "+zoombase+"): "+speed);
System.out.println("speed (frames per zoom factor "+zoombase+"): "+fpz);
System.out.println("total zoom: "+i.getTotalZoom());
System.out.println("total frames: "+i.getTotalFrames());
System.out.println("total movie length (seconds): "+time);
if (sdt>0) {
SlowDown s=new SlowDown(zpf,sdt,sd);
// double z1=Math.log(sdt)/Math.log(zoombase);
// double v1=Math.log(zpf)/Math.log(zoombase);
// double ve=sd*v1;
// double a=v1*v1*(1-sd*sd)/2/z1;
// double t=2*z1/v1/(1+sd);
// double orig=Math.log(sdt)/Math.log(zpf);
// double sda=Math.pow(zoombase, a);
s.print("slow down mode info");
}
if (!iflag) {
System.out.println("starting interpolation...............");
int cnt=0;
while (i.getNext()!=null) {
print(cnt++, i);
handler.save(i);
handler.prepareNext();
}
}
}
catch (IOException io) {
System.out.println("illegal intrpolation: "+io);
}
catch (IllegalConfigurationException ex) {
System.out.println("illegal configuration: "+ex);
}
}
}