001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/graphics/BlurImage.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 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     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
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    
045    package org.deegree.graphics;
046    
047    import java.awt.AlphaComposite;
048    import java.awt.Composite;
049    import java.awt.Graphics2D;
050    import java.awt.Polygon;
051    import java.awt.color.ColorSpace;
052    import java.awt.image.BufferedImage;
053    import java.awt.image.ColorConvertOp;
054    import java.awt.image.ConvolveOp;
055    import java.awt.image.Kernel;
056    import java.util.ArrayList;
057    import java.util.List;
058    
059    import org.deegree.framework.xml.XMLParsingException;
060    import org.deegree.graphics.transformation.WorldToScreenTransform;
061    import org.deegree.model.spatialschema.Envelope;
062    import org.deegree.model.spatialschema.GMLGeometryAdapter;
063    import org.deegree.model.spatialschema.Geometry;
064    import org.deegree.model.spatialschema.GeometryException;
065    import org.deegree.model.spatialschema.MultiSurface;
066    import org.deegree.model.spatialschema.Position;
067    import org.deegree.model.spatialschema.Ring;
068    import org.deegree.model.spatialschema.Surface;
069    
070    /**
071     * Display map surface depending on the security parameters. The rest of the Map Image will be
072     * blurred allowing the user a clear view of only the allowed surface.
073     * 
074     * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh</a>
075     * 
076     * @author last edited by: $Author: apoth $
077     * 
078     * @version 2.0, $Revision: 9340 $, $Date: 2007-12-27 13:32:12 +0100 (Do, 27 Dez 2007) $
079     * 
080     * @since 2.0
081     */
082    
083    public class BlurImage {
084    
085        /**
086         * Create a new BlurImage instance.
087         */
088        public BlurImage() {
089        }
090    
091        /**
092         * Render the surface geometry the user is allowed to see. The geometry must be within the
093         * bounding box of the map image.
094         * 
095         * 1. Geometry contains bbox -> no need to blur the image 2. Geometry complety within bbox. 3.
096         * BBOX intersects Geometry a. Returns a MultiSurface b. Returns a Surface 4. BBOX disjunkt
097         * Geometry
098         * 
099         * @param image
100         * @param bbox
101         * @param geom
102         * @return BufferedImage
103         * @throws GeometryException
104         * @throws XMLParsingException
105         */
106        public BufferedImage renderUserRealm( BufferedImage image, Envelope bbox, Geometry geom )
107                                throws GeometryException, XMLParsingException {
108    
109            int blurScale = 8;
110            float alpha = 0.2f;
111    
112            // Create output image
113            BufferedImage output = null;
114            if ( image.getType() == BufferedImage.TYPE_INT_RGB ) {
115                System.out.println( "setting rgb" );
116                output = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB );
117            } else {
118                System.out.println( "setting rgba" );
119                output = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB );
120            }
121    
122            // Transform the world coordinates to screen coordinates.
123            WorldToScreenTransform wsTransform = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(),
124                                                                             bbox.getMax().getX(), bbox.getMax().getY(),
125                                                                             image.getMinX(), image.getMinY(),
126                                                                             image.getWidth(), image.getHeight() );
127    
128            Graphics2D graphics = output.createGraphics();
129            Composite composite = graphics.getComposite();
130            graphics.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
131            // blur image, along with the blur scale.
132            graphics.drawImage( blur( grayScale( image ), blurScale ), null, image.getMinX(), image.getMinY() );
133            graphics.setComposite( composite );
134            try {
135                // convert bbox to geometry.
136                StringBuffer envelope = GMLGeometryAdapter.exportAsBox( bbox );
137                Geometry boundingBOX = GMLGeometryAdapter.wrap( envelope.toString(), null );
138                Geometry intersection = boundingBOX.intersection( geom );
139                if ( intersection instanceof Surface ) {
140                    Surface surface = (Surface) intersection;
141                    Polygon polygon = retrieveSurfacePolygon( surface, wsTransform );
142                    graphics = renderClip( graphics, polygon, image );
143                } else if ( intersection instanceof MultiSurface ) {
144                    MultiSurface multiSurface = (MultiSurface) intersection;
145                    Surface[] surfaces = multiSurface.getAllSurfaces();
146                    for ( int i = 0; i < surfaces.length; i++ ) {
147                        Surface surface = surfaces[i];
148                        Polygon polygon = retrieveSurfacePolygon( surface, wsTransform );
149                        graphics = renderClip( graphics, polygon, image );
150                    }
151                }
152            } catch ( GeometryException e ) {
153                throw new GeometryException( "Error creating a geometry from the bounding box. " + e );
154            } catch ( XMLParsingException e ) {
155                throw new XMLParsingException( "Error exporting the bounding box to its " + "string format. " + e );
156            }
157            graphics.dispose();
158            return output;
159        }
160    
161        /**
162         * Render the clip image on the output graphics context.
163         * 
164         * @param graphics
165         * @param polygon
166         * @param image
167         * @return Graphics2D
168         */
169        private Graphics2D renderClip( Graphics2D graphics, Polygon polygon, BufferedImage image ) {
170    
171            // clip the region which the user is allowed to see
172            graphics.setClip( polygon );
173            // draw region
174            graphics.drawImage( image, null, image.getMinX(), image.getMinY() );
175    
176            return graphics;
177    
178        }
179    
180        /**
181         * Retrieve the surface as a java.awt.Polygon. The exterior and interior rings are retrieved and
182         * the coordinates transformed to screen coordinates.
183         * 
184         * @param surface
185         * @param wsTransform
186         * @return Polygon
187         */
188        private Polygon retrieveSurfacePolygon( Surface surface, WorldToScreenTransform wsTransform ) {
189    
190            Ring exteriorRing = surface.getSurfaceBoundary().getExteriorRing();
191            Position[] exteriorPositions = exteriorRing.getPositions();
192            Ring[] interiorRings = surface.getSurfaceBoundary().getInteriorRings();
193    
194            Position[][] interiorPositions;
195            if ( interiorRings != null ) {
196                interiorPositions = new Position[interiorRings.length][];
197                for ( int i = 0; i < interiorPositions.length; i++ ) {
198                    interiorPositions[i] = interiorRings[i].getPositions();
199                }
200            } else {
201                interiorPositions = new Position[0][];
202            }
203    
204            int[] xArray = getXArray( exteriorPositions, interiorPositions, wsTransform );
205            int[] yArray = getYArray( exteriorPositions, interiorPositions, wsTransform );
206    
207            Polygon polygon = new Polygon( xArray, yArray, xArray.length );
208    
209            return polygon;
210        }
211    
212        /**
213         * Retrieve the array of x-coordinates after transformation to screen coordinates.
214         * 
215         * @param exteriorRing
216         * @param interiorRing
217         * @param wsTransform
218         * @return int[]
219         */
220        private int[] getXArray( Position[] exteriorRing, Position[][] interiorRing, WorldToScreenTransform wsTransform ) {
221    
222            List<Double> xList = new ArrayList<Double>();
223            for ( int i = 0; i < exteriorRing.length; i++ ) {
224                Position position = exteriorRing[i];
225                xList.add( wsTransform.getDestX( position.getX() ) );
226            }
227            for ( int i = 0; i < interiorRing.length; i++ ) {
228                Position[] positions = interiorRing[i];
229                for ( int j = 0; j < positions.length; j++ ) {
230                    Position position = positions[j];
231                    xList.add( wsTransform.getDestX( position.getX() ) );
232                }
233            }
234    
235            int[] xArray = new int[xList.size()];
236            for ( int i = 0; i < xList.size(); i++ ) {
237                Double tmp = xList.get( i );
238                xArray[i] = tmp.intValue();
239            }
240            return xArray;
241        }
242    
243        /**
244         * Retrieve the array of y-coordinates after transformation to screen coordinates.
245         * 
246         * @param exteriorRing
247         * @param interiorRing
248         * @param wsTransform
249         * @return int[]
250         */
251        private int[] getYArray( Position[] exteriorRing, Position[][] interiorRing, WorldToScreenTransform wsTransform ) {
252    
253            List<Double> yList = new ArrayList<Double>();
254            for ( int i = 0; i < exteriorRing.length; i++ ) {
255                Position position = exteriorRing[i];
256                yList.add( wsTransform.getDestY( position.getY() ) );
257            }
258            for ( int i = 0; i < interiorRing.length; i++ ) {
259                Position[] positions = interiorRing[i];
260                for ( int j = 0; j < positions.length; j++ ) {
261                    Position position = positions[j];
262                    yList.add( wsTransform.getDestY( position.getY() ) );
263                }
264            }
265    
266            int[] yArray = new int[yList.size()];
267            for ( int i = 0; i < yList.size(); i++ ) {
268                Double tmp = yList.get( i );
269                yArray[i] = tmp.intValue();
270            }
271            return yArray;
272        }
273    
274        /**
275         * Blur effect carried out on the image. The blur scale defines the intensity of the blur.
276         * 
277         * @param image
278         * @param blurScale
279         * @return BufferedImage
280         */
281        private BufferedImage blur( BufferedImage image, int blurScale ) {
282    
283            BufferedImage destination = null;
284            if ( image.getType() == BufferedImage.TYPE_INT_RGB ) {
285                destination = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB );
286            } else {
287                destination = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB );
288            }
289    
290            float[] data = new float[blurScale * blurScale];
291            float value = 1.0f / ( blurScale * blurScale );
292            for ( int i = 0; i < data.length; i++ ) {
293                data[i] = value;
294            }
295            Kernel kernel = new Kernel( blurScale, blurScale, data );
296            ConvolveOp convolve = new ConvolveOp( kernel, ConvolveOp.EDGE_NO_OP, null );
297            convolve.filter( image, destination );
298    
299            return destination;
300        }
301    
302        /**
303         * Convert BufferedImage RGB to black and white image
304         * 
305         * @param image
306         * @return BufferedImage
307         */
308        private BufferedImage grayScale( BufferedImage image ) {
309    
310            BufferedImage destination = null;
311            if ( image.getType() == BufferedImage.TYPE_INT_RGB ) {
312                destination = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB );
313            } else {
314                destination = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB );
315            }
316            ColorConvertOp colorConvert = new ColorConvertOp( ColorSpace.getInstance( ColorSpace.CS_GRAY ), null );
317            colorConvert.filter( image, destination );
318    
319            return destination;
320        }
321    
322    }