/*
* Copyright 2011 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.cm;
import java.awt.Color;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.swing.event.ChangeListener;
import com.mandelsoft.mand.mapping.Mapping;
import com.mandelsoft.swing.ChangeListenerSupport;
/**
*
* @author Uwe Krueger
*/
public class ColormapModel extends EventSourceSupport
implements ColormapSource {
public static boolean debug=false;
public static final int RESIZE_PROPORTIONAL=0;
public static final int RESIZE_LOCK_IPS=1;
public static final int RESIZE_LOCK_COLORS=2;
public enum ResizeMode {
RESIZE_PROPORTIONAL("proportional"),
RESIZE_LOCK_IPS("lock interpolation points"),
RESIZE_LOCK_COLORS("lock iteration colors");
private String name;
ResizeMode(String name)
{
this.name=name;
}
public String getName()
{
return name;
}
}
////////////////////////////////////////////////////////////////////////////
private boolean modifiable;
private ResizeMode resizemode=ResizeMode.RESIZE_LOCK_IPS;
private Colormap colormap;
private InterpolationPoint ips;
private boolean adjusting;
public ColormapModel()
{
}
public ColormapModel(Colormap cm)
{ if (cm!=null) setColormap(cm);
}
public Colormap getColormap()
{ return colormap;
}
public int getSize()
{ return colormap==null?0:colormap.getSize();
}
public void setModifiable(boolean b)
{ boolean old=modifiable;
modifiable=b;
if (old!=b) change.fireChangeEvent(this);
}
public void setResizeMode(ResizeMode resizemode)
{
if (debug) System.out.println("setting resize mode "+resizemode);
this.resizemode=resizemode;
}
public void setAdjusting(boolean adjusting)
{
this.adjusting=adjusting;
}
public boolean isAdjusting()
{
return adjusting;
}
public boolean isModifiable()
{
return modifiable;
}
public ResizeMode getResizeMode()
{
return resizemode;
}
public Set<InterpolationPoint> getInterpolationPoints()
{
Set<InterpolationPoint> set=new HashSet<InterpolationPoint>();
InterpolationPoint ip=ips;
while (ip!=null) {
set.add(ip);
ip=ip.getNext();
}
return set;
}
public Iterator<InterpolationPoint> interpolationPoints()
{
return new IPIterator();
}
private class IPIterator implements Iterator<InterpolationPoint> {
private InterpolationPoint ip=ips;
public boolean hasNext()
{
return ip!=null;
}
public InterpolationPoint next()
{
try {
return ip;
}
finally {
ip=ip.getNext();
}
}
public void remove()
{
throw new UnsupportedOperationException("Not supported yet.");
}
}
public void setColormap(Colormap map)
{
if (_setColormap(map)) {
if (map!=null) {
setupInterpolationPoints();
}
change.fireChangeEvent(this);
}
}
private boolean _setColormap(Colormap map)
{
if (this.colormap==map) {
if (debug) System.out.println("colormap in colormap model unchanged");
return false;
}
if (map==null) {
if (debug) System.out.println("clear colormap in colormap model");
}
else {
if (debug) System.out.println("setting colormap in colormap model "+map.getSize());
}
if (this.colormap!=null) this.colormap.removeChangeListener(listener);
this.colormap=map;
if (map!=null) {
this.colormap.addChangeListener(listener);
}
return true;
}
private void cleanupInterpolationPoints()
{
InterpolationPoint ip=ips;
while (ip!=null) {
ip.cleanup();
ip=ip.getNext();
}
}
private void setupInterpolationPoints()
{ InterpolationPoint ip;
cleanupInterpolationPoints();
//System.out.println("setup new ips "+colormap.getSize());
ips=new InterpolationPoint(1, this);
ips.fireInterpolationPointEvent(InterpolationPointEvent.IPE_ADDED);
ips.setFixed(true);
ip=new InterpolationPoint(colormap.getSize()-1, this);
ip.setFixed(true);
ips.add(ip);
determineInterpolationPoints();
}
protected void determineInterpolationPoints()
{
// handleComponent(ColorComponentAccess.red);
// handleComponent(ColorComponentAccess.green);
// handleComponent(ColorComponentAccess.blue);
handleComponents();
}
protected int dir(int diff, int dir)
{ if (diff==0) return dir;
return Integer.signum(diff);
}
protected boolean sameDir(int diff, int dir)
{
return diff==0||dir==0||Integer.signum(diff)==dir;
}
protected void handleComponent(ColorComponentAccess comp)
{
int index=1;
while (index<colormap.getSize()-1) {
int diff=comp.getComponentValue(colormap.getColor(index+1))-
comp.getComponentValue(colormap.getColor(index));
int cur=diff;
int dir=dir(diff,0);
while (diff-1<=cur&&cur<=diff+1&&sameDir(cur,dir)
&&++index<colormap.getSize()-1) {
dir=dir(cur,dir);
if (debug) System.out.println("dir="+dir+" diff="+diff);
cur=comp.getComponentValue(colormap.getColor(index+1))-
comp.getComponentValue(colormap.getColor(index));
}
createInterpolationPoint(index);
}
}
private class State {
ColorComponentAccess comp;
State(ColorComponentAccess comp)
{ this.comp=comp;
}
int rx;
double start;
double end;
double dc;
void setup(int idx1, int idx2)
{
rx = idx2 - idx1;
start = comp.getComponentValue(getColor(idx1));
end = comp.getComponentValue(getColor(idx2));
dc=end-start;
}
boolean check(int cur, int i)
{ int c = (int) (start + i * dc / rx);
int f=comp.getComponentValue(getColor(cur));
if (c-1>f || f>c+1) {
return true;
}
return false;
}
}
private class CheckState {
State red=new State(ColorComponentAccess.red);
State green=new State(ColorComponentAccess.green);
State blue=new State(ColorComponentAccess.blue);
void setup(int idx1, int idx2)
{
red.setup(idx1, idx2);
green.setup(idx1, idx2);
blue.setup(idx1, idx2);
}
boolean check(int cur, int i)
{
return red.check(cur, i) || green.check(cur, i) || blue.check(cur, i);
}
}
protected void handleComponents()
{ CheckState state=new CheckState();
int index=1;
while (index<colormap.getSize()) {
int found=index;
for (int i=index; i<colormap.getSize(); i++) {
if (checkInterpolation(index, i, state)) {
found=i;
}
}
createInterpolationPoint(found);
if (found==index) index=found+1;
else index=found;
}
}
private boolean checkInterpolation(int idx1, int idx2,
CheckState state)
{
return _checkInterpolation(idx1,idx2,state);
//||_checkInterpolation(idx2,idx1,comp);
}
private boolean _checkInterpolation(int idx1, int idx2,
CheckState state)
{
int rx = idx2 - idx1;
int dx = Integer.signum(rx);
state.setup(idx1,idx2);
//System.out.println("check "+idx1+"-"+idx2);
for (int i = 0; i != rx; i += dx) {
int cur = i + idx1;
if (state.check(cur, i)) {
//System.out.println("break "+cur);
return false;
}
}
return true;
}
public Color getColor(int i)
{
return colormap.getColor(i);
}
public InterpolationPoint createInterpolationPoint(int index)
{
//System.out.println(" create ip at "+index+": "+getColor(index));
InterpolationPoint ip=ips;
InterpolationPoint n;
while (ip.getIndex()<index) {
ip=ip.getNext();
}
if (ip==null)
throw new IllegalArgumentException("index beyond colormap");
if (ip.getIndex()==index) return ip;
n=new InterpolationPoint(index,this);
ip.getPrev().add(n);
return ip;
}
public InterpolationPoint getInterpolationPoint(int index)
{
InterpolationPoint ip=ips;
while (ip!=null&&ip.getIndex()<index) {
ip=ip.getNext();
}
if (ip==null||ip.getIndex()!=index) return null;
return ip;
}
////////////////////////////////////////////////////////////////////////////
// resizing
////////////////////////////////////////////////////////////////////////////
public void resize(int size)
{
resizeI(size,new FactorResize(colormap.getSize(),size));
}
public void resize(int size, Mapping src, Mapping dst)
{
resize(resizemode, size, src, dst);
}
public void resize(ResizeMode mode, int size, Mapping src, Mapping dst)
{
if (debug) System.out.println("resizemode is "+mode);
switch (mode) {
case RESIZE_PROPORTIONAL:
resizeI(size);
break;
case RESIZE_LOCK_IPS:
resizeI(size,src,dst);
break;
case RESIZE_LOCK_COLORS:
resizeC(size,src,dst);
break;
}
}
public void resizeI(int size)
{
resizeI(size,new FactorResize(colormap.getSize(),size));
}
public void resizeI(int size, Mapping src, Mapping dst)
{
resizeI(size,new MappingBasedResize(src,dst));
}
private void resizeI(int size, ResizeHandler h)
{
if (colormap==null || size==getSize()) return;
if (debug) System.out.println("resize from "+getSize()+" to "+size);
Colormap old=colormap;
double factor=((double)size-2)/(old.getSize()-2);
if (debug) System.out.println("resize factor "+factor);
Colormap cm=new Colormap(size);
cm.startModification();
cm.setColor(0, old.getColor(0));
setAdjusting(true);
_setColormap(cm);
InterpolationPoint ip=ips;
int last=0;
while (ip!=null) {
int n;
if (ip.getPrev()==null) n=1;
else n=h.getRelocatedIndex(ip.getIndex());
if (ip.getNext()==null) {
if (n!=size-1) {
if (debug) System.out.println("^correcting last index "+n+" to "+(size-1));
n=size-1;
}
}
if (n==0) {
n=(int)Math.round((ip.getIndex()-1)*factor+1);
if (n<=last) n=last+1;
if (debug) System.out.println(" no mapping found for "+ip.getIndex());
}
if (debug) System.out.println(" moving "+ip.getIndex()+" to "+n);
ip._prepareSetIndex(last=n);
ip=ip.getNext();
}
ip=ips;
while (ip!=null) {
ip._finishSetIndex(true);
ip=ip.getNext();
}
colormap.endModification();
change.fireChangeEvent(this);
setAdjusting(false);
}
public void resizeC(int size, Mapping src, Mapping dst)
{
resizeC(size,new MappingBasedResize(src,dst));
}
private void resizeC(int size, ResizeHandler h)
{
if (colormap==null || size==getSize()) return;
if (debug) System.out.println("resize new from "+getSize()+" to "+size);
Colormap old=colormap;
double factor=((double)size-2)/(old.getSize()-2);
if (debug) System.out.println("resize factor "+factor);
cleanupInterpolationPoints();
Colormap cm=new Colormap(size);
cm.startModification();
cm.setColor(0, old.getColor(0));
setAdjusting(true);
_setColormap(cm);
// first create temp ips to setup colormap
cm.setColor(1, old.getColor(1));
ips=new InterpolationPoint(1, this);
ips.setFixed(true);
cm.setColor(cm.getSize()-1, old.getColor(old.getSize()-1));
InterpolationPoint ip=new InterpolationPoint(cm.getSize()-1, this);
ip.setFixed(true);
ips.add(ip);
int n;
int last=1;
for (int c=2; c<old.getSize()-1; c++) {
n=h.getRelocatedIndex(c);
if (n==0) {
n=(int)Math.round((c-1)*factor+1);
if (n<=last) n=last+1;
if (debug) System.out.println(" no mapping found for "+c);
}
last=n;
if (debug) System.out.println(" moving index "+c+" to "+n);
cm.setColor(n, old.getColor(c));
createInterpolationPoint(n);
}
// second fill colormap
ip=ips;
while (ip!=null) {
ip._finishSetIndex(true);
ip=ip.getNext();
}
// third officially setup ips according new colormap
setupInterpolationPoints();
cm.endModification();
}
///////////////////////////////////////////////////////////////////////////
public interface ResizeHandler {
int getRelocatedIndex(int index);
}
public static class FactorResize implements ResizeHandler {
private double factor;
public FactorResize(int src, int dst)
{
factor=((double)dst-2)/(src-2);
}
public int getRelocatedIndex(int index)
{
return (int)Math.round((index-1)*factor+1);
}
}
public static class MappingBasedResize implements ResizeHandler {
private Mapping dst;
private Mapping src;
public MappingBasedResize(Mapping src, Mapping dst)
{
this.dst=dst;
this.src=src;
}
public int getRelocatedIndex(int index)
{
return dst.getColormapIndex(src.getInteration(index, 0, false));
}
}
////////////////////////////////////////////////////////////////////////////
// colormap changes
////////////////////////////////////////////////////////////////////////////
private ChangeListenerSupport change=new ChangeListenerSupport();
private com.mandelsoft.util.ChangeListener listener=new com.mandelsoft.util.ChangeListener() {
public void stateChanged(com.mandelsoft.util.ChangeEvent e)
{ if (!isAdjusting()) {
setupInterpolationPoints();
}
change.fireChangeEvent(ColormapModel.this);
}
};
public void removeChangeListener(ChangeListener h)
{
change.removeChangeListener(h);
}
public void addChangeListener(ChangeListener h)
{
change.addChangeListener(h);
}
}