001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/graphics/BlurImage.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstraße 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Jens Fitzke
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041     
042     ---------------------------------------------------------------------------*/
043    
044    package org.deegree.graphics;
045    
046    import java.awt.AlphaComposite;
047    import java.awt.Composite;
048    import java.awt.Graphics2D;
049    import java.awt.Polygon;
050    import java.awt.color.ColorSpace;
051    import java.awt.image.BufferedImage;
052    import java.awt.image.ColorConvertOp;
053    import java.awt.image.ConvolveOp;
054    import java.awt.image.Kernel;
055    import java.util.ArrayList;
056    import java.util.List;
057    
058    import org.deegree.framework.xml.XMLParsingException;
059    import org.deegree.graphics.transformation.WorldToScreenTransform;
060    import org.deegree.model.spatialschema.Envelope;
061    import org.deegree.model.spatialschema.GMLGeometryAdapter;
062    import org.deegree.model.spatialschema.Geometry;
063    import org.deegree.model.spatialschema.GeometryException;
064    import org.deegree.model.spatialschema.MultiSurface;
065    import org.deegree.model.spatialschema.Position;
066    import org.deegree.model.spatialschema.Ring;
067    import org.deegree.model.spatialschema.Surface;
068    
069    /**
070     * Display map surface depending on the security parameters. The rest of the Map Image will be
071     * blurred allowing the user a clear view of only the allowed surface.
072     * 
073     * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh</a>
074     * 
075     * @author last edited by: $Author: apoth $
076     * 
077     * @version 2.0, $Revision: 7823 $, $Date: 2007-07-24 10:00:09 +0200 (Di, 24 Jul 2007) $
078     * 
079     * @since 2.0
080     */
081    
082    public class BlurImage {
083    
084        /**
085         * Create a new BlurImage instance.
086         */
087        public BlurImage() {
088        }
089    
090        /**
091         * Render the surface geometry the user is allowed to see. The geometry must be within the
092         * bounding box of the map image.
093         * 
094         * 1. Geometry contains bbox -> no need to blur the image 2. Geometry complety within bbox. 3.
095         * BBOX intersects Geometry a. Returns a MultiSurface b. Returns a Surface 4. BBOX disjunkt
096         * Geometry
097         * 
098         * @param image
099         * @param bbox
100         * @param geom
101         * @return BufferedImage
102         * @throws GeometryException
103         * @throws XMLParsingException
104         */
105        public BufferedImage renderUserRealm( BufferedImage image, Envelope bbox, Geometry geom )
106                                throws GeometryException, XMLParsingException {
107    
108            int blurScale = 8;
109            float alpha = 0.2f;
110    
111            // Create output image
112            BufferedImage output = null;
113            if ( image.getType() == BufferedImage.TYPE_INT_RGB ) {
114                System.out.println( "setting rgb" );
115                output = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB );
116            } else {
117                System.out.println( "setting rgba" );
118                output = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB );
119            }
120    
121            // Transform the world coordinates to screen coordinates.
122            WorldToScreenTransform wsTransform = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(),
123                                                                             bbox.getMax().getX(), bbox.getMax().getY(),
124                                                                             image.getMinX(), image.getMinY(),
125                                                                             image.getWidth(), image.getHeight() );
126    
127            Graphics2D graphics = output.createGraphics();
128            Composite composite = graphics.getComposite();
129            graphics.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
130            // blur image, along with the blur scale.
131            graphics.drawImage( blur( grayScale( image ), blurScale ), null, image.getMinX(), image.getMinY() );
132            graphics.setComposite( composite );
133            try {
134                // convert bbox to geometry.
135                StringBuffer envelope = GMLGeometryAdapter.exportAsBox( bbox );
136                Geometry boundingBOX = GMLGeometryAdapter.wrap( envelope.toString(), null );
137                Geometry intersection = boundingBOX.intersection( geom );
138                if ( intersection instanceof Surface ) {
139                    Surface surface = (Surface) intersection;
140                    Polygon polygon = retrieveSurfacePolygon( surface, wsTransform );
141                    graphics = renderClip( graphics, polygon, image );
142                } else if ( intersection instanceof MultiSurface ) {
143                    MultiSurface multiSurface = (MultiSurface) intersection;
144                    Surface[] surfaces = multiSurface.getAllSurfaces();
145                    for ( int i = 0; i < surfaces.length; i++ ) {
146                        Surface surface = surfaces[i];
147                        Polygon polygon = retrieveSurfacePolygon( surface, wsTransform );
148                        graphics = renderClip( graphics, polygon, image );
149                    }
150                }
151            } catch ( GeometryException e ) {
152                throw new GeometryException( "Error creating a geometry from the bounding box. " + e );
153            } catch ( XMLParsingException e ) {
154                throw new XMLParsingException( "Error exporting the bounding box to its " + "string format. " + e );
155            }
156            graphics.dispose();
157            return output;
158        }
159    
160        /**
161         * Render the clip image on the output graphics context.
162         * 
163         * @param graphics
164         * @param polygon
165         * @param image
166         * @return Graphics2D
167         */
168        private Graphics2D renderClip( Graphics2D graphics, Polygon polygon, BufferedImage image ) {
169    
170            // clip the region which the user is allowed to see
171            graphics.setClip( polygon );
172            // draw region
173            graphics.drawImage( image, null, image.getMinX(), image.getMinY() );
174    
175            return graphics;
176    
177        }
178    
179        /**
180         * Retrieve the surface as a java.awt.Polygon. The exterior and interior rings are retrieved and
181         * the coordinates transformed to screen coordinates.
182         * 
183         * @param surface
184         * @param wsTransform
185         * @return Polygon
186         */
187        private Polygon retrieveSurfacePolygon( Surface surface, WorldToScreenTransform wsTransform ) {
188    
189            Ring exteriorRing = surface.getSurfaceBoundary().getExteriorRing();
190            Position[] exteriorPositions = exteriorRing.getPositions();
191            Ring[] interiorRings = surface.getSurfaceBoundary().getInteriorRings();
192    
193            Position[][] interiorPositions;
194            if ( interiorRings != null ) {
195                interiorPositions = new Position[interiorRings.length][];
196                for ( int i = 0; i < interiorPositions.length; i++ ) {
197                    interiorPositions[i] = interiorRings[i].getPositions();
198                }
199            } else {
200                interiorPositions = new Position[0][];
201            }
202    
203            int[] xArray = getXArray( exteriorPositions, interiorPositions, wsTransform );
204            int[] yArray = getYArray( exteriorPositions, interiorPositions, wsTransform );
205    
206            Polygon polygon = new Polygon( xArray, yArray, xArray.length );
207    
208            return polygon;
209        }
210    
211        /**
212         * Retrieve the array of x-coordinates after transformation to screen coordinates.
213         * 
214         * @param exteriorRing
215         * @param interiorRing
216         * @param wsTransform
217         * @return int[]
218         */
219        private int[] getXArray( Position[] exteriorRing, Position[][] interiorRing, WorldToScreenTransform wsTransform ) {
220    
221            List<Double> xList = new ArrayList<Double>();
222            for ( int i = 0; i < exteriorRing.length; i++ ) {
223                Position position = exteriorRing[i];
224                xList.add( wsTransform.getDestX( position.getX() ) );
225            }
226            for ( int i = 0; i < interiorRing.length; i++ ) {
227                Position[] positions = interiorRing[i];
228                for ( int j = 0; j < positions.length; j++ ) {
229                    Position position = positions[j];
230                    xList.add( wsTransform.getDestX( position.getX() ) );
231                }
232            }
233    
234            int[] xArray = new int[xList.size()];
235            for ( int i = 0; i < xList.size(); i++ ) {
236                Double tmp = xList.get( i );
237                xArray[i] = tmp.intValue();
238            }
239            return xArray;
240        }
241    
242        /**
243         * Retrieve the array of y-coordinates after transformation to screen coordinates.
244         * 
245         * @param exteriorRing
246         * @param interiorRing
247         * @param wsTransform
248         * @return int[]
249         */
250        private int[] getYArray( Position[] exteriorRing, Position[][] interiorRing, WorldToScreenTransform wsTransform ) {
251    
252            List<Double> yList = new ArrayList<Double>();
253            for ( int i = 0; i < exteriorRing.length; i++ ) {
254                Position position = exteriorRing[i];
255                yList.add( wsTransform.getDestY( position.getY() ) );
256            }
257            for ( int i = 0; i < interiorRing.length; i++ ) {
258                Position[] positions = interiorRing[i];
259                for ( int j = 0; j < positions.length; j++ ) {
260                    Position position = positions[j];
261                    yList.add( wsTransform.getDestY( position.getY() ) );
262                }
263            }
264    
265            int[] yArray = new int[yList.size()];
266            for ( int i = 0; i < yList.size(); i++ ) {
267                Double tmp = yList.get( i );
268                yArray[i] = tmp.intValue();
269            }
270            return yArray;
271        }
272    
273        /**
274         * Blur effect carried out on the image. The blur scale defines the intensity of the blur.
275         * 
276         * @param image
277         * @param blurScale
278         * @return BufferedImage
279         */
280        private BufferedImage blur( BufferedImage image, int blurScale ) {
281    
282            BufferedImage destination = null;
283            if ( image.getType() == BufferedImage.TYPE_INT_RGB ) {
284                destination = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB );
285            } else {
286                destination = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB );
287            }
288    
289            float[] data = new float[blurScale * blurScale];
290            float value = 1.0f / ( blurScale * blurScale );
291            for ( int i = 0; i < data.length; i++ ) {
292                data[i] = value;
293            }
294            Kernel kernel = new Kernel( blurScale, blurScale, data );
295            ConvolveOp convolve = new ConvolveOp( kernel, ConvolveOp.EDGE_NO_OP, null );
296            convolve.filter( image, destination );
297    
298            return destination;
299        }
300    
301        /**
302         * Convert BufferedImage RGB to black and white image
303         * 
304         * @param image
305         * @return BufferedImage
306         */
307        private BufferedImage grayScale( BufferedImage image ) {
308    
309            BufferedImage destination = null;
310            if ( image.getType() == BufferedImage.TYPE_INT_RGB ) {
311                destination = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB );
312            } else {
313                destination = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB );
314            }
315            ColorConvertOp colorConvert = new ColorConvertOp( ColorSpace.getInstance( ColorSpace.CS_GRAY ), null );
316            colorConvert.filter( image, destination );
317    
318            return destination;
319        }
320    
321    }