/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.renderer.lite.gridcoverage2d; // J2SE dependencies import java.awt.AlphaComposite; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.image.BufferedImage; import java.awt.image.ImagingOpException; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.media.jai.Interpolation; import javax.media.jai.InterpolationNearest; import javax.media.jai.JAI; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.processing.DefaultProcessor; import org.geotools.coverage.processing.operation.Crop; import org.geotools.coverage.processing.operation.Resample; import org.geotools.coverage.processing.operation.Scale; import org.geotools.factory.Hints; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.image.ImageWorker; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.referencing.operation.builder.GridToEnvelopeMapper; import org.geotools.referencing.operation.matrix.XAffineTransform; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.resources.image.ImageUtilities; import org.geotools.styling.RasterSymbolizer; import org.opengis.coverage.grid.GridCoverage; import org.opengis.filter.expression.Expression; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransform2D; import org.opengis.referencing.operation.TransformException; import com.vividsolutions.jts.geom.Envelope; /** * A helper class for rendering {@link GridCoverage} objects. Support for grid coverage SLD stylers is still limited. * @author Simone Giannecchini * @author Andrea Aime * @author Alessio Fabiani * @source $URL$ * @version $Id$ * @task Add support for SLD styles */ @SuppressWarnings("deprecation") public final class GridCoverageRenderer { /** * Helper function * * @param symbolizer */ static float getOpacity(RasterSymbolizer symbolizer) { float alpha = 1.0f; Expression exp = symbolizer.getOpacity(); if (exp == null){ return alpha; } Number number = (Number) exp.evaluate(null,Float.class); if (number == null){ return alpha; } return number.floatValue(); } /** * This variable is use for testing purposes in order to force this * {@link GridCoverageRenderer} to dump images at various steps on the disk. */ private static boolean DEBUG = Boolean .getBoolean("org.geotools.renderer.lite.gridcoverage2d.debug"); private static String debugDir; static { if (DEBUG) { final File tempDir = new File(System.getProperty("user.home"),"gt-renderer"); if (!tempDir.exists() ) { if(!tempDir.mkdir()) System.out .println("Unable to create debug dir, exiting application!!!"); DEBUG=false; debugDir = null; } else { debugDir = tempDir.getAbsolutePath(); System.out.println("Debug dir "+debugDir); } } } /** Cached factory for the {@link Crop} operation. */ private final static Crop coverageCropFactory = new Crop(); /** Logger. */ private static final Logger LOGGER = org.geotools.util.logging.Logging .getLogger("org.geotools.rendering"); static { // /////////////////////////////////////////////////////////////////// // // Caching parameters for performing the various operations. // // /////////////////////////////////////////////////////////////////// final DefaultProcessor processor = new DefaultProcessor(new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE)); resampleParams = processor.getOperation("Resample").getParameters(); cropParams = processor.getOperation("CoverageCrop").getParameters(); } /** The Display (User defined) CRS * */ private final CoordinateReferenceSystem destinationCRS; /** Area we want to draw. */ private final GeneralEnvelope destinationEnvelope; /** Size of the area we want to draw in pixels. */ private final Rectangle destinationSize; private final AffineTransform finalGridToWorld; private final AffineTransform finalWorldToGrid; private final Hints hints = new Hints(); /** Parameters used to control the {@link Resample} operation. */ private final static ParameterValueGroup resampleParams; /** Parameters used to control the {@link Crop} operation. */ private static ParameterValueGroup cropParams; /** Parameters used to control the {@link Scale} operation. */ private static final Resample resampleFactory = new Resample(); /** * Creates a new {@link GridCoverageRenderer} object. * * @param destinationCRS * the CRS of the {@link GridCoverage2D} to render. * @param envelope * delineating the area to be rendered. * @param screenSize * at which we want to rendere the source * {@link GridCoverage2D}. * @param worldToScreen if not <code>null</code> and if it contains a rotation, * this Affine Tranform is used directly to convert from world coordinates * to screen coordinates. Otherwise, a standard {@link GridToEnvelopeMapper} * is used to calculate the affine transform. * * @throws TransformException * @throws NoninvertibleTransformException * @deprecated Use {@link GridCoverageRenderer#GridCoverageRenderer(CoordinateReferenceSystem, Envelope, Rectangle, AffineTransform, RenderingHints)} * instead */ public GridCoverageRenderer(final CoordinateReferenceSystem destinationCRS, final Envelope envelope, Rectangle screenSize) throws TransformException, NoninvertibleTransformException { this(destinationCRS, envelope, screenSize, null, null); } /** * Creates a new {@link GridCoverageRenderer} object. * * @param destinationCRS * the CRS of the {@link GridCoverage2D} to render. * @param envelope * delineating the area to be rendered. * @param screenSize * at which we want to rendere the source * {@link GridCoverage2D}. * @param worldToScreen if not <code>null</code> and if it contains a rotation, * this Affine Tranform is used directly to convert from world coordinates * to screen coordinates. Otherwise, a standard {@link GridToEnvelopeMapper} * is used to calculate the affine transform. * * @throws TransformException * @throws NoninvertibleTransformException */ public GridCoverageRenderer(final CoordinateReferenceSystem destinationCRS, final Envelope envelope, Rectangle screenSize, AffineTransform worldToScreen) throws TransformException, NoninvertibleTransformException { this(destinationCRS, envelope, screenSize, worldToScreen, null); } /** * Creates a new {@link GridCoverageRenderer} object. * * @param destinationCRS * the CRS of the {@link GridCoverage2D} to render. * @param envelope * delineating the area to be rendered. * @param screenSize * at which we want to rendere the source * {@link GridCoverage2D}. * @param java2dHints * to control this rendering process. * @throws TransformException * @throws NoninvertibleTransformException * @deprecated Use {@link GridCoverageRenderer#GridCoverageRenderer(CoordinateReferenceSystem, Envelope, Rectangle, AffineTransform, RenderingHints)} * instead */ public GridCoverageRenderer(final CoordinateReferenceSystem destinationCRS, final Envelope envelope, Rectangle screenSize, RenderingHints java2dHints) throws TransformException, NoninvertibleTransformException { this(destinationCRS, envelope, screenSize, null,java2dHints); } /** * Creates a new {@link GridCoverageRenderer} object. * * @param destinationCRS * the CRS of the {@link GridCoverage2D} to render. * @param envelope * delineating the area to be rendered. * @param screenSize * at which we want to rendere the source * {@link GridCoverage2D}. * @param worldToScreen if not <code>null</code> and if it contains a rotation, * this Affine Tranform is used directly to convert from world coordinates * to screen coordinates. Otherwise, a standard {@link GridToEnvelopeMapper} * is used to calculate the affine transform. * * @param java2dHints * to control this rendering process. * @throws TransformException * @throws NoninvertibleTransformException */ public GridCoverageRenderer(final CoordinateReferenceSystem destinationCRS, final Envelope envelope, Rectangle screenSize, AffineTransform worldToScreen, RenderingHints java2dHints) throws TransformException, NoninvertibleTransformException { // /////////////////////////////////////////////////////////////////// // // Initialize this renderer // // /////////////////////////////////////////////////////////////////// this.destinationSize = screenSize; this.destinationCRS = CRS.getHorizontalCRS(destinationCRS); if (this.destinationCRS == null) throw new TransformException(Errors.format( ErrorKeys.CANT_SEPARATE_CRS_$1, destinationCRS)); final GridToEnvelopeMapper gridToEnvelopeMapper = new GridToEnvelopeMapper(); gridToEnvelopeMapper.setPixelAnchor(PixelInCell.CELL_CORNER); gridToEnvelopeMapper.setGridRange(new GridEnvelope2D(destinationSize)); destinationEnvelope = new GeneralEnvelope(new ReferencedEnvelope(envelope, destinationCRS)); // /////////////////////////////////////////////////////////////////// // // FINAL DRAWING DIMENSIONS AND RESOLUTION // I am here getting the final drawing dimensions (on the device) and // the resolution for this rendererbut in the CRS of the source coverage // since I am going to compare this info with the same info for the // source coverage. // // /////////////////////////////////////////////////////////////////// gridToEnvelopeMapper.setEnvelope(destinationEnvelope); // PHUSTAD : The gridToEnvelopeMapper does not handle rotated views. // if (worldToScreen != null && XAffineTransform.getRotation(worldToScreen) != 0.0) { finalWorldToGrid = new AffineTransform(worldToScreen); finalGridToWorld = finalWorldToGrid.createInverse(); } else { finalGridToWorld = new AffineTransform(gridToEnvelopeMapper.createAffineTransform()); finalWorldToGrid = finalGridToWorld.createInverse(); } // /////////////////////////////////////////////////////////////////// // // HINTS // // /////////////////////////////////////////////////////////////////// if (java2dHints != null) this.hints.add(java2dHints); // this prevents users from overriding lenient hint this.hints.put(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE); this.hints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL); } /** * Reprojecting the input coverage using the provided parameters. * * @param gc * @param crs * @param interpolation * @return * @throws FactoryException */ private static GridCoverage2D resample(final GridCoverage2D gc, CoordinateReferenceSystem crs, final Interpolation interpolation, final GeneralEnvelope destinationEnvelope, final Hints hints) throws FactoryException { // paranoiac check assert CRS.equalsIgnoreMetadata(destinationEnvelope .getCoordinateReferenceSystem(), crs) || CRS .findMathTransform( destinationEnvelope .getCoordinateReferenceSystem(), crs) .isIdentity(); final ParameterValueGroup param = (ParameterValueGroup) resampleParams .clone(); param.parameter("source").setValue(gc); param.parameter("CoordinateReferenceSystem").setValue(crs); param.parameter("InterpolationType").setValue(interpolation); return (GridCoverage2D) resampleFactory.doOperation(param, hints); } /** * Cropping the provided coverage to the requested geographic area. * * @param gc * @param envelope * @param crs * @return */ private static GridCoverage2D getCroppedCoverage(GridCoverage2D gc, GeneralEnvelope envelope, CoordinateReferenceSystem crs, final Hints hints) { final GeneralEnvelope oldEnvelope = (GeneralEnvelope) gc.getEnvelope(); // intersect the envelopes in order to prepare for crooping the coverage // down to the neded resolution final GeneralEnvelope intersectionEnvelope = new GeneralEnvelope( envelope); intersectionEnvelope.setCoordinateReferenceSystem(crs); intersectionEnvelope.intersect((GeneralEnvelope) oldEnvelope); // Do we have something to show? After the crop I could get a null // coverage which would mean nothing to show. if (intersectionEnvelope.isEmpty()) return null; // crop final ParameterValueGroup param = (ParameterValueGroup) cropParams .clone(); param.parameter("source").setValue(gc); param.parameter("Envelope").setValue(intersectionEnvelope); return (GridCoverage2D) coverageCropFactory.doOperation(param, hints); } /** * Paint this grid coverage. The caller must ensure that * <code>graphics</code> has an affine transform mapping "real world" * coordinates in the coordinate system given by {@link * #getCoordinateSystem}. * * @param graphics * the {@link Graphics2D} context in which to paint. * @param metaBufferedEnvelope * @throws FactoryException * @throws TransformException * @throws NoninvertibleTransformException * @throws Exception * @throws UnsupportedOperationException * if the transformation from grid to coordinate system in * the GridCoverage is not an AffineTransform */ public void paint( final Graphics2D graphics, final GridCoverage2D gridCoverage, final RasterSymbolizer symbolizer) throws FactoryException, TransformException, NoninvertibleTransformException { // /////////////////////////////////////////////////////////////////// // // Initial checks // // /////////////////////////////////////////////////////////////////// if(graphics==null) throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"graphics")); if(gridCoverage==null) throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"gridCoverage")); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(new StringBuilder("Drawing coverage ").append(gridCoverage.toString()).toString()); // /////////////////////////////////////////////////////////////////// // // Getting information about the source coverage like the source CRS, // the source envelope and the source geometry. // // /////////////////////////////////////////////////////////////////// final CoordinateReferenceSystem sourceCoverageCRS = gridCoverage.getCoordinateReferenceSystem2D(); final GeneralEnvelope sourceCoverageEnvelope = (GeneralEnvelope) gridCoverage.getEnvelope(); // /////////////////////////////////////////////////////////////////// // // GET THE CRS MAPPING // // This step I instantiate the MathTransform for going from the source // crs to the destination crs. // // /////////////////////////////////////////////////////////////////// // math transform from source to target crs final MathTransform sourceCRSToDestinationCRSTransformation = CRS.findMathTransform(sourceCoverageCRS, destinationCRS, true); final MathTransform destinationCRSToSourceCRSTransformation = sourceCRSToDestinationCRSTransformation.inverse(); final boolean doReprojection = !sourceCRSToDestinationCRSTransformation.isIdentity(); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine( new StringBuilder("Transforming coverage envelope with transform ").append(destinationCRSToSourceCRSTransformation.toWKT()).toString()); // // // // Do we need reprojection? // // // GeneralEnvelope destinationEnvelopeInSourceCRS; if (doReprojection) { // ///////////////////////////////////////////////////////////////////// // // PHASE 1 // // PREPARING THE REQUESTED ENVELOPE FOR LATER INTERSECTION // // ///////////////////////////////////////////////////////////////////// // // // // Try to convert the destination envelope in the source crs. If // this fails we pass through WGS84 as an intermediate step // // // try { // convert the destination envelope to the source coverage // native crs in order to try and crop it. If we get an error we // try to // do this in two steps using WGS84 as a pivot. This introduces // some erros (it usually // increases the envelope we want to check) but it is still // useful. destinationEnvelopeInSourceCRS = CRS.transform( destinationCRSToSourceCRSTransformation, destinationEnvelope); } catch (TransformException te) { // // // // Convert the destination envelope to WGS84 if needed for safer // comparisons later on with the original crs of this coverage. // // // final GeneralEnvelope destinationEnvelopeWGS84; if (!CRS.equalsIgnoreMetadata(destinationCRS, DefaultGeographicCRS.WGS84)) { // get a math transform to go to WGS84 final MathTransform destinationCRSToWGS84transformation = CRS .findMathTransform(destinationCRS, DefaultGeographicCRS.WGS84, true); if (!destinationCRSToWGS84transformation.isIdentity()) { destinationEnvelopeWGS84 = CRS.transform( destinationCRSToWGS84transformation, destinationEnvelope); destinationEnvelopeWGS84 .setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); } else { destinationEnvelopeWGS84 = new GeneralEnvelope( destinationEnvelope); } } else { destinationEnvelopeWGS84 = new GeneralEnvelope( destinationEnvelope); } // // // // Convert the requested envelope from WGS84 to the source crs // for cropping the provided coverage. // // // if (!CRS.equalsIgnoreMetadata(sourceCoverageCRS, DefaultGeographicCRS.WGS84)) { // get a math transform to go to WGS84 final MathTransform WGS84ToSourceCoverageCRSTransformation = CRS .findMathTransform(DefaultGeographicCRS.WGS84, sourceCoverageCRS, true); if (!WGS84ToSourceCoverageCRSTransformation.isIdentity()) { destinationEnvelopeInSourceCRS = CRS.transform( WGS84ToSourceCoverageCRSTransformation, destinationEnvelopeWGS84); destinationEnvelopeInSourceCRS .setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); } else { destinationEnvelopeInSourceCRS = new GeneralEnvelope( destinationEnvelopeWGS84); } } else { destinationEnvelopeInSourceCRS = new GeneralEnvelope( destinationEnvelopeWGS84); } } } else destinationEnvelopeInSourceCRS = new GeneralEnvelope(destinationEnvelope); // ///////////////////////////////////////////////////////////////////// // // NOW CHECKING THE INTERSECTION IN WGS84 // // // // // If the two envelopes intersect each other in WGS84 we are // reasonably sure that they intersect // // ///////////////////////////////////////////////////////////////////// final GeneralEnvelope intersectionEnvelope = new GeneralEnvelope(destinationEnvelopeInSourceCRS); intersectionEnvelope.intersect(sourceCoverageEnvelope); if (intersectionEnvelope.isEmpty()||intersectionEnvelope.isNull()) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER .info("The destination envelope does not intersect the envelope of the source coverage."); } return; } final Interpolation interpolation = (Interpolation) hints.get(JAI.KEY_INTERPOLATION); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(new StringBuilder("Using interpolation ").append(interpolation).toString()); // ///////////////////////////////////////////////////////////////////// // // CROPPING Coverage // // ///////////////////////////////////////////////////////////////////// GridCoverage2D preResample=gridCoverage; try{ preResample = getCroppedCoverage(gridCoverage, intersectionEnvelope, sourceCoverageCRS,this.hints); if (preResample == null) { // nothing to render, the AOI does not overlap if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine( new StringBuilder("Skipping current coverage because cropped to an empty area").toString()); return; } }catch (Throwable t) { //// // // If it happens that the crop fails we try to proceed since the crop does only an optimization. Things might // work out anyway. // //// if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(new StringBuilder("Crop Failed for reason: ").append(t.getLocalizedMessage()).toString()); preResample=gridCoverage; } if (DEBUG) { writeRenderedImage(preResample.geophysics(false).getRenderedImage(),"preresample"); } // ///////////////////////////////////////////////////////////////////// // // Reproject // // ///////////////////////////////////////////////////////////////////// GridCoverage2D preSymbolizer; if (doReprojection) { preSymbolizer = resample(preResample, destinationCRS,interpolation == null ? new InterpolationNearest(): interpolation, destinationEnvelope,this.hints); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(new StringBuilder("Reprojecting to crs ").append( destinationCRS.toWKT()).toString()); } else preSymbolizer = preResample; if (DEBUG) { writeRenderedImage(preSymbolizer.geophysics(false).getRenderedImage(),"preSymbolizer"); } // /////////////////////////////////////////////////////////////////// // // Apply RasterSymbolizer // // /////////////////////////////////////////////////////////////////// if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(new StringBuilder("Raster Symbolizer ").toString()); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(new StringBuffer("Raster Symbolizer ").toString()); final RenderedImage finalImage; final GridCoverage2D finalGC; if(symbolizer!=null){ final RasterSymbolizerHelper rsp = new RasterSymbolizerHelper (preSymbolizer,this.hints); rsp.visit(symbolizer); finalGC = (GridCoverage2D) rsp.getOutput(); finalImage = finalGC.geophysics(false).getRenderedImage(); } else{ finalGC=preSymbolizer; finalImage=finalGC.geophysics(false).getRenderedImage(); } // /////////////////////////////////////////////////////////////////// // // DRAW ME // I need the grid to world transform for drawing this grid coverage to // the display // // /////////////////////////////////////////////////////////////////// final GridGeometry2D recoloredCoverageGridGeometry = ((GridGeometry2D) finalGC.getGridGeometry()); // I need to translate half of a pixel since in wms the envelope // map to the corners of the raster space not to the center of the // pixels. final MathTransform2D finalGCTransform=recoloredCoverageGridGeometry.getGridToCRS2D(PixelOrientation.UPPER_LEFT); if (!(finalGCTransform instanceof AffineTransform)) { throw new UnsupportedOperationException( "Non-affine transformations not yet implemented"); // TODO } final AffineTransform finalGCgridToWorld = new AffineTransform((AffineTransform) finalGCTransform); // // // // I am going to concatenate the final world to grid transform for the // screen area with the grid to world transform of the input coverage. // // This way i right away position the coverage at the right place in the // area of interest for the device. // // // final AffineTransform clonedFinalWorldToGrid = (AffineTransform) finalWorldToGrid.clone(); clonedFinalWorldToGrid.concatenate(finalGCgridToWorld); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(new StringBuilder("clonedFinalWorldToGrid ").append(clonedFinalWorldToGrid.toString()).toString()); final RenderingHints oldHints = graphics.getRenderingHints(); graphics.setRenderingHints(this.hints); // // // Opacity // // final float alpha = getOpacity(symbolizer); final Composite oldAlphaComposite = graphics.getComposite(); graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); try { //debug if (DEBUG) { writeRenderedImage(finalImage,"final"); } // // // Drawing the Image // // graphics.drawRenderedImage(finalImage, clonedFinalWorldToGrid); } catch (Throwable t) { try { //log the error if(LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE,t.getLocalizedMessage(),t); // ///////////////////////////////////////////////////////////// // this is a workaround for a bug in Java2D, we need to convert // the image to component color model to make it work just fine. // ///////////////////////////////////////////////////////////// if(t instanceof IllegalArgumentException){ if (DEBUG) { writeRenderedImage(finalImage,"preWORKAROUND1"); } final RenderedImage image = new ImageWorker(finalImage).forceComponentColorModel(true).getRenderedImage(); if (DEBUG) { writeRenderedImage(image,"WORKAROUND1"); } graphics.drawRenderedImage(image, clonedFinalWorldToGrid); } else if(t instanceof ImagingOpException) // ///////////////////////////////////////////////////////////// // this is a workaround for a bug in Java2D // (see bug 4723021 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4723021). // // AffineTransformOp.filter throws a // java.awt.image.ImagingOpException: Unable to tranform src // image when a PixelInterleavedSampleModel is used. // // CUSTOMER WORKAROUND : // draw the BufferedImage into a buffered image of type ARGB // then perform the affine transform. THIS OPERATION WASTES // RESOURCES BY PERFORMING AN ALLOCATION OF MEMORY AND A COPY ON // LARGE IMAGES. // ///////////////////////////////////////////////////////////// { BufferedImage buf = finalImage.getColorModel().hasAlpha()? new BufferedImage(finalImage.getWidth(), finalImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR): new BufferedImage(finalImage.getWidth(), finalImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR); if (DEBUG) { writeRenderedImage(buf,"preWORKAROUND2"); } final Graphics2D g = (Graphics2D) buf.getGraphics(); final int translationX=finalImage.getMinX(),translationY=finalImage.getMinY(); g.drawRenderedImage(finalImage, AffineTransform.getTranslateInstance(-translationX, -translationY)); g.dispose(); if (DEBUG) { writeRenderedImage(buf,"WORKAROUND2"); } clonedFinalWorldToGrid.concatenate(AffineTransform.getTranslateInstance(translationX, translationY)); graphics.drawImage(buf, clonedFinalWorldToGrid, null); //release buf.flush(); buf=null; } else //log the error if(LOGGER.isLoggable(Level.FINE)) LOGGER.fine("Unable to renderer this raster, no workaround found"); } catch (Throwable t1) { // if the workaround fails again, there is really nothing to do // :-( LOGGER.log(Level.WARNING, t1.getLocalizedMessage(), t1); } } // /////////////////////////////////////////////////////////////////// // // Restore old elements // // /////////////////////////////////////////////////////////////////// graphics.setComposite(oldAlphaComposite); graphics.setRenderingHints(oldHints); } /** * Write the provided {@link RenderedImage} in the debug directory with the provided file name. * @param raster the {@link RenderedImage} that we have to write. * @param fileName a {@link String} indicating where we should write it. */ private void writeRenderedImage(final RenderedImage raster,final String fileName) { if(debugDir==null) throw new NullPointerException("Unable to write the provided coverage in the debug directory"); if(DEBUG==false) throw new IllegalStateException("Unable to write the provided coverage since we are not in debug mode"); try { ImageIO.write( raster, "tiff", new File(debugDir,fileName+".tiff")); } catch (IOException e) { LOGGER.log(Level.SEVERE,e.getLocalizedMessage(),e); } } }