package ini.trakem2.display;
import ij.ImagePlus;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import ini.trakem2.display.graphics.GraphicsSource;
import ini.trakem2.utils.IJError;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.TreeMap;
public abstract class GroupingMode implements Mode {
protected final Display display;
protected final Layer layer;
protected final Updater updater;
protected final Painter painter;
protected Rectangle srcRect;
protected double magnification;
protected final GroupedGraphicsSource gs;
protected final List<Patch> originalPatches;
protected final List<PatchRange> ranges;
protected HashMap<Paintable, ScreenPatchRange<?>> screenPatchRanges;
public GroupingMode(final Display display, final List<Displayable> selected) {
this.display = display;
this.layer = display.getLayer();
this.srcRect = ( Rectangle )display.getCanvas().getSrcRect().clone();
this.magnification = display.getCanvas().getMagnification();
this.originalPatches = new ArrayList<Patch>();
this.ranges = new ArrayList<PatchRange>();
final TreeMap<Integer,Patch> m = new TreeMap<Integer,Patch>();
for (final Displayable d : selected) {
if (d.getClass() == Patch.class) {
m.put(layer.indexOf(d), (Patch) d);
}
}
originalPatches.addAll(m.values());
this.screenPatchRanges = new HashMap<Paintable, ScreenPatchRange<?>>( originalPatches.size() );
this.gs = createGroupedGraphicSource();
this.updater = new Updater();
this.painter = new Painter();
}
final protected void initThreads() {
updater.start();
painter.start();
updater.update(); // will call painter.update()
}
final protected void quitThreads() {
painter.quit();
updater.quit();
}
protected abstract GroupedGraphicsSource createGroupedGraphicSource();
protected abstract class GroupedGraphicsSource implements GraphicsSource {
/** Returns the list given as argument without any modification. */
public List<? extends Paintable> asPaintable(final List<? extends Paintable> ds) {
final List<Paintable> newList = new ArrayList< Paintable >();
final HashSet<ScreenPatchRange<?>> used = new HashSet<ScreenPatchRange<?>>();
/* fill it */
final HashMap<Paintable, ScreenPatchRange<?>> screenPatchRanges = GroupingMode.this.screenPatchRanges; // keep a pointer to the current list
for ( final Paintable p : ds )
{
final ScreenPatchRange<?> spr = screenPatchRanges.get( p );
if ( spr == null )
newList.add(p);
else if (!used.contains(spr))
{
used.add(spr);
newList.add( spr );
}
}
return newList;
}
}
static protected class PatchRange {
final ArrayList< Patch > list = new ArrayList<Patch>();
boolean starts_at_bottom = false;
boolean is_gray = true;
void addConsecutive(Patch p) {
list.add(p);
if (is_gray) {
switch (p.getType()) {
case ImagePlus.COLOR_RGB:
case ImagePlus.COLOR_256:
is_gray = false;
break;
}
}
}
void setAsBottom() {
this.starts_at_bottom = true;
}
}
protected abstract ScreenPatchRange<?> createScreenPathRange(final PatchRange range, final Rectangle srcRect, final double magnification);
static protected abstract class ScreenPatchRange<T> implements Paintable {
final ImageProcessor ip;
final ImageProcessor ipTransformed;
final FloatProcessor mask;
final FloatProcessor maskTransformed;
BufferedImage transformedImage;
static final int pad = 100;
final byte compositeMode;
ScreenPatchRange( final PatchRange range, final Rectangle srcRect, final double magnification )
{
this.compositeMode = range.list.get(0).getCompositeMode();
final BufferedImage image =
new BufferedImage(
( int )( srcRect.width * magnification + 0.5 ) + 2 * pad,
( int )( srcRect.height * magnification + 0.5 ) + 2 * pad,
range.starts_at_bottom ? range.is_gray ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB );
Graphics2D g = image.createGraphics();
final AffineTransform atc = new AffineTransform();
atc.translate(pad, pad);
atc.scale( magnification, magnification);
atc.translate(-srcRect.x, -srcRect.y);
g.setTransform( atc );
for (final Patch patch : range.list) {
patch.paint( g, srcRect, magnification, false, 0xffffffff, patch.getLayer(), null);
}
ip = new ImagePlus( "", image ).getProcessor();
ipTransformed = ip.createProcessor( ip.getWidth(), ip.getHeight() );
ipTransformed.snapshot();
if (!range.starts_at_bottom) {
final float[] pixels = new float[ ip.getWidth() * ip.getHeight() ];
mask = new FloatProcessor( ip.getWidth(), ip.getHeight(), image.getAlphaRaster().getPixels( 0, 0, ip.getWidth(), ip.getHeight(), pixels ), null );
maskTransformed = new FloatProcessor( ip.getWidth(), ip.getHeight() );
maskTransformed.snapshot();
}
else
{
mask = null;
maskTransformed = null;
}
image.flush();
transformedImage = makeImage(ip, mask);
}
abstract public void update(T t);
protected BufferedImage makeImage( final ImageProcessor ip, final FloatProcessor mask )
{
final BufferedImage transformedImage = new BufferedImage( ip.getWidth(), ip.getHeight(), null == mask ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB );
final Image img = ip.createImage();
transformedImage.createGraphics().drawImage( img, 0, 0, null );
img.flush();
if (null != mask) {
transformedImage.getAlphaRaster().setPixels( 0, 0, ip.getWidth(), ip.getHeight(), ( float[] )mask.getPixels() );
}
return transformedImage;
}
public void flush() {
if (null != transformedImage) transformedImage.flush();
}
@Override
public void paint( Graphics2D g, Rectangle srcRect, double magnification, boolean active, int channels, Layer active_layer, List<Layer> layers)
{
final AffineTransform at = g.getTransform();
final AffineTransform atp = new AffineTransform();
atp.translate( -pad, -pad );
g.setTransform( atp );
Composite original_composite = null;
if (Displayable.COMPOSITE_NORMAL != compositeMode) {
original_composite = g.getComposite();
g.setComposite(Displayable.getComposite(compositeMode, 1.0f));
}
g.drawImage( transformedImage, 0, 0, null );
if (null != original_composite) {
g.setComposite(original_composite);
}
g.setTransform( at );
}
@Override
public void prePaint( Graphics2D g, Rectangle srcRect, double magnification, boolean active, int channels, Layer active_layer, List<Layer> layers)
{
paint( g, srcRect, magnification, active, channels, active_layer, layers );
}
}
/** Creates a list of patch ranges and calls painter.update() */
private class Updater extends SimpleThread
{
void doit( final Rectangle r, final double m )
{
// 1 - Create the list of patch ranges
// A list of Displayable to paint within the current srcRect
final ArrayList<Displayable> to_paint = new ArrayList<Displayable>(layer.find(srcRect, true));
PatchRange current_range = new PatchRange();
ranges.clear();
ranges.add(current_range);
int last_i = -Integer.MAX_VALUE;
byte last_b = Displayable.COMPOSITE_NORMAL;
for (final Patch p : originalPatches) {
final int i = to_paint.indexOf(p);
final byte b = p.getCompositeMode();
if (0 == i) {
current_range.setAsBottom();
current_range.addConsecutive(p);
} else if (1 == i - last_i && b == Displayable.COMPOSITE_NORMAL && last_b == Displayable.COMPOSITE_NORMAL) {
current_range.addConsecutive(p);
} else {
current_range = new PatchRange();
ranges.add(current_range);
current_range.addConsecutive(p);
}
last_i = i;
last_b = b;
}
// 2 - Create the list of ScreenPatchRange, which are Paintable
final HashMap<Paintable, ScreenPatchRange<?>> screenPatchRanges = new HashMap<Paintable, ScreenPatchRange<?>>( originalPatches.size() );
for (final PatchRange range : ranges) {
if (0 == range.list.size()) continue;
final ScreenPatchRange<?> spr = createScreenPathRange(range, r, m);
for (Patch p : range.list) {
screenPatchRanges.put(p, spr);
}
}
// swap old for new:
GroupingMode.this.screenPatchRanges = screenPatchRanges;
painter.update();
}
}
/** @param r The srcRect
* @param m The magnification */
abstract protected void doPainterUpdate(Rectangle r, double m);
protected class Painter extends SimpleThread
{
void doit( final Rectangle r, final double m )
{
try {
doPainterUpdate(r, m);
} catch (Throwable e) {
IJError.print(e);
}
display.getCanvas().repaint( true );
}
}
protected abstract class SimpleThread extends Thread
{
private volatile boolean updateAgain = false;
SimpleThread() {
setPriority(Thread.NORM_PRIORITY);
try { setDaemon(true); } catch (Exception e) {}
}
public void run()
{
while ( !isInterrupted() )
{
final boolean b;
final Rectangle r;
final double m;
synchronized ( this )
{
b = updateAgain;
updateAgain = false;
r = ( Rectangle )srcRect.clone();
m = magnification;
}
if ( b )
{
doit( r, m );
}
synchronized ( this )
{
try
{
if ( !updateAgain ) wait();
}
catch ( InterruptedException e ){}
}
}
}
abstract void doit( final Rectangle r, final double m );
void update()
{
synchronized ( this )
{
updateAgain = true;
notify();
}
}
void quit()
{
interrupt();
synchronized ( this )
{
updateAgain = false;
notify();
}
}
}
public boolean canChangeLayer() { return false; }
public boolean canZoom() { return true; }
public boolean canPan() { return true; }
private void updated( final Rectangle srcRect, double magnification )
{
if (
this.srcRect.x == srcRect.x &&
this.srcRect.y == srcRect.y &&
this.srcRect.width == srcRect.width &&
this.srcRect.height == srcRect.height &&
this.magnification == magnification )
return;
this.srcRect = (Rectangle) srcRect.clone();
this.magnification = magnification;
updater.update();
}
public void srcRectUpdated( Rectangle srcRect, double magnification )
{
updated( srcRect, magnification );
}
public void magnificationUpdated( Rectangle srcRect, double magnification )
{
updated( srcRect, magnification );
}
public void redoOneStep() {}
public void undoOneStep() {}
@Override
public boolean cancel() {
painter.quit();
updater.quit();
return true;
}
public Rectangle getRepaintBounds() { return (Rectangle) srcRect.clone(); }
public GraphicsSource getGraphicsSource() {
return gs;
}
}