/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008 - 2010, Geomatys
*
* This library 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;
* version 2.1 of the License.
*
* This library 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.
*/
package org.geotoolkit.display2d.canvas;
import java.awt.*;
import java.awt.geom.Area;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.VolatileImage;
import java.util.logging.Level;
import org.geotoolkit.display.container.GraphicContainer;
import org.geotoolkit.display2d.GO2Utilities;
import org.geotoolkit.display2d.canvas.painter.SolidColorPainter;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.internal.Threads;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
/**
* Canvas based on a VolatileImage.
*
* @author Johann Sorel (Geomatys)
* @module
*/
public class J2DCanvasVolatile extends J2DCanvas{
private final GraphicsConfiguration GC;
private final DrawTask drawtask = new DrawTask();
private VolatileImage buffer0;
private Dimension dim;
private boolean mustupdate = false;
private Envelope wishedEnvelope = null;
private final Object LOCK = new Object();
private final Area dirtyArea = new Area();
public J2DCanvasVolatile(final CoordinateReferenceSystem crs, final Dimension dim){
this(crs,dim,null);
}
public J2DCanvasVolatile(final CoordinateReferenceSystem crs, final Dimension dim, final Hints hints){
super(crs,hints);
//we might not know our dimension until it is painted by a swing component for exemple.
if(dim != null){
setDisplayBounds(new Rectangle(dim));
}
this.dim = dim;
GC = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
painter = new SolidColorPainter(Color.WHITE);
}
@Override
public void dispose(){
super.dispose();
buffer0 = null;
dim = null;
}
/**
* Resize the volatile image, this will set to null the buffer.
* A new one will be created when repaint is called.
*/
public synchronized void resize(Dimension dim){
if(this.dim == null){
//first time we affect the size
this.dim = dim;
setDisplayBounds(new Rectangle(dim));
if(wishedEnvelope!=null){
try {
setVisibleArea(wishedEnvelope);
} catch (NoninvertibleTransformException ex) {
getLogger().log(Level.SEVERE, null, ex);
} catch (TransformException ex) {
getLogger().log(Level.SEVERE, null, ex);
}
wishedEnvelope = null;
return;
}
}else if(this.dim.equals(dim)){
//same size, nothing to do
return;
}
//ensure dimension is valid
if(dim.width<=0 || dim.height<=0){
dim = new Dimension(dim);
if(dim.width<=0)dim.width=1;
if(dim.height<=0)dim.height=1;
}
this.dim = dim;
setDisplayBounds(new Rectangle(dim));
buffer0 = null;
if(isAutoRepaint()){
repaint();
}
}
private synchronized VolatileImage createBackBuffer() {
if(dim == null){
return null;
}
return GC.createCompatibleVolatileImage(dim.width, dim.height, VolatileImage.OPAQUE);
}
private void render(Shape paintingDisplayShape){
if(paintingDisplayShape == null) paintingDisplayShape = getDisplayBounds();
final Graphics2D output;
synchronized(LOCK){
VolatileImage buffer;
if(buffer0 == null){
//create the buffer at the last possible moment
//or create a new one if we are already rendering
//TODO : find a way to stop previous thread
buffer = createBackBuffer();
if(buffer == null){
//size may not be knowned yet
return;
}
buffer.setAccelerationPriority(1);
output = (Graphics2D) buffer.getGraphics();
output.setComposite(GO2Utilities.ALPHA_COMPOSITE_0F);
output.fillRect(0,0,dim.width,dim.height);
}else{
buffer = buffer0;
//we clear the buffer part if it exists
output = (Graphics2D) buffer0.getGraphics();
output.setComposite(GO2Utilities.ALPHA_COMPOSITE_0F);
output.fill(paintingDisplayShape);
}
buffer0 = buffer;
output.setComposite(GO2Utilities.ALPHA_COMPOSITE_1F);
// Rectangle clipBounds = output.getClipBounds();
// /*
// * Sets a flag for avoiding some "refresh()" events while we are actually painting.
// * For example some implementation of the GraphicPrimitive2D.paint(...) method may
// * detects changes since the last rendering and invokes some kind of invalidate(...)
// * methods before the graphic rendering begin. Invoking those methods may cause in some
// * indirect way a call to GraphicPrimitive2D.refresh(), which will trig an other widget
// * repaint. This second repaint is usually not needed, since Graphics usually managed
// * to update their informations before they start their rendering. Consequently,
// * disabling repaint events while we are painting help to reduces duplicated rendering.
// */
// final Rectangle displayBounds = getDisplayBounds().getBounds();
// Rectangle2D dirtyArea = XRectangle2D.INFINITY;
// if (clipBounds == null) {
// clipBounds = displayBounds;
// } else if (displayBounds.contains(clipBounds)) {
// dirtyArea = clipBounds;
// }
// output.setClip(clipBounds);
// paintStarted(dirtyArea);
output.setClip(paintingDisplayShape);
//must be called outside of the lock or it may provoque a deadlock
prepareContext(context2D, output, paintingDisplayShape);
//paint background if there is one.
if(painter != null){
painter.paint(context2D);
}
}
monitor.renderingStarted();
fireRenderingStateChanged(RENDERING);
try{
final GraphicContainer container = getContainer();
if(container != null){
render(context2D, container.flatten(true));
}
}catch(Exception ex){
//volatile canvas must never lock itself.
monitor.exceptionOccured(ex, Level.WARNING);
}
//End painting
output.dispose();
fireRenderingStateChanged(ON_HOLD);
monitor.renderingFinished();
}
@Override
public Image getSnapShot(){
if(buffer0 != null){
return buffer0.getSnapshot();
}
return null;
}
public VolatileImage getVolatile(){
synchronized(LOCK){
return buffer0;
}
}
@Override
public synchronized void repaint(final Shape displayArea) {
//finish any previous painting
getMonitor().stopRendering();
this.dirtyArea.add(new Area(displayArea));
mustupdate = true;
Threads.executeWork(drawtask);
}
private class DrawTask implements Runnable {
@Override
public synchronized void run() {
if (!mustupdate) {
return;
}
mustupdate = false;
final Shape copy;
synchronized (dirtyArea) {
copy = (dirtyArea.isEmpty()) ? null : new Area(dirtyArea);
dirtyArea.reset();
}
render(copy);
}
}
@Override
public void setVisibleArea(final Envelope env) throws NoninvertibleTransformException, TransformException {
if(dim == null){
//we don't know our size yet, store the information for later
wishedEnvelope = env;
}else{
super.setVisibleArea(env);
}
}
}