Source Code Cross Referenced for ImageWorker.java in  » GIS » GeoTools-2.4.1 » org » geotools » image » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.image 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *    GeoTools - OpenSource mapping toolkit
0003:         *    http://geotools.org
0004:         *    (C) 2006, Geotools Project Managment Committee (PMC)
0005:         *
0006:         *    This library is free software; you can redistribute it and/or
0007:         *    modify it under the terms of the GNU Lesser General Public
0008:         *    License as published by the Free Software Foundation; either
0009:         *    version 2.1 of the License, or (at your option) any later version.
0010:         *
0011:         *    This library is distributed in the hope that it will be useful,
0012:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0013:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014:         *    Lesser General Public License for more details.
0015:         */
0016:        package org.geotools.image;
0017:
0018:        import java.awt.Image;
0019:        import java.awt.image.*;
0020:        import java.awt.Color;
0021:        import java.awt.Transparency;
0022:        import java.awt.RenderingHints;
0023:        import java.awt.HeadlessException;
0024:        import java.awt.color.ColorSpace;
0025:
0026:        import javax.imageio.IIOImage;
0027:        import javax.imageio.ImageIO;
0028:        import javax.imageio.ImageTypeSpecifier;
0029:        import javax.imageio.ImageWriteParam;
0030:        import javax.imageio.ImageWriter;
0031:        import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
0032:        import javax.imageio.spi.IIORegistry;
0033:        import javax.imageio.spi.ImageWriterSpi;
0034:        import javax.imageio.stream.ImageOutputStream;
0035:        import javax.imageio.IIOException;
0036:        import java.io.IOException;
0037:        import java.io.FileNotFoundException;
0038:        import java.io.File;
0039:        import java.util.List;
0040:        import java.util.Arrays;
0041:        import java.util.ArrayList;
0042:        import java.util.Iterator;
0043:        import java.util.Locale;
0044:        import java.util.logging.Logger;
0045:        import java.lang.reflect.InvocationTargetException;
0046:
0047:        import javax.media.jai.*;
0048:        import javax.media.jai.operator.*;
0049:        import com.sun.media.jai.util.ImageUtil;
0050:
0051:        import org.geotools.factory.Hints;
0052:        import org.geotools.util.logging.Logging;
0053:        import org.geotools.resources.Arguments;
0054:        import org.geotools.resources.i18n.Errors;
0055:        import org.geotools.resources.i18n.ErrorKeys;
0056:        import org.geotools.resources.image.ColorUtilities;
0057:        import org.geotools.resources.image.ImageUtilities;
0058:
0059:        /**
0060:         * Helper methods for applying JAI operations on an image. The image is specified at
0061:         * {@linkplain #ImageWorker(RenderedImage) creation time}. Sucessive operations can
0062:         * be applied by invoking the methods defined in this class, and the final image can
0063:         * be obtained by invoking {@link #getRenderedImage} at the end of the process.
0064:         * <p>
0065:         * If an exception is thrown during a method invocation, then this {@code ImageWorker}
0066:         * is left in an undetermined state and should not be used anymore.
0067:         *
0068:         * @since 2.3
0069:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/image/ImageWorker.java $
0070:         * @version $Id: ImageWorker.java 28776 2008-01-15 20:26:28Z desruisseaux $
0071:         * @author Simone Giannecchini
0072:         * @author Bryce Nordgren
0073:         * @author Martin Desruisseaux
0074:         */
0075:        public class ImageWorker {
0076:            /**
0077:             * Workaround class for compressing PNG using the default
0078:             * {@link PNGImageEncoder} shipped with the JDK.
0079:             * <p>
0080:             * {@link PNGImageWriter} does not support
0081:             * {@link ImageWriteParam#setCompressionMode(int)} set to
0082:             * {@link ImageWriteParam#MODE_EXPLICIT}, it only allows
0083:             * {@link ImageWriteParam#MODE_DEFAULT}.
0084:             *
0085:             * @author Simone Giannecchini
0086:             *
0087:             * @todo Consider moving to {@link org.geotools.image.io} package.
0088:             */
0089:            public final static class PNGImageWriteParam extends
0090:                    ImageWriteParam {
0091:                /**
0092:                 * Default constructor.
0093:                 */
0094:                public PNGImageWriteParam() {
0095:                    super ();
0096:                    this .canWriteProgressive = true;
0097:                    this .canWriteCompressed = true;
0098:                    this .locale = Locale.getDefault();
0099:                }
0100:            }
0101:
0102:            /**
0103:             * The logger to use for this class.
0104:             */
0105:            private final static Logger LOGGER = Logging
0106:                    .getLogger("org.geotools.image");
0107:
0108:            /**
0109:             * If {@link Boolean#FALSE FALSE}, image operators are not allowed to
0110:             * produce tiled images. The default is {@link Boolean#TRUE TRUE}. The
0111:             * {@code FALSE} value is sometime useful for exporting images to some
0112:             * formats that doesn't support tiling (e.g. GIF).
0113:             *
0114:             * @see #setRenderingHint
0115:             */
0116:            public static final Hints.Key TILING_ALLOWED = new Hints.Key(
0117:                    Boolean.class);
0118:
0119:            /**
0120:             * The image property name generated by {@link ExtremaDescriptor}.
0121:             */
0122:            private static final String EXTREMA = "extrema";
0123:
0124:            /**
0125:             * The image specified by the user at construction time, or last time
0126:             * {@link #invalidateStatistics} were invoked. The {@link #getComputedProperty}
0127:             * method will not search a property pass this point.
0128:             */
0129:            private RenderedImage inheritanceStopPoint;
0130:
0131:            /**
0132:             * The image being built.
0133:             */
0134:            protected RenderedImage image;
0135:
0136:            /**
0137:             * The region of interest, or {@code null} if none.
0138:             */
0139:            private ROI roi;
0140:
0141:            /**
0142:             * The rendering hints to provides to all image operators. Additional hints may
0143:             * be set (in a separated {@link RenderingHints} object) for particular images.
0144:             */
0145:            private RenderingHints commonHints;
0146:
0147:            /**
0148:             * 0 if tile cache is enabled, any other value otherwise. This counter is
0149:             * incremented everytime {@code tileCacheEnabled(false)} is invoked, and
0150:             * decremented every time {@code tileCacheEnabled(true)} is invoked.
0151:             */
0152:            private int tileCacheDisabled = 0;
0153:
0154:            /**
0155:             * Creates a new uninitialized builder for an {@linkplain #load image read}.
0156:             *
0157:             * @see #load
0158:             */
0159:            public ImageWorker() {
0160:                inheritanceStopPoint = this .image = null;
0161:            }
0162:
0163:            /**
0164:             * Creates a new builder for an image read from the specified file.
0165:             *
0166:             * @param  input The file to read.
0167:             * @throws IOException if the file can't be read.
0168:             */
0169:            public ImageWorker(final File input) throws IOException {
0170:                this (ImageIO.read(input));
0171:            }
0172:
0173:            /**
0174:             * Creates a new builder for the specified image. The images to be computed (if any)
0175:             * will save their tiles in the default {@linkplain TileCache tile cache}.
0176:             *
0177:             * @param image The source image.
0178:             */
0179:            public ImageWorker(final RenderedImage image) {
0180:                inheritanceStopPoint = this .image = image;
0181:            }
0182:
0183:            /**
0184:             * Prepare this builder for the specified image. The images to be computed (if any)
0185:             * will save their tiles in the default {@linkplain TileCache tile cache}.
0186:             *
0187:             * @param image The source image.
0188:             */
0189:            public final ImageWorker setImage(final RenderedImage image) {
0190:                inheritanceStopPoint = this .image = image;
0191:                return this ;
0192:            }
0193:
0194:            /**
0195:             * Creates a new image worker with the same hints but a different image.
0196:             */
0197:            private ImageWorker fork(final RenderedImage image) {
0198:                final ImageWorker worker = new ImageWorker(image);
0199:                if (commonHints != null && !commonHints.isEmpty()) {
0200:                    RenderingHints hints = new RenderingHints(null);
0201:                    hints.add(worker.commonHints);
0202:                    worker.commonHints = hints;
0203:                }
0204:                return worker;
0205:            }
0206:
0207:            /**
0208:             * Loads an image using the provided file name and the provided hints, which
0209:             * are used to control caching and layout.
0210:             *
0211:             * @param source
0212:             *            The source image.
0213:             * @param hints
0214:             *            The hints to use.
0215:             * @param imageChoice
0216:             *            For multipage images.
0217:             * @return The loaded image.
0218:             *
0219:             * @deprecated Use #load instead.
0220:             */
0221:            public static PlanarImage loadPlanarImageImage(final String source,
0222:                    final RenderingHints hints, final int imageChoice,
0223:                    final boolean readMetadata) {
0224:                final ImageWorker worker = new ImageWorker();
0225:                worker.commonHints = new RenderingHints((java.util.Map) hints);
0226:                worker.load(source, imageChoice, readMetadata);
0227:                return worker.getPlanarImage();
0228:            }
0229:
0230:            /**
0231:             * Loads an image using the provided file name and the
0232:             * {@linkplain #getRenderingHints current hints}, which are used to control caching and layout.
0233:             *
0234:             * @param source
0235:             *            Filename of the source image to read.
0236:             * @param imageChoice
0237:             *            Image index in multipage images.
0238:             * @param readMatadata
0239:             *            If {@code true}, metadata will be read.
0240:             */
0241:            public final void load(final String source, final int imageChoice,
0242:                    final boolean readMetadata) {
0243:                final ParameterBlockJAI pbj = new ParameterBlockJAI("ImageRead");
0244:                pbj.setParameter("Input", source).setParameter("ImageChoice",
0245:                        new Integer(imageChoice)).setParameter("ReadMetadata",
0246:                        new Boolean(readMetadata)).setParameter("VerifyInput",
0247:                        Boolean.TRUE);
0248:                image = JAI.create("ImageRead", pbj, getRenderingHints());
0249:            }
0250:
0251:            ///////////////////////////////////////////////////////////////////////////////////////
0252:            ////////                                                                       ////////
0253:            ////////            IMAGE, PROPERTIES AND RENDERING HINTS ACCESSORS            ////////
0254:            ////////                                                                       ////////
0255:            ///////////////////////////////////////////////////////////////////////////////////////
0256:
0257:            /**
0258:             * Returns the current image.
0259:             *
0260:             * @see #getPlanarImage
0261:             * @see #getRenderedOperation
0262:             * @see #getImageAsROI
0263:             */
0264:            public final RenderedImage getRenderedImage() {
0265:                return image;
0266:            }
0267:
0268:            /**
0269:             * Returns the {@linkplain #getRenderedImage rendered image} as a planar image.
0270:             *
0271:             * @see #getRenderedImage
0272:             * @see #getRenderedOperation
0273:             * @see #getImageAsROI
0274:             */
0275:            public final PlanarImage getPlanarImage() {
0276:                return PlanarImage.wrapRenderedImage(getRenderedImage());
0277:            }
0278:
0279:            /**
0280:             * Returns the {@linkplain #getRenderedImage rendered image} as a rendered operation.
0281:             *
0282:             * @see #getRenderedImage
0283:             * @see #getPlanarImage
0284:             * @see #getImageAsROI
0285:             */
0286:            public final RenderedOp getRenderedOperation() {
0287:                final RenderedImage image = getRenderedImage();
0288:                if (image instanceof  RenderedOp) {
0289:                    return (RenderedOp) image;
0290:                }
0291:                return NullDescriptor.create(image, getRenderingHints());
0292:            }
0293:
0294:            /**
0295:             * Returns a {@linkplain ROI Region Of Interest} built from the current
0296:             * {@linkplain #getRenderedImage image}. If the image is multi-bands, then this method first
0297:             * computes an estimation of its {@linkplain #intensity intensity}. Next, this method
0298:             * {@linkplain #binarize() binarize} the image and constructs a {@link ROI} from the result.
0299:             *
0300:             * @see #getRenderedImage
0301:             * @see #getPlanarImage
0302:             * @see #getRenderedOperation
0303:             */
0304:            public final ROI getImageAsROI() {
0305:                binarize();
0306:                return new ROI(getRenderedImage());
0307:            }
0308:
0309:            /**
0310:             * Returns the <cite>region of interest</cite> currently set, or {@code null} if none.
0311:             * The default value is {@code null}.
0312:             *
0313:             * @see #getMinimums
0314:             * @see #getMaximums
0315:             */
0316:            public final ROI getROI() {
0317:                return roi;
0318:            }
0319:
0320:            /**
0321:             * Set the <cite>region of interest</cite> (ROI). A {@code null} set the ROI to the whole
0322:             * {@linkplain #image}. The ROI is used by statistical methods like {@link #getMinimums}
0323:             * and {@link #getMaximums}.
0324:             *
0325:             * @return This ImageWorker
0326:             *
0327:             * @see #getMinimums
0328:             * @see #getMaximums
0329:             */
0330:            public final ImageWorker setROI(final ROI roi) {
0331:                this .roi = roi;
0332:                invalidateStatistics();
0333:                return this ;
0334:            }
0335:
0336:            /**
0337:             * Returns the rendering hint for the specified key, or {@code null} if none.
0338:             */
0339:            public final Object getRenderingHint(final RenderingHints.Key key) {
0340:                return (commonHints != null) ? commonHints.get(key) : null;
0341:            }
0342:
0343:            /**
0344:             * Sets a rendering hint tile to use for all images to be computed by this class. This method
0345:             * applies only to the next images to be computed; images already computed before this method
0346:             * call (if any) will not be affected.
0347:             * <p>
0348:             * Some common examples:
0349:             * <p>
0350:             * <ul>
0351:             *   <li><code>setRenderingHint({@linkplain JAI#KEY_TILE_CACHE}, null)</code>
0352:             *       disables completly the tile cache.</li>
0353:             *   <li><code>setRenderingHint({@linkplain #TILING_ALLOWED}, Boolean.FALSE)</code>
0354:             *       forces all operators to produce untiled images.</li>
0355:             * </ul>
0356:             *
0357:             * @return This ImageWorker
0358:             */
0359:            public final ImageWorker setRenderingHint(
0360:                    final RenderingHints.Key key, final Object value) {
0361:                if (commonHints == null) {
0362:                    commonHints = new RenderingHints(null);
0363:                }
0364:                commonHints.add(new RenderingHints(key, value));
0365:                return this ;
0366:            }
0367:
0368:            /**
0369:             * Removes a rendering hint. Note that invoking this method is <strong>not</strong> the same
0370:             * than invoking <code>{@linkplain #setRenderingHint setRenderingHint}(key, null)</code>.
0371:             * This is especially true for the {@linkplain javax.media.jai.TileCache tile cache} hint:
0372:             * <p>
0373:             * <ul>
0374:             *   <li><code>{@linkplain #setRenderingHint setRenderingHint}({@linkplain JAI#KEY_TILE_CACHE},
0375:             *       null)</code> disables the use of any tile cache. In other words, this method call do
0376:             *       request a tile cache, which happen to be the "null" cache.</li>
0377:             *
0378:             *   <li><code>removeRenderingHint({@linkplain JAI#KEY_TILE_CACHE})</code> unsets any tile cache
0379:             *       specified by a previous rendering hint. All images to be computed after this method
0380:             *       call will save their tiles in the {@linkplain JAI#getTileCache JAI default tile
0381:             *       cache}.</li>
0382:             * </ul>
0383:             *
0384:             * @return This ImageWorker
0385:             */
0386:            public final ImageWorker removeRenderingHint(
0387:                    final RenderingHints.Key key) {
0388:                if (commonHints != null) {
0389:                    commonHints.remove(key);
0390:                }
0391:                return this ;
0392:            }
0393:
0394:            /**
0395:             * Returns the rendering hints for an image to be computed by this class.
0396:             * The default implementation returns the following hints:
0397:             * <p>
0398:             * <ul>
0399:             *   <li>An {@linkplain ImageLayout image layout} with tiles size computed automatically
0400:             *       from the current {@linkplain #image} size.</li>
0401:             *   <li>Any additional hints specified through the {@link #setRenderingHint} method. If the
0402:             *       user provided explicitly a {@link JAI#KEY_IMAGE_LAYOUT}, then the user layout has
0403:             *       precedence over the automatic layout computed in previous step.</li>
0404:             * </ul>
0405:             *
0406:             * @return The rendering hints to use for image computation (never {@code null}).
0407:             */
0408:            public final RenderingHints getRenderingHints() {
0409:                RenderingHints hints = ImageUtilities.getRenderingHints(image);
0410:                if (hints == null) {
0411:                    hints = new RenderingHints(null);
0412:                    if (commonHints != null) {
0413:                        hints.add(commonHints);
0414:                    }
0415:                } else if (commonHints != null) {
0416:                    hints.putAll(commonHints);
0417:                }
0418:                if (Boolean.FALSE.equals(hints.get(TILING_ALLOWED))) {
0419:                    final ImageLayout layout = getImageLayout(hints);
0420:                    if (commonHints == null
0421:                            || layout != commonHints.get(JAI.KEY_IMAGE_LAYOUT)) {
0422:                        // Set the layout only if it is not a user-supplied object.
0423:                        layout.setTileWidth(image.getWidth());
0424:                        layout.setTileHeight(image.getHeight());
0425:                        layout.setTileGridXOffset(image.getMinX());
0426:                        layout.setTileGridYOffset(image.getMinY());
0427:                        hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
0428:                    }
0429:                }
0430:                if (tileCacheDisabled != 0
0431:                        && (commonHints != null && !commonHints
0432:                                .containsKey(JAI.KEY_TILE_CACHE))) {
0433:                    hints.add(new RenderingHints(JAI.KEY_TILE_CACHE, null));
0434:                }
0435:                return hints;
0436:            }
0437:
0438:            /**
0439:             * Returns the {@linkplain #getRenderingHints rendering hints}, but with a
0440:             * {@linkplain ComponentColorModel component color model} of the specified
0441:             * data type. The data type is changed only if no color model was explicitly
0442:             * specified by the user through {@link #getRenderingHints()}.
0443:             *
0444:             * @param type The data type (typically {@link DataBuffer#TYPE_BYTE}).
0445:             */
0446:            private final RenderingHints getRenderingHints(final int type) {
0447:                /*
0448:                 * Gets the default hints, which usually contains only informations about tiling.
0449:                 * If the user overridden the rendering hints with an explict color model, keep
0450:                 * the user's choice.
0451:                 */
0452:                final RenderingHints hints = getRenderingHints();
0453:                final ImageLayout layout = getImageLayout(hints);
0454:                if (layout.isValid(ImageLayout.COLOR_MODEL_MASK)) {
0455:                    return hints;
0456:                }
0457:                /*
0458:                 * Creates the new color model.
0459:                 */
0460:                final ColorModel oldCm = image.getColorModel();
0461:                final ColorModel newCm = new ComponentColorModel(oldCm
0462:                        .getColorSpace(), oldCm.hasAlpha(), // If true, supports transparency.
0463:                        oldCm.isAlphaPremultiplied(), // If true, alpha is premultiplied.
0464:                        oldCm.getTransparency(), // What alpha values can be represented.
0465:                        type); // Type of primitive array used to represent pixel.
0466:                /*
0467:                 * Creating the final image layout which should allow us to change color model.
0468:                 */
0469:                layout.setColorModel(newCm);
0470:                layout.setSampleModel(newCm.createCompatibleSampleModel(image
0471:                        .getWidth(), image.getHeight()));
0472:                hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
0473:                return hints;
0474:            }
0475:
0476:            /**
0477:             * Gets the image layout from the specified rendering hints, creating a new one if needed.
0478:             * This method do not modify the specified hints. If the caller modifies the image layout,
0479:             * it should invoke {@code hints.put(JAI.KEY_IMAGE_LAYOUT, layout)} explicitly.
0480:             */
0481:            private static ImageLayout getImageLayout(final RenderingHints hints) {
0482:                final Object candidate = hints.get(JAI.KEY_IMAGE_LAYOUT);
0483:                if (candidate instanceof  ImageLayout) {
0484:                    return (ImageLayout) candidate;
0485:                }
0486:                return new ImageLayout();
0487:            }
0488:
0489:            /**
0490:             * If {@code false}, disables the tile cache. Invoking this method with value {@code true}
0491:             * cancel the last invocation with value {@code false}. If this method was invoking many
0492:             * time with value {@code false}, then this method must be invoked the same amount of time
0493:             * with the value {@code true} for reenabling the cache.
0494:             * <p>
0495:             * <strong>Note:</strong> This method name doesn't contain the usual {@code set} prefix
0496:             * because it doesn't really set a flag. Instead it increments or decrements a counter.
0497:             *
0498:             * @return This ImageWorker
0499:             */
0500:            public final ImageWorker tileCacheEnabled(final boolean status) {
0501:                if (status) {
0502:                    if (tileCacheDisabled != 0) {
0503:                        tileCacheDisabled--;
0504:                    } else {
0505:                        throw new IllegalStateException();
0506:                    }
0507:                } else {
0508:                    tileCacheDisabled++;
0509:                }
0510:                return this ;
0511:            }
0512:
0513:            /**
0514:             * Returns the number of bands in the {@linkplain #image}.
0515:             *
0516:             * @see #retainBands
0517:             * @see #retainFirstBand
0518:             * @see SampleModel#getNumBands
0519:             */
0520:            public final int getNumBands() {
0521:                return image.getSampleModel().getNumBands();
0522:            }
0523:
0524:            /**
0525:             * Returns the transparent pixel value, or -1 if none.
0526:             */
0527:            public final int getTransparentPixel() {
0528:                final ColorModel cm = image.getColorModel();
0529:                return (cm instanceof  IndexColorModel) ? ((IndexColorModel) cm)
0530:                        .getTransparentPixel() : -1;
0531:            }
0532:
0533:            /**
0534:             * Gets a property from the property set of the {@linkplain #image}. If the property name
0535:             * is not recognized, then {@link Image#UndefinedProperty} will be returned. This method
0536:             * do <strong>not</strong> inherits properties from the image specified at
0537:             * {@linkplain #ImageWorker(RenderedImage) construction time} - only properties generated
0538:             * by this class are returned.
0539:             */
0540:            private Object getComputedProperty(final String name) {
0541:                final Object value = image.getProperty(name);
0542:                return (value == inheritanceStopPoint.getProperty(name)) ? Image.UndefinedProperty
0543:                        : value;
0544:            }
0545:
0546:            /**
0547:             * Returns the minimums and maximums values found in the image. Those extremas are
0548:             * returned as an array of the form {@code double[2][#bands]}.
0549:             */
0550:            private double[][] getExtremas() {
0551:                Object extrema = getComputedProperty(EXTREMA);
0552:                if (!(extrema instanceof  double[][])) {
0553:                    final Integer ONE = new Integer(1);
0554:                    image = ExtremaDescriptor.create(image, // The source image.
0555:                            roi, // The region of the image to scan. Default to all.
0556:                            ONE, // The horizontal sampling rate. Default to 1.
0557:                            ONE, // The vertical sampling rate. Default to 1.
0558:                            null, // Whether to store extrema locations. Default to false.
0559:                            ONE, // Maximum number of run length codes to store. Default to 1.
0560:                            getRenderingHints());
0561:                    extrema = getComputedProperty(EXTREMA);
0562:                }
0563:                return (double[][]) extrema;
0564:            }
0565:
0566:            /**
0567:             * Tells this builder that all statistics on pixel values (e.g. the "extrema" property
0568:             * in the {@linkplain #image}) should not be inherited from the source images (if any).
0569:             * This method should be invoked every time an operation changed the pixel values.
0570:             *
0571:             * @return This ImageWorker
0572:             */
0573:            private ImageWorker invalidateStatistics() {
0574:                inheritanceStopPoint = image;
0575:                return this ;
0576:            }
0577:
0578:            /**
0579:             * Returns the minimal values found in every {@linkplain #image} bands. If a
0580:             * {@linkplain #getROI region of interest} is defined, then the statistics
0581:             * will be computed only over that region.
0582:             *
0583:             * @see #getMaximums
0584:             * @see #setROI
0585:             */
0586:            public final double[] getMinimums() {
0587:                return getExtremas()[0];
0588:            }
0589:
0590:            /**
0591:             * Returns the maximal values found in every {@linkplain #image} bands. If a
0592:             * {@linkplain #getROI region of interest} is defined, then the statistics
0593:             * will be computed only over that region.
0594:             *
0595:             * @see #getMinimums
0596:             * @see #setROI
0597:             */
0598:            public final double[] getMaximums() {
0599:                return getExtremas()[1];
0600:            }
0601:
0602:            ///////////////////////////////////////////////////////////////////////////////////////
0603:            ////////                                                                       ////////
0604:            ////////            KIND OF IMAGE (BYTES, BINARY, INDEXED, RGB...)             ////////
0605:            ////////                                                                       ////////
0606:            ///////////////////////////////////////////////////////////////////////////////////////
0607:
0608:            /**
0609:             * Returns {@code true} if the {@linkplain #image} stores its pixel values in 8 bits.
0610:             *
0611:             * @see #rescaleToBytes
0612:             */
0613:            public final boolean isBytes() {
0614:                return image.getSampleModel().getDataType() == DataBuffer.TYPE_BYTE;
0615:            }
0616:
0617:            /**
0618:             * Returns {@code true} if the {@linkplain #image} is binary. Such image usually contains
0619:             * only two values: 0 and 1.
0620:             *
0621:             * @see #binarize()
0622:             * @see #binarize(double)
0623:             * @see #binarize(int,int)
0624:             */
0625:            public final boolean isBinary() {
0626:                return ImageUtil.isBinary(image.getSampleModel());
0627:            }
0628:
0629:            /**
0630:             * Returns {@code true} if the {@linkplain #image} uses an {@linkplain IndexColorModel
0631:             * index color model}.
0632:             *
0633:             * @see #forceIndexColorModel
0634:             * @see #forceBitmaskIndexColorModel
0635:             * @see #forceIndexColorModelForGIF
0636:             */
0637:            public final boolean isIndexed() {
0638:                return image.getColorModel() instanceof  IndexColorModel;
0639:            }
0640:
0641:            /**
0642:             * Returns {@code true} if the {@linkplain #image} uses a RGB {@linkplain ColorSpace color
0643:             * space}. Note that a RGB color space doesn't mean that pixel values are directly stored
0644:             * as RGB components. The image may be {@linkplain #isIndexed indexed} as well.
0645:             *
0646:             * @see #forceColorSpaceRGB
0647:             */
0648:            public final boolean isColorSpaceRGB() {
0649:                return image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_RGB;
0650:            }
0651:
0652:            /**
0653:             * Returns {@code true} if the {@linkplain #image} uses a GrayScale
0654:             * {@linkplain ColorSpace color space}. Note that a GrayScale color space
0655:             * doesn't mean that pixel values are directly stored as GrayScale
0656:             * component. The image may be {@linkplain #isIndexed indexed} as well.
0657:             *
0658:             * @see #forceColorSpaceGRAYScale
0659:             */
0660:            public final boolean isColorSpaceGRAYScale() {
0661:                return image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_GRAY;
0662:            }
0663:
0664:            /**
0665:             * Returns {@code true} if the {@linkplain #image} is
0666:             * {@linkplain Transparency#TRANSLUCENT translucent}.
0667:             *
0668:             * @see #forceBitmaskIndexColorModel
0669:             */
0670:            public final boolean isTranslucent() {
0671:                return image.getColorModel().getTransparency() == Transparency.TRANSLUCENT;
0672:            }
0673:
0674:            ///////////////////////////////////////////////////////////////////////////////////////
0675:            ////////                                                                       ////////
0676:            ////////                            IMAGE OPERATORS                            ////////
0677:            ////////                                                                       ////////
0678:            ///////////////////////////////////////////////////////////////////////////////////////
0679:
0680:            /**
0681:             * Rescales the {@linkplain #image} such that it uses 8 bits. If the image already uses 8 bits,
0682:             * then this method does nothing. Otherwise this method computes the minimum and maximum values
0683:             * for each band, {@linkplain RescaleDescriptor rescale} them in the range {@code [0 .. 255]}
0684:             * and force the resulting image to {@link DataBuffer#TYPE_BYTE TYPE_BYTE}.
0685:             *
0686:             * @return This ImageWorker
0687:             *
0688:             * @see #isBytes
0689:             * @see RescaleDescriptor
0690:             */
0691:            public final ImageWorker rescaleToBytes() {
0692:                if (isBytes()) {
0693:                    // Already using bytes - nothing to do.
0694:                    return this ;
0695:                }
0696:                if (isIndexed()) {
0697:                    throw new UnsupportedOperationException(
0698:                            "Rescaling not yet implemented for IndexColorModel.");
0699:                }
0700:                final double[][] extrema = getExtremas();
0701:                final int length = extrema[0].length;
0702:                final double[] scale = new double[length];
0703:                final double[] offset = new double[length];
0704:                for (int i = 0; i < length; i++) {
0705:                    final double delta = extrema[1][i] - extrema[0][i];
0706:                    scale[i] = 255 / delta;
0707:                    offset[i] = -scale[i] * extrema[0][i];
0708:                }
0709:                final RenderingHints hints = getRenderingHints(DataBuffer.TYPE_BYTE);
0710:                image = RescaleDescriptor.create(image, // The source image.
0711:                        scale, // The per-band constants to multiply by.
0712:                        offset, // The per-band offsets to be added.
0713:                        hints); // The rendering hints.
0714:                invalidateStatistics(); // Extremas are no longer valid.
0715:
0716:                // All post conditions for this method contract.
0717:                assert isBytes();
0718:                return this ;
0719:            }
0720:
0721:            /**
0722:             * Reduces the color model to {@linkplain IndexColorModel index color model}.
0723:             * If the current {@linkplain #image} already uses an
0724:             * {@linkplain IndexColorModel index color model}, then this method do
0725:             * nothing. Otherwise, the current implementation performs a ditering on the
0726:             * original color model. Note that this operation loose the alpha channel.
0727:             * <p>
0728:             * This for the moment should work only with opaque images, with non opaque
0729:             * images we just remove the alpha band in order to build an
0730:             * {@link IndexColorModel}. This is one because in general it could be very
0731:             * difficult to decide the final transparency for each pixel given the
0732:             * complexity if the algorithms for obtaining an {@link IndexColorModel}.
0733:             * <p>
0734:             * If an {@link IndexColorModel} with a single transparency index is enough
0735:             * for you, we advise you to take a look at
0736:             * {@link #forceIndexColorModelForGIF(boolean)} methdo.
0737:             *
0738:             * @see #isIndexed
0739:             * @see #forceBitmaskIndexColorModel
0740:             * @see #forceIndexColorModelForGIF
0741:             * @see OrderedDitherDescriptor
0742:             */
0743:            public final ImageWorker forceIndexColorModel(final boolean error) {
0744:                final ColorModel cm = image.getColorModel();
0745:                if (cm instanceof  IndexColorModel) {
0746:                    // Already an index color model - nothing to do.
0747:                    return this ;
0748:                }
0749:                tileCacheEnabled(false);
0750:                final int numBands = getNumBands();
0751:                if ((numBands & 1) == 0) {
0752:                    retainBands(numBands - 1);
0753:                }
0754:                forceColorSpaceRGB();
0755:                final RenderingHints hints = getRenderingHints();
0756:                if (error) {
0757:                    if (false) {
0758:                        // color quantization, disabled for now.
0759:                        final RenderedOp temp = ColorQuantizerDescriptor
0760:                                .create(image,
0761:                                        ColorQuantizerDescriptor.MEDIANCUT,
0762:                                        new Integer(254), new Integer(200),
0763:                                        null, new Integer(1), new Integer(1),
0764:                                        hints);
0765:                        final ImageLayout layout = new ImageLayout();
0766:                        layout.setColorModel(temp.getColorModel());
0767:                        hints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT,
0768:                                layout));
0769:                        LookupTableJAI lookup = (LookupTableJAI) temp
0770:                                .getProperty("JAI.LookupTable");
0771:                    }
0772:                    // error diffusion
0773:                    final KernelJAI ditherMask = KernelJAI.ERROR_FILTER_FLOYD_STEINBERG;
0774:                    final LookupTableJAI colorMap = ColorCube.BYTE_496;
0775:                    image = ErrorDiffusionDescriptor.create(image, colorMap,
0776:                            ditherMask, hints);
0777:                } else {
0778:                    // ordered dither
0779:                    final KernelJAI[] ditherMask = KernelJAI.DITHER_MASK_443;
0780:                    final ColorCube colorMap = ColorCube.BYTE_496;
0781:                    image = OrderedDitherDescriptor.create(image, colorMap,
0782:                            ditherMask, hints);
0783:                }
0784:                tileCacheEnabled(true);
0785:                invalidateStatistics();
0786:
0787:                // All post conditions for this method contract.
0788:                assert isIndexed();
0789:                return this ;
0790:            }
0791:
0792:            /**
0793:             * Reduces the color model to {@linkplain IndexColorModel index color model}
0794:             * with {@linkplain Transparency#OPAQUE opaque} or
0795:             * {@linkplain Transparency#BITMASK bitmask} transparency. If the current
0796:             * {@linkplain #image} already uses a suitable color model, then this method
0797:             * do nothing.
0798:             *
0799:             * @return this {@link ImageWorker}.
0800:             *
0801:             * @see #isIndexed
0802:             * @see #isTranslucent
0803:             * @see #forceIndexColorModel
0804:             * @see #forceIndexColorModelForGIF
0805:             */
0806:            public final ImageWorker forceBitmaskIndexColorModel() {
0807:                forceBitmaskIndexColorModel(getTransparentPixel(), true);
0808:                return this ;
0809:            }
0810:
0811:            /**
0812:             * Reduces the color model to {@linkplain IndexColorModel index color model}
0813:             * with {@linkplain Transparency#OPAQUE opaque} or
0814:             * {@linkplain Transparency#BITMASK bitmask} transparency. If the current
0815:             * {@linkplain #image} already uses a suitable color model, then this method
0816:             * do nothing.
0817:             *
0818:             * @param transparent
0819:             *            A pixel value to define as the transparent pixel. *
0820:             * @param errorDiffusion
0821:             *            Tells if I should use {@link ErrorDiffusionDescriptor} or
0822:             *            {@link OrderedDitherDescriptor} JAi operations. errorDiffusion
0823:             * @return this {@link ImageWorker}.
0824:             *
0825:             * @see #isIndexed
0826:             * @see #isTranslucent
0827:             * @see #forceIndexColorModel
0828:             * @see #forceIndexColorModelForGIF
0829:             */
0830:            public final ImageWorker forceBitmaskIndexColorModel(
0831:                    int transparent, final boolean errorDiffusion) {
0832:                final ColorModel cm = image.getColorModel();
0833:                if (cm instanceof  IndexColorModel) {
0834:                    final IndexColorModel oldCM = (IndexColorModel) cm;
0835:                    switch (oldCM.getTransparency()) {
0836:                    case Transparency.OPAQUE: {
0837:                        // Suitable color model. There is nothing to do.
0838:                        return this ;
0839:                    }
0840:                    case Transparency.BITMASK: {
0841:                        if (oldCM.getTransparentPixel() == transparent) {
0842:                            // Suitable color model. There is nothing to do.
0843:                            return this ;
0844:                        }
0845:                        break;
0846:                    }
0847:                    default: {
0848:                        break;
0849:                    }
0850:                    }
0851:                    /*
0852:                     * The index color model need to be replaced. Creates a lookup table
0853:                     * mapping from the old pixel values to new pixels values, with
0854:                     * transparent colors mapped to the new transparent pixel value. The
0855:                     * lookup table uses TYPE_BYTE or TYPE_USHORT, which are the two
0856:                     * only types supported by IndexColorModel.
0857:                     */
0858:                    final int pixelSize = oldCM.getPixelSize();
0859:                    transparent &= (1 << pixelSize) - 1;
0860:                    final int mapSize = oldCM.getMapSize();
0861:                    final int newSize = Math.max(mapSize, transparent + 1);
0862:                    final LookupTableJAI lookupTable;
0863:                    if (newSize <= 0xFF) {
0864:                        final byte[] table = new byte[mapSize];
0865:                        for (int i = 0; i < mapSize; i++) {
0866:                            table[i] = (byte) ((oldCM.getAlpha(i) == 0) ? transparent
0867:                                    : i);
0868:                        }
0869:                        lookupTable = new LookupTableJAI(table);
0870:                    } else if (newSize <= 0xFFFF) {
0871:                        final short[] table = new short[mapSize];
0872:                        for (int i = 0; i < mapSize; i++) {
0873:                            table[i] = (short) ((oldCM.getAlpha(i) == 0) ? transparent
0874:                                    : i);
0875:                        }
0876:                        lookupTable = new LookupTableJAI(table, true);
0877:                    } else {
0878:                        throw new AssertionError(mapSize); // Should never happen.
0879:                    }
0880:                    /*
0881:                     * Now we need to perform the look up transformation. First of all
0882:                     * we create the new color model with a bitmask transparency using
0883:                     * the transparency index specified to this method. Then we perform
0884:                     * the lookup operation in order to prepare for the gif image.
0885:                     */
0886:                    final byte[][] rgb = new byte[3][newSize];
0887:                    oldCM.getReds(rgb[0]);
0888:                    oldCM.getGreens(rgb[1]);
0889:                    oldCM.getBlues(rgb[2]);
0890:                    final IndexColorModel newCM = new IndexColorModel(
0891:                            pixelSize, newSize, rgb[0], rgb[1], rgb[2],
0892:                            transparent);
0893:                    final RenderingHints hints = getRenderingHints();
0894:                    final ImageLayout layout = getImageLayout(hints);
0895:                    layout.setColorModel(newCM);
0896:                    hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
0897:                    hints.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
0898:                    image = LookupDescriptor.create(image, lookupTable, hints);
0899:                } else {
0900:                    /*
0901:                     * The image is not indexed. Getting the alpha channel.
0902:                     */
0903:                    RenderedImage alphaChannel = null;
0904:                    if (cm.hasAlpha()) {
0905:                        tileCacheEnabled(false);
0906:                        int numBands = getNumBands();
0907:                        final RenderingHints hints = getRenderingHints();
0908:                        alphaChannel = BandSelectDescriptor.create(image,
0909:                                new int[] { --numBands }, hints);
0910:                        retainBands(numBands);
0911:                        forceIndexColorModel(errorDiffusion);
0912:                        tileCacheEnabled(true);
0913:                    }
0914:                    /*
0915:                     * Adding transparency if needed, which means using the alpha
0916:                     * channel to build a new color model. The method call below implies
0917:                     * 'forceColorSpaceRGB()' and 'forceIndexColorModel()' method calls.
0918:                     */
0919:                    addTransparencyToIndexColorModel(alphaChannel, false,
0920:                            transparent, errorDiffusion);
0921:                }
0922:                // All post conditions for this method contract.
0923:                assert isIndexed();
0924:                assert !isTranslucent();
0925:                return this ;
0926:            }
0927:
0928:            /**
0929:             * Converts the image to a GIF-compliant image. This method has been created
0930:             * in order to convert the input image to a form that is compatible with the
0931:             * GIF model. It first remove the information about transparency since the
0932:             * error diffusion and the error dither operations are unable to process
0933:             * images with more than 3 bands. Afterwards the image is processed with an
0934:             * error diffusion operator in order to reduce the number of bands from 3 to
0935:             * 1 and the number of color to 216. A suitable layout is used for the final
0936:             * image via the {@linkplain #getRenderingHints rendering hints} in order to
0937:             * take into account the different layout model for the final image.
0938:             * <p>
0939:             * <strong>Tip:</strong> For optimizing writing GIF, we need to create the
0940:             * image untiled. This can be done by invoking
0941:             * <code>{@linkplain #setRenderingHint setRenderingHint}({@linkplain
0942:             * #TILING_ALLOWED}, Boolean.FALSE)</code>
0943:             * first.
0944:             *
0945:             * @param errorDiffusion
0946:             *            Tells if I should use {@link ErrorDiffusionDescriptor} or
0947:             *            {@link OrderedDitherDescriptor} JAi operations.
0948:             *
0949:             * @return this {@link ImageWorker}.
0950:             *
0951:             * @see #isIndexed
0952:             * @see #forceIndexColorModel
0953:             * @see #forceBitmaskIndexColorModel
0954:             */
0955:            public final ImageWorker forceIndexColorModelForGIF(
0956:                    final boolean errorDiffusion) {
0957:                /*
0958:                 * Checking the color model to see if we need to convert it back to
0959:                 * color model. We might also need to reformat the image in order to get
0960:                 * it to 8 bits samples.
0961:                 */
0962:                tileCacheEnabled(false);
0963:                if (image.getColorModel() instanceof  PackedColorModel) {
0964:                    forceComponentColorModel();
0965:                }
0966:                rescaleToBytes();
0967:                tileCacheEnabled(true);
0968:                /*
0969:                 * Getting the alpha channel and separating from the others bands. If
0970:                 * the initial image had no alpha channel (more specifically, if it is
0971:                 * either opaque or a bitmask) we proceed without doing anything since
0972:                 * it seems that GIF encoder in such a case works fine. If we need to
0973:                 * create a bitmask, we will use the last index value allowed (255) as
0974:                 * the transparent pixel value.
0975:                 */
0976:                if (isTranslucent()) {
0977:                    forceBitmaskIndexColorModel(255, errorDiffusion);
0978:                } else {
0979:                    forceIndexColorModel(errorDiffusion);
0980:                }
0981:                // All post conditions for this method contract.
0982:                assert isBytes();
0983:                assert isIndexed();
0984:                assert !isTranslucent();
0985:                return this ;
0986:            }
0987:
0988:            /**
0989:             * Reformats the {@linkplain ColorModel color model} to a
0990:             * {@linkplain ComponentColorModel component color model} preserving
0991:             * transparency. This is used especially in order to go from
0992:             * {@link PackedColorModel} to {@link ComponentColorModel}, which seems to
0993:             * be well accepted from {@code PNGEncoder} and {@code TIFFEncoder}.
0994:             * <p>
0995:             * This code is adapted from jai-interests mailing list archive.
0996:             *
0997:             * @return this {@link ImageWorker}.
0998:             *
0999:             * @see FormatDescriptor
1000:             */
1001:            public final ImageWorker forceComponentColorModel() {
1002:                return forceComponentColorModel(false);
1003:            }
1004:
1005:            /**
1006:             * Reformats the {@linkplain ColorModel color model} to a
1007:             * {@linkplain ComponentColorModel component color model} preserving
1008:             * transparency. This is used especially in order to go from
1009:             * {@link PackedColorModel} to {@link ComponentColorModel}, which seems to
1010:             * be well accepted from {@code PNGEncoder} and {@code TIFFEncoder}.
1011:             * <p>
1012:             * This code is adapted from jai-interests mailing list archive.
1013:             *
1014:             * @param checkTransparent
1015:             *            tells this method to not consider fully transparent pixels
1016:             *            when optimizing grayscale palettes.
1017:             *
1018:             * @return this {@link ImageWorker}.
1019:             *
1020:             * @todo Make this function work on 16 bits indexed images which means
1021:             *       changing the lookup code.
1022:             * @see FormatDescriptor
1023:             */
1024:            public final ImageWorker forceComponentColorModel(
1025:                    boolean checkTransparent) {
1026:                final ColorModel cm = image.getColorModel();
1027:                if (cm instanceof  ComponentColorModel) {
1028:                    // Already an component color model - nothing to do.
1029:                    return this ;
1030:                }
1031:                // shortcut for index color model
1032:                if (cm instanceof  IndexColorModel) {
1033:                    final IndexColorModel icm = (IndexColorModel) cm;
1034:                    boolean gray = ColorUtilities.isGrayPalette(icm,
1035:                            checkTransparent);
1036:                    boolean alpha = icm.hasAlpha();
1037:                    final int numBands = icm.getNumComponents();
1038:                    byte data[][] = new byte[cm.getNumComponents()][icm
1039:                            .getMapSize()];
1040:                    icm.getReds(data[0]);
1041:                    icm.getGreens(data[1]);
1042:                    icm.getBlues(data[2]);
1043:                    if (numBands == 4) {
1044:                        icm.getAlphas(data[3]);
1045:                    }
1046:                    final LookupTableJAI lut = new LookupTableJAI(data);
1047:                    /*
1048:                     * Get the default hints, which usually contains only informations
1049:                     * about tiling. If the user overrode the rendering hints with an
1050:                     * explict color model, keep the user's choice.
1051:                     */
1052:                    final RenderingHints hints = getRenderingHints();
1053:                    image = JAI.create("Lookup", image, lut, hints);
1054:                    /*
1055:                     * If the image is grayscale, retain only the first band.
1056:                     */
1057:                    if (gray && !alpha) {
1058:                        retainBands(1);
1059:                    } else if (gray && alpha) {
1060:                        retainBands(new int[] { 0, 3 });
1061:                    }
1062:                } else {
1063:                    // Most of the code adapted from jai-interests is in 'getRenderingHints(int)'.
1064:                    final int type = (cm instanceof  DirectColorModel) ? DataBuffer.TYPE_BYTE
1065:                            : image.getSampleModel().getTransferType();
1066:                    final RenderingHints hints = getRenderingHints(type);
1067:                    image = FormatDescriptor.create(image, new Integer(type),
1068:                            hints);
1069:                }
1070:                invalidateStatistics();
1071:
1072:                // All post conditions for this method contract.
1073:                assert image.getColorModel() instanceof  ComponentColorModel;
1074:                return this ;
1075:            }
1076:
1077:            /**
1078:             * Forces the {@linkplain #image} color model to the
1079:             * {@linkplain ColorSpace#CS_sRGB RGB color space}. If the current color
1080:             * space is already of {@linkplain ColorSpace#TYPE_RGB RGB type}, then this
1081:             * method does nothing. This operation may loose the alpha channel.
1082:             *
1083:             * @return this {@link ImageWorker}.
1084:             *
1085:             * @see #isColorSpaceRGB
1086:             * @see ColorConvertDescriptor
1087:             */
1088:            public final ImageWorker forceColorSpaceRGB() {
1089:                if (!isColorSpaceRGB()) {
1090:                    final ColorModel cm = new ComponentColorModel(ColorSpace
1091:                            .getInstance(ColorSpace.CS_sRGB), false, false,
1092:                            Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
1093:                    image = ColorConvertDescriptor.create(image, cm,
1094:                            getRenderingHints());
1095:                    invalidateStatistics();
1096:                }
1097:                // All post conditions for this method contract.
1098:                assert isColorSpaceRGB();
1099:                return this ;
1100:            }
1101:
1102:            /**
1103:             * Forces the {@linkplain #image} color model to the
1104:             * {@linkplain ColorSpace#CS_GRAY GRAYScale color space}. If the current
1105:             * color space is already of {@linkplain ColorSpace#TYPE_GRAY  type}, then
1106:             * this method does nothing.
1107:             *
1108:             * @return this {@link ImageWorker}.
1109:             *
1110:             * @see #isColorSpaceGRAYScale
1111:             * @see ColorConvertDescriptor
1112:             */
1113:            public final ImageWorker forceColorSpaceGRAYScale() {
1114:                if (!isColorSpaceRGB()) {
1115:                    final ColorModel cm = new ComponentColorModel(ColorSpace
1116:                            .getInstance(ColorSpace.CS_GRAY), false, false,
1117:                            Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
1118:                    image = ColorConvertDescriptor.create(image, cm,
1119:                            getRenderingHints());
1120:                    invalidateStatistics();
1121:                }
1122:                // All post conditions for this method contract.
1123:                assert isColorSpaceGRAYScale();
1124:                return this ;
1125:            }
1126:
1127:            /**
1128:             * Creates an image which represents approximatively the intensity of
1129:             * {@linkplain #image}. The result is always a single-banded image. If the
1130:             * image uses an {@linkplain IHSColorSpace IHS color space}, then this
1131:             * method just {@linkplain #retainFirstBand retain the first band} without
1132:             * any further processing. Otherwise, this method performs a simple
1133:             * {@linkplain BandCombineDescriptor band combine} operation on the
1134:             * {@linkplain #image} in order to come up with a simple estimation of the
1135:             * intensity of the image based on the average value of the color
1136:             * components. It is worthwhile to note that the alpha band is stripped from
1137:             * the image.
1138:             *
1139:             * @return this {@link ImageWorker}.
1140:             *
1141:             * @see BandCombineDescriptor
1142:             */
1143:            public final ImageWorker intensity() {
1144:                /*
1145:                 * If the color model already uses a IHS color space or a Gray color
1146:                 * space, keep only the intensity band. Otherwise, we need a component
1147:                 * color model to be sure to understand what we are doing.
1148:                 */
1149:                ColorModel cm = image.getColorModel();
1150:                final ColorSpace cs = cm.getColorSpace();
1151:                if (cs.getType() == ColorSpace.TYPE_GRAY
1152:                        || cs instanceof  IHSColorSpace) {
1153:                    retainFirstBand();
1154:                    return this ;
1155:                }
1156:                if (cm instanceof  IndexColorModel) {
1157:                    forceComponentColorModel();
1158:                    cm = image.getColorModel();
1159:                }
1160:
1161:                // Number of color componenents
1162:                final int numBands = cm.getNumComponents();
1163:                final int numColorBands = cm.getNumColorComponents();
1164:                final boolean hasAlpha = cm.hasAlpha();
1165:
1166:                // One band, nothing to combine.
1167:                if (numBands == 1) {
1168:                    return this ;
1169:                }
1170:                // One band plus alpha, let's remove alpha.
1171:                if (numColorBands == 1 && hasAlpha) {
1172:                    retainFirstBand();
1173:                    return this ;
1174:                }
1175:                /*
1176:                 * We have more than one band. Note that there is no need to remove the
1177:                 * alpha band before to apply the "bandCombine" operation - it is
1178:                 * suffisient to let the coefficient for the alpha band to the 0 value.
1179:                 */
1180:                final double[][] coeff = new double[1][numBands + 1];
1181:                Arrays.fill(coeff[0], 0, numColorBands, 1.0 / numColorBands);
1182:                image = BandCombineDescriptor.create(image, coeff,
1183:                        getRenderingHints());
1184:                invalidateStatistics();
1185:
1186:                // All post conditions for this method contract.
1187:                assert getNumBands() == 1;
1188:                return this ;
1189:            }
1190:
1191:            /**
1192:             * Retains inconditionnaly the first band of {@linkplain #image}. All other
1193:             * bands (if any) are discarted without any further processing.
1194:             *
1195:             * @return this {@link ImageWorker}.
1196:             *
1197:             * @see #getNumBands
1198:             * @see #retainBands
1199:             * @see BandSelectDescriptor
1200:             */
1201:            public final ImageWorker retainFirstBand() {
1202:                retainBands(1);
1203:
1204:                // All post conditions for this method contract.
1205:                assert getNumBands() == 1;
1206:                return this ;
1207:            }
1208:
1209:            /**
1210:             * Retains inconditionnaly the last band of {@linkplain #image}. All other
1211:             * bands (if any) are discarted without any further processing.
1212:             *
1213:             * @return this {@link ImageWorker}.
1214:             *
1215:             * @see #getNumBands
1216:             * @see #retainBands
1217:             * @see BandSelectDescriptor
1218:             */
1219:            public final ImageWorker retainLastBand() {
1220:                retainBands(new int[] { getNumBands() - 1 });
1221:
1222:                // All post conditions for this method contract.
1223:                assert getNumBands() == 1;
1224:                return this ;
1225:            }
1226:
1227:            /**
1228:             * Retains inconditionnaly the first {@code numBands} of {@linkplain #image}.
1229:             * All other bands (if any) are discarted without any further processing.
1230:             * This method does nothing if the current {@linkplain #image} does not have
1231:             * a greater amount of bands than {@code numBands}.
1232:             *
1233:             * @param numBands
1234:             *            the number of bands to retain.
1235:             * @return this {@link ImageWorker}.
1236:             *
1237:             * @see #getNumBands
1238:             * @see #retainFirstBand
1239:             * @see BandSelectDescriptor
1240:             */
1241:            public final ImageWorker retainBands(final int numBands) {
1242:                if (numBands <= 0) {
1243:                    throw new IndexOutOfBoundsException(Errors.format(
1244:                            ErrorKeys.ILLEGAL_ARGUMENT_$2, "numBands",
1245:                            new Integer(numBands)));
1246:                }
1247:                if (getNumBands() > numBands) {
1248:                    final int[] bands = new int[numBands];
1249:                    for (int i = 0; i < bands.length; i++) {
1250:                        bands[i] = i;
1251:                    }
1252:                    image = BandSelectDescriptor.create(image, bands,
1253:                            getRenderingHints());
1254:                }
1255:
1256:                // All post conditions for this method contract.
1257:                assert getNumBands() <= numBands;
1258:                return this ;
1259:            }
1260:
1261:            /**
1262:             * Retains inconditionnaly certain bands of {@linkplain #image}. All other
1263:             * bands (if any) are discarded without any further processing.
1264:             *
1265:             * @param bands
1266:             *            the bands to retain.
1267:             * @return this {@link ImageWorker}.
1268:             *
1269:             * @see #getNumBands
1270:             * @see #retainFirstBand
1271:             * @see BandSelectDescriptor
1272:             */
1273:            public final ImageWorker retainBands(final int[] bands) {
1274:                image = BandSelectDescriptor.create(image, bands,
1275:                        getRenderingHints());
1276:                return this ;
1277:            }
1278:
1279:            /**
1280:             * Formats the underlying image to the provided data type.
1281:             *
1282:             * @param dataType
1283:             *            to be used for this {@link FormatDescriptor} operation.
1284:             * @return this {@link ImageWorker}
1285:             */
1286:            public final ImageWorker format(final int dataType) {
1287:                image = FormatDescriptor.create(image, new Integer(dataType),
1288:                        getRenderingHints());
1289:
1290:                // All post conditions for this method contract.
1291:                assert image.getSampleModel().getDataType() == dataType;
1292:                return this ;
1293:            }
1294:
1295:            /**
1296:             * Binarizes the {@linkplain #image}. If the image is multi-bands, then
1297:             * this method first computes an estimation of its
1298:             * {@linkplain #intensity intensity}. Then, the threshold value is set
1299:             * halfway between the minimal and maximal values found in the image.
1300:             *
1301:             * @return this {@link ImageWorker}.
1302:             *
1303:             * @see #isBinary
1304:             * @see #binarize(double)
1305:             * @see #binarize(int,int)
1306:             * @see BinarizeDescriptor
1307:             */
1308:            public final ImageWorker binarize() {
1309:                binarize(Double.NaN);
1310:
1311:                // All post conditions for this method contract.
1312:                assert isBinary();
1313:                return this ;
1314:            }
1315:
1316:            /**
1317:             * Binarizes the {@linkplain #image}. If the image is already binarized,
1318:             * then this method does nothing.
1319:             *
1320:             * @param threshold
1321:             *            The threshold value.
1322:             * @return this {@link ImageWorker}.
1323:             *
1324:             * @see #isBinary
1325:             * @see #binarize()
1326:             * @see #binarize(int,int)
1327:             * @see BinarizeDescriptor
1328:             */
1329:            public final ImageWorker binarize(double threshold) {
1330:                // If the image is already binary and the threshold is >=1 then there is no work to do.
1331:                if (!isBinary()) {
1332:                    if (Double.isNaN(threshold)) {
1333:                        if (getNumBands() != 1) {
1334:                            tileCacheEnabled(false);
1335:                            intensity();
1336:                            tileCacheEnabled(true);
1337:                        }
1338:                        final double[][] extremas = getExtremas();
1339:                        threshold = 0.5 * (extremas[0][0] + extremas[1][0]);
1340:                    }
1341:                    final RenderingHints hints = getRenderingHints();
1342:                    image = BinarizeDescriptor.create(image, new Double(
1343:                            threshold), hints);
1344:                    invalidateStatistics();
1345:                }
1346:                // All post conditions for this method contract.
1347:                assert isBinary();
1348:                return this ;
1349:            }
1350:
1351:            /**
1352:             * Binarizes the {@linkplain #image} (if not already done) and replace all 0
1353:             * values by {@code value0} and all 1 values by {@code value1}. If the
1354:             * image should be binarized using a custom threshold value (instead of the
1355:             * automatic one), invoke {@link #binarize(double)} explicitly before this
1356:             * method.
1357:             *
1358:             * @return this {@link ImageWorker}.
1359:             * @see #isBinary
1360:             * @see #binarize()
1361:             * @see #binarize(double)
1362:             * @see BinarizeDescriptor
1363:             * @see LookupDescriptor
1364:             */
1365:            public final ImageWorker binarize(final int value0, final int value1) {
1366:                tileCacheEnabled(false);
1367:                binarize();
1368:                tileCacheEnabled(true);
1369:                final LookupTableJAI table;
1370:                final int min = Math.min(value0, value1);
1371:                if (min >= 0) {
1372:                    final int max = Math.max(value0, value1);
1373:                    if (max < 256) {
1374:                        table = new LookupTableJAI(new byte[] { (byte) value0,
1375:                                (byte) value1 });
1376:                    } else if (max < 65536) {
1377:                        table = new LookupTableJAI(new short[] {
1378:                                (short) value0, (short) value1 }, true);
1379:                    } else {
1380:                        table = new LookupTableJAI(new int[] { value0, value1 });
1381:                    }
1382:                } else {
1383:                    table = new LookupTableJAI(new int[] { value0, value1 });
1384:                }
1385:                image = LookupDescriptor.create(image, table,
1386:                        getRenderingHints());
1387:                invalidateStatistics();
1388:                return this ;
1389:            }
1390:
1391:            /**
1392:             * Replaces all occurences of the given color (usually opaque) by a fully transparent color.
1393:             * Currents implementation supports image backed by any {@link IndexColorModel}, or by
1394:             * {@link ComponentColorModel} with {@link DataBuffer#TYPE_BYTE TYPE_BYTE}. More types
1395:             * may be added in future GeoTools versions.
1396:             *
1397:             * @param transparentColor The color to make transparent.
1398:             * @return this image worker.
1399:             *
1400:             * @throws IllegalStateException if the current {@linkplain #image} has an unsupported color
1401:             *         model.
1402:             */
1403:            public final ImageWorker makeColorTransparent(
1404:                    final Color transparentColor) throws IllegalStateException {
1405:                if (transparentColor == null) {
1406:                    throw new IllegalArgumentException(Errors.format(
1407:                            ErrorKeys.NULL_ARGUMENT_$1, "transparentColor"));
1408:                }
1409:                final ColorModel cm = image.getColorModel();
1410:                if (cm instanceof  IndexColorModel) {
1411:                    return maskIndexColorModelByte(transparentColor);
1412:                } else if (cm instanceof  ComponentColorModel) {
1413:                    switch (image.getSampleModel().getDataType()) {
1414:                    case DataBuffer.TYPE_BYTE: {
1415:                        return maskComponentColorModelByte(transparentColor);
1416:                    }
1417:                        // Add other types here if we support them...
1418:                    }
1419:                }
1420:                throw new IllegalStateException(Errors
1421:                        .format(ErrorKeys.UNSUPPORTED_DATA_TYPE));
1422:            }
1423:
1424:            /**
1425:             * For an image backed by an {@link IndexColorModel}, replaces all occurences of the given
1426:             * color (usually opaque) by a fully transparent color.
1427:             *
1428:             * @param transparentColor The color to make transparent.
1429:             * @return this image worker.
1430:             *
1431:             * @deprecated This method will be private (and maybe replaced) in a future version.
1432:             *             Use {@link #makeColorTransparent} instead.
1433:             */
1434:            public final ImageWorker maskIndexColorModelByte(
1435:                    final Color transparentColor) {
1436:                assert image.getColorModel() instanceof  IndexColorModel;
1437:
1438:                // Gets informations about the provided images.
1439:                IndexColorModel cm = (IndexColorModel) image.getColorModel();
1440:                final int numComponents = cm.getNumComponents();
1441:                int transparency = cm.getTransparency();
1442:                int transparencyIndex = cm.getTransparentPixel();
1443:                final int mapSize = cm.getMapSize();
1444:                final int transparentRGB = transparentColor.getRGB() & 0xFFFFFF;
1445:                /*
1446:                 * Optimization in case of Transparency.BITMASK.
1447:                 * If the color we want to use as the fully transparent one is the same
1448:                 * that is actually used as the transparent color, we leave doing nothing.
1449:                 */
1450:                if (transparency == Transparency.BITMASK
1451:                        && transparencyIndex != -1) {
1452:                    int transpColor = cm.getRGB(transparencyIndex) & 0xFFFFFF;
1453:                    if (transpColor == transparentRGB) {
1454:                        return this ;
1455:                    }
1456:                }
1457:                /*
1458:                 * Find the index of the specified color. Most of the time, the color should appears only
1459:                 * once, which will leads us to a BITMASK image. However we allows more occurences, which
1460:                 * will leads us to a TRANSLUCENT image.
1461:                 */
1462:                final List transparentPixelsIndexes = new ArrayList();
1463:                for (int i = 0; i < mapSize; i++) {
1464:                    // Gets the color for this pixel removing the alpha information.
1465:                    final int color = cm.getRGB(i) & 0xFFFFFF;
1466:                    if (transparentRGB == color) {
1467:                        transparentPixelsIndexes.add(new Integer(i));
1468:                        if (Transparency.BITMASK == transparency) {
1469:                            break;
1470:                        }
1471:                    }
1472:                }
1473:                final int found = transparentPixelsIndexes.size();
1474:                if (found == 1) {
1475:                    // Transparent color found.
1476:                    transparencyIndex = ((Integer) transparentPixelsIndexes
1477:                            .get(0)).intValue();
1478:                    transparency = Transparency.BITMASK;
1479:                } else if (found == 0) {
1480:                    return this ;
1481:                } else {
1482:                    transparencyIndex = -1;
1483:                    transparency = Transparency.TRANSLUCENT;
1484:                }
1485:
1486:                // Prepare the new ColorModel.
1487:                // Get the old map and update it as needed.
1488:                final byte rgb[][] = new byte[4][mapSize];
1489:                Arrays.fill(rgb[3], (byte) 255);
1490:                cm.getReds(rgb[0]);
1491:                cm.getGreens(rgb[1]);
1492:                cm.getBlues(rgb[2]);
1493:                if (numComponents == 4) {
1494:                    cm.getAlphas(rgb[3]);
1495:                }
1496:                if (transparency != Transparency.TRANSLUCENT) {
1497:                    cm = new IndexColorModel(cm.getPixelSize(), mapSize,
1498:                            rgb[0], rgb[1], rgb[2], transparencyIndex);
1499:                } else {
1500:                    for (int k = 0; k < found; k++) {
1501:                        rgb[3][((Integer) transparentPixelsIndexes.get(k))
1502:                                .intValue()] = (byte) 0;
1503:                    }
1504:                    cm = new IndexColorModel(cm.getPixelSize(), mapSize,
1505:                            rgb[0], rgb[1], rgb[2], rgb[3]);
1506:                }
1507:
1508:                // Format the input image.
1509:                final ImageLayout layout = new ImageLayout(image);
1510:                layout.setColorModel(cm);
1511:                final RenderingHints hints = getRenderingHints();
1512:                hints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout));
1513:                hints.add(new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
1514:                        Boolean.FALSE));
1515:                image = FormatDescriptor.create(image, new Integer(image
1516:                        .getSampleModel().getDataType()), hints);
1517:                invalidateStatistics();
1518:                return this ;
1519:            }
1520:
1521:            /**
1522:             * For an image backed by an {@link ComponentColorModel}, replaces all occurences
1523:             * of the given color (usually opaque) by a fully transparent color.
1524:             *
1525:             * @param transparentColor The color to make transparent.
1526:             * @return this image worker.
1527:             *
1528:             * @deprecated This method will be private (and maybe replaced) in a future version.
1529:             *             Use {@link #makeColorTransparent} instead.
1530:             *
1531:             * Current implementation invokes a lot of JAI operations:
1532:             *
1533:             *     "BandSelect" --> "Lookup" --> "BandCombine" --> "Extrema" --> "Binarize" -->
1534:             *     "Format" --> "BandSelect" (one more time) --> "Multiply" --> "BandMerge".
1535:             *
1536:             * I would expect more speed and memory efficiency by writting our own JAI operation (PointOp
1537:             * subclass) doing that in one step. It would also be more determinist (our "binarize" method
1538:             * depends on statistics on pixel values) and avoid unwanted side-effect like turning black
1539:             * color (RGB = 0,0,0) to transparent one. It would also be easier to maintain I believe.
1540:             */
1541:            public final ImageWorker maskComponentColorModelByte(
1542:                    final Color transparentColor) {
1543:                assert image.getColorModel() instanceof  ComponentColorModel;
1544:                assert image.getSampleModel().getDataType() == DataBuffer.TYPE_BYTE;
1545:                /*
1546:                 * Prepares the look up table for the source image.
1547:                 * Remember what follows which is taken from the JAI programming guide.
1548:                 *
1549:                 *     "The lookup operation performs a general table lookup on a rendered or renderable
1550:                 *      image. The destination image is obtained by passing the source image through the
1551:                 *      lookup table. The source image may be single- or multi-banded of data types byte,
1552:                 *      ushort, short, or int. The lookup table may be single- or multi-banded of any JAI-
1553:                 *      supported data types.
1554:                 *
1555:                 *      The destination image must have the same data type as the lookup table, and its
1556:                 *      number of bands is determined based on the number of bands of the source and the
1557:                 *      table. If the source is single-banded, the destination has the same number of bands
1558:                 *      as the lookup table; otherwise, the destination has the same number of bands as the
1559:                 *      source.
1560:                 *
1561:                 *      If either the source or the table is single-banded and the other one is multibanded,
1562:                 *      the single band is applied to every band of the multi-banded object. If both are
1563:                 *      multi-banded, their corresponding bands are matched up."
1564:                 *
1565:                 * A final annotation, if we have an input image with transparency we just DROP it since
1566:                 * we want to re-add it using the supplied color as the mask for transparency.
1567:                 */
1568:
1569:                /*
1570:                 * In case of a gray color model we can do everything in one step by expanding
1571:                 * the color model to get one more band directly which is the alpha band itself.
1572:                 *
1573:                 * For a multiband image the lookup is applied to each band separately.
1574:                 * This means that we cannot control directly the image as a whole but
1575:                 * we need first to interact with the single bands then to combine the
1576:                 * result into a single band that will provide us with the alpha band.
1577:                 */
1578:                final int numBands = image.getSampleModel().getNumBands();
1579:                final int numColorBands = image.getColorModel()
1580:                        .getNumColorComponents();
1581:                final RenderingHints hints = getRenderingHints();
1582:                RenderedImage transparentBand = null;
1583:                if (numColorBands != numBands) {
1584:                    // Typically, numColorBands will be equals to numBands-1.
1585:                    transparentBand = BandSelectDescriptor.create(image,
1586:                            new int[] { numColorBands }, hints);
1587:                    final int[] opaqueBands = new int[numColorBands];
1588:                    for (int i = 0; i < opaqueBands.length; i++) {
1589:                        opaqueBands[i] = i;
1590:                    }
1591:                    image = BandSelectDescriptor.create(image, opaqueBands,
1592:                            hints);
1593:                }
1594:                final byte[][] tableData = new byte[numColorBands][256];
1595:                final boolean singleStep = (numColorBands == 1);
1596:                if (singleStep) {
1597:                    final byte[] data = tableData[0];
1598:                    Arrays.fill(data, (byte) 255);
1599:                    data[transparentColor.getRed()] = 0;
1600:                } else {
1601:                    for (int j = 0; j < numColorBands; j++) {
1602:                        final byte[] data = tableData[j];
1603:                        for (int i = 0; i < data.length; i++) {
1604:                            data[i] = (byte) i;
1605:                        }
1606:                    }
1607:                    if (true) {
1608:                        // TODO: BUG ???????????????
1609:                        // The previous code was written in an other way with an end result as below, which
1610:                        // sound like a bug to me. But I'm dont understand well enough what the code tries
1611:                        // to do, so I reproduce here what the old code did.
1612:                        Arrays.fill(tableData[1], (byte) 0);
1613:                        Arrays.fill(tableData[2], (byte) 255);
1614:                    }
1615:                    switch (numColorBands) {
1616:                    case 3:
1617:                        tableData[2][transparentColor.getBlue()] = 0; // fall through
1618:                    case 2:
1619:                        tableData[1][transparentColor.getGreen()] = 0; // fall through
1620:                    case 1:
1621:                        tableData[0][transparentColor.getRed()] = 0; // fall through
1622:                    case 0:
1623:                        break;
1624:                    }
1625:                }
1626:                // Create a LookupTableJAI object to be used with the "lookup" operator.
1627:                LookupTableJAI table = new LookupTableJAI(tableData);
1628:                // Do the lookup operation.
1629:                PlanarImage luImage = LookupDescriptor.create(image, table,
1630:                        hints);
1631:                /*
1632:                 * Now that we have performed the lookup operation we have to remember
1633:                 * what we stated here above.
1634:                 *
1635:                 * If the input image is multibanded we will get a multiband image as
1636:                 * the output of the lookup operation hence we need to perform some form
1637:                 * of band combination to get the alpha band out of the lookup image.
1638:                 *
1639:                 * The way we wanted things to be done is by exploiting the clamping
1640:                 * behaviour that kicks in when we do sums and the like on pixels and
1641:                 * we overcome the maximum value allowed by the DataBufer DataType.
1642:                 */
1643:                if (!singleStep) {
1644:                    // We simply add the three generated bands together in order to get the right.
1645:                    final double[][] matrix = new double[1][4];
1646:                    // Values at index 0,1,2 are set to 1.0, value at index 3 is left to 0.
1647:                    Arrays.fill(matrix[0], 0, 3, 1.0);
1648:                    luImage = BandCombineDescriptor.create(luImage, matrix,
1649:                            hints);
1650:                    if (transparentBand != null) {
1651:                        luImage = fork(luImage).binarize(254)
1652:                                .forceComponentColorModel().retainFirstBand()
1653:                                .getPlanarImage();
1654:                        luImage = MultiplyDescriptor.create(transparentBand,
1655:                                luImage, hints);
1656:                    }
1657:                }
1658:                image = BandMergeDescriptor.create(image, luImage, hints);
1659:                invalidateStatistics();
1660:                return this ;
1661:            }
1662:
1663:            /**
1664:             * Inverts the pixel values of the {@linkplain #image}.
1665:             *
1666:             * @see InvertDescriptor
1667:             */
1668:            public final ImageWorker invert() {
1669:                image = InvertDescriptor.create(image, getRenderingHints());
1670:                invalidateStatistics();
1671:                return this ;
1672:            }
1673:
1674:            /**
1675:             * Applies the specified mask over the current {@linkplain #image}. The mask should be
1676:             * {@linkplain #binarize() binarized} - if it is not, this method will do it itself.
1677:             * Then, for every pixels in the mask with value equals to {@code maskValue}, the
1678:             * corresponding pixel in the {@linkplain #image} will be set to the specified
1679:             * {@code newValue}.
1680:             * <p>
1681:             * <strong>Note:</strong> current implementation force the color model to an
1682:             * {@linkplain IndexColorModel indexed} one. Future versions may avoid this change.
1683:             *
1684:             * @param mask
1685:             *            The mask to apply, as a {@linkplain #binarize() binarized} image.
1686:             * @param maskValue
1687:             *            The mask value to search for ({@code false} for 0 or {@code true} for 1).
1688:             * @param newValue
1689:             *            The new value for every pixels in {@linkplain #image}
1690:             *            corresponding to {@code maskValue} in the mask.
1691:             *
1692:             * @return this {@link ImageWorker}.
1693:             *
1694:             * @todo This now should work only if {@code newValue} is 255
1695:             *       and {@code maskValue} is {@code false}.
1696:             */
1697:            public final ImageWorker mask(RenderedImage mask,
1698:                    final boolean maskValue, int newValue) {
1699:                /*
1700:                 * Makes sure that the underlying image is indexed.
1701:                 */
1702:                tileCacheEnabled(false);
1703:                forceIndexColorModel(true);
1704:                final RenderingHints hints = new RenderingHints(
1705:                        JAI.KEY_TILE_CACHE, null);
1706:                /*
1707:                 * special case for newValue == 255 && !maskValue.
1708:                 */
1709:                if (newValue == 255 && !maskValue) {
1710:                    /*
1711:                     * Build a lookup table in order to make the transparent pixels
1712:                     * equal to 255 and all the others equal to 0.
1713:                     */
1714:                    final byte[] lutData = new byte[256]; // Initially filled to 0.
1715:                    lutData[0] = (byte) 255; // for transparent pixels.
1716:                    final LookupTableJAI lut = new LookupTableJAI(lutData);
1717:                    mask = LookupDescriptor.create(mask, lut, hints);
1718:                    /*
1719:                     * Adding to the other image exploiting the implict clamping.
1720:                     */
1721:                    image = AddDescriptor.create(image, mask,
1722:                            getRenderingHints());
1723:                    tileCacheEnabled(true);
1724:                    invalidateStatistics();
1725:                } else {
1726:                    // General case. It has to be binary
1727:                    if (!isBinary()) {
1728:                        binarize();
1729:                    }
1730:                    // Now if we mask with 1 we have to invert the mask.
1731:                    if (maskValue) {
1732:                        mask = NotDescriptor.create(mask, new RenderingHints(
1733:                                JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
1734:                                Boolean.FALSE));
1735:                    }
1736:                    // And with the image to zero the interested pixels.
1737:                    tileCacheEnabled(false);
1738:                    image = AndDescriptor.create(mask, image,
1739:                            getRenderingHints());
1740:
1741:                    // Add the new value to the mask.
1742:                    mask = AddConstDescriptor.create(mask,
1743:                            new double[] { newValue }, new RenderingHints(
1744:                                    JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
1745:                                    Boolean.FALSE));
1746:
1747:                    // Add the mask to the image to mask with the new value
1748:                    image = AddDescriptor.create(mask, image,
1749:                            getRenderingHints());
1750:                    tileCacheEnabled(true);
1751:                    invalidateStatistics();
1752:                }
1753:                return this ;
1754:            }
1755:
1756:            /**
1757:             * Takes two rendered or renderable source images, and adds every pair of pixels, one from
1758:             * each source image of the corresponding position and band. See JAI {@link AddDescriptor}
1759:             * for details.
1760:             *
1761:             * @param renderedImage
1762:             *            the {@link RenderedImage} to be added to this {@link ImageWorker}.
1763:             * @return this {@link ImageWorker}.
1764:             *
1765:             * @see AddDescriptor
1766:             */
1767:            public final ImageWorker addImage(final RenderedImage renderedImage) {
1768:                image = AddDescriptor.create(image, renderedImage,
1769:                        getRenderingHints());
1770:                invalidateStatistics();
1771:                return this ;
1772:            }
1773:
1774:            /**
1775:             * Takes one rendered or renderable image and an array of double constants, and multiplies
1776:             * every pixel of the same band of the source by the constant from the corresponding array
1777:             * entry. See JAI {@link MultiplyConstDescriptor} for details.
1778:             *
1779:             * @param inValues
1780:             *            The constants to be multiplied.
1781:             * @return this {@link ImageWorker}.
1782:             *
1783:             * @see MultiplyConstDescriptor
1784:             */
1785:            public final ImageWorker multiplyConst(double[] inValues) {
1786:                image = MultiplyConstDescriptor.create(image, inValues,
1787:                        getRenderingHints());
1788:                invalidateStatistics();
1789:                return this ;
1790:            }
1791:
1792:            /**
1793:             * Takes one rendered or renderable image and an array of integer constants, and performs a
1794:             * bit-wise logical "xor" between every pixel in the same band of the source and the constant
1795:             * from the corresponding array entry. See JAI {@link XorConstDescriptor} for details.
1796:             *
1797:             * @see XorConstDescriptor
1798:             */
1799:            public final ImageWorker xorConst(int[] values) {
1800:                image = XorConstDescriptor.create(image, values,
1801:                        getRenderingHints());
1802:                invalidateStatistics();
1803:                return this ;
1804:            }
1805:
1806:            /**
1807:             * Adds transparency to a preexisting image whose color model is
1808:             * {@linkplain IndexColorModel index color model}. For all pixels with the
1809:             * value {@code false} in the specified transparency mask, the corresponding
1810:             * pixel in the {@linkplain #image} is set to the transparent pixel value.
1811:             * All other pixels are left unchanged.
1812:             *
1813:             * @param alphaChannel
1814:             *            The mask to apply as a {@linkplain #binarize() binarized} image.
1815:             * @param errorDiffusion
1816:             *            Tells if I should use {@link ErrorDiffusionDescriptor} or
1817:             *            {@link OrderedDitherDescriptor} JAi operations.
1818:             * @return this {@link ImageWorker}.
1819:             *
1820:             * @see #isTranslucent
1821:             * @see #forceBitmaskIndexColorModel
1822:             */
1823:            public ImageWorker addTransparencyToIndexColorModel(
1824:                    final RenderedImage alphaChannel,
1825:                    final boolean errorDiffusion) {
1826:                addTransparencyToIndexColorModel(alphaChannel, true,
1827:                        getTransparentPixel(), errorDiffusion);
1828:                return this ;
1829:            }
1830:
1831:            /**
1832:             * Adds transparency to a preexisting image whose color model is {@linkplain IndexColorModel
1833:             * index color model}. First, this method creates a new index color model with the specified
1834:             * {@code transparent} pixel, if needed (this method may skip this step if the specified pixel
1835:             * is already transparent. Then for all pixels with the value {@code false} in the specified
1836:             * transparency mask, the corresponding pixel in the {@linkplain #image} is set to that
1837:             * transparent value. All other pixels are left unchanged.
1838:             *
1839:             * @param alphaChannel
1840:             *            The mask to apply as a {@linkplain #binarize() binarized} image.
1841:             * @param translucent
1842:             *            {@code true} if {@linkplain Transparency#TRANSLUCENT translucent} images are
1843:             *            allowed, or {@code false} if the resulting images must be a
1844:             *            {@linkplain Transparency#BITMASK bitmask}.
1845:             * @param transparent
1846:             *            The value for transparent pixels, to be given to every pixels in the
1847:             *            {@linkplain #image} corresponding to {@code false} in the mask. The
1848:             *            special value {@code -1} maps to the last pixel value allowed for the
1849:             *            {@linkplain IndexedColorModel indexed color model}.
1850:             * @param errorDiffusion
1851:             *            Tells if I should use {@link ErrorDiffusionDescriptor} or
1852:             *            {@link OrderedDitherDescriptor} JAi operations.
1853:             *
1854:             * @return this {@link ImageWorker}.
1855:             */
1856:            public final ImageWorker addTransparencyToIndexColorModel(
1857:                    final RenderedImage alphaChannel,
1858:                    final boolean translucent, int transparent,
1859:                    final boolean errorDiffusion) {
1860:                tileCacheEnabled(false);
1861:                forceIndexColorModel(errorDiffusion);
1862:                tileCacheEnabled(true);
1863:                /*
1864:                 * Prepares hints and layout to use for mask operations. A color model
1865:                 * hint will be set only if the block below is executed.
1866:                 */
1867:                final ImageWorker worker = fork(image);
1868:                final RenderingHints hints = worker.getRenderingHints();
1869:                /*
1870:                 * Gets the index color model. If the specified 'transparent' value is not fully
1871:                 * transparent, replaces the color model by a new one with the transparent pixel
1872:                 * defined. NOTE: the "transparent &= (1 << pixelSize) - 1" instruction below is
1873:                 * a safety for making sure that the transparent index value can hold in the amount
1874:                 * of bits allowed for this color model (the mapSize value may not use all bits).
1875:                 * It works as expected with the -1 special value. It also make sure that
1876:                 * "transparent + 1" do not exeed the maximum map size allowed.
1877:                 */
1878:                final boolean forceBitmask;
1879:                final IndexColorModel oldCM = (IndexColorModel) image
1880:                        .getColorModel();
1881:                final int pixelSize = oldCM.getPixelSize();
1882:                transparent &= (1 << pixelSize) - 1;
1883:                forceBitmask = !translucent
1884:                        && oldCM.getTransparency() == Transparency.TRANSLUCENT;
1885:                if (forceBitmask || oldCM.getTransparentPixel() != transparent) {
1886:                    final int mapSize = Math.max(oldCM.getMapSize(),
1887:                            transparent + 1);
1888:                    final byte[][] RGBA = new byte[translucent ? 4 : 3][mapSize];
1889:                    // Note: we might use less that 256 values.
1890:                    oldCM.getReds(RGBA[0]);
1891:                    oldCM.getGreens(RGBA[1]);
1892:                    oldCM.getBlues(RGBA[2]);
1893:                    final IndexColorModel newCM;
1894:                    if (translucent) {
1895:                        oldCM.getAlphas(RGBA[3]);
1896:                        RGBA[3][transparent] = 0;
1897:                        newCM = new IndexColorModel(pixelSize, mapSize,
1898:                                RGBA[0], RGBA[1], RGBA[2], RGBA[3]);
1899:                    } else {
1900:                        newCM = new IndexColorModel(pixelSize, mapSize,
1901:                                RGBA[0], RGBA[1], RGBA[2], transparent);
1902:                    }
1903:                    /*
1904:                     * Set the color model hint.
1905:                     */
1906:                    final ImageLayout layout = getImageLayout(hints);
1907:                    layout.setColorModel(newCM);
1908:                    worker.setRenderingHint(JAI.KEY_IMAGE_LAYOUT, layout);
1909:                }
1910:                /*
1911:                 * Applies the mask, maybe with a color model change.
1912:                 */
1913:                worker.setRenderingHint(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
1914:                        Boolean.FALSE);
1915:                worker.mask(alphaChannel, false, transparent);
1916:                image = worker.image;
1917:                invalidateStatistics();
1918:
1919:                // All post conditions for this method contract.
1920:                assert isIndexed();
1921:                assert translucent || !isTranslucent() : translucent;
1922:                assert ((IndexColorModel) image.getColorModel())
1923:                        .getAlpha(transparent) == 0;
1924:                return this ;
1925:            }
1926:
1927:            /**
1928:             * If the was not already tiled, tile it. Note that no tiling will be done
1929:             * if 'getRenderingHints()' failed to suggest a tile size. This method is
1930:             * for internal use by {@link #write} methods only.
1931:             *
1932:             * @return this {@link ImageWorker}.
1933:             */
1934:            public final ImageWorker tile() {
1935:                final RenderingHints hints = getRenderingHints();
1936:                final ImageLayout layout = getImageLayout(hints);
1937:                if (layout.isValid(ImageLayout.TILE_WIDTH_MASK)
1938:                        || layout.isValid(ImageLayout.TILE_HEIGHT_MASK)) {
1939:                    final int type = image.getSampleModel().getDataType();
1940:                    image = FormatDescriptor.create(image, new Integer(type),
1941:                            hints);
1942:                }
1943:                return this ;
1944:            }
1945:
1946:            /**
1947:             * Writes the {@linkplain #image} to the specified file. This method differs
1948:             * from {@link ImageIO#write(String,File)} in a number of ways:
1949:             * <p>
1950:             * <ul>
1951:             *   <li>The {@linkplain ImageWriter image writer} to use is inferred from the file
1952:             *       extension.</li>
1953:             *   <li>If the image writer accepts {@link File} objects as input, then the {@code file}
1954:             *       argument is given directly without creating an {@link ImageOutputStream} object.
1955:             *       This is important for some formats like HDF, which work <em>only</em> with files.</li>
1956:             *   <li>If the {@linkplain #image} is not tiled, then it is tiled prior to be written.</li>
1957:             *   <li>If some special processing is needed for a given format, then the corresponding method
1958:             *       is invoked. Example: {@link #forceIndexColorModelForGIF}.</li>
1959:             * </ul>
1960:             *
1961:             * @return this {@link ImageWorker}.
1962:             */
1963:            public final ImageWorker write(final File output)
1964:                    throws IOException {
1965:                final String filename = output.getName();
1966:                final int dot = filename.lastIndexOf('.');
1967:                if (dot < 0) {
1968:                    throw new IIOException(Errors
1969:                            .format(ErrorKeys.NO_IMAGE_WRITER));
1970:                }
1971:                final String extension = filename.substring(dot + 1).trim();
1972:                write(output, ImageIO.getImageWritersBySuffix(extension));
1973:                return this ;
1974:            }
1975:
1976:            /**
1977:             * Writes outs the image contained into this {@link ImageWorker} as a PNG
1978:             * using the provided destination, compression and compression rate.
1979:             * <p>
1980:             * The destination object can be anything providing that we have an
1981:             * {@link ImageOutputStreamSpi} that recognizes it.
1982:             *
1983:             * @param destination
1984:             *            where to write the internal {@link #image} as a PNG.
1985:             * @param compression
1986:             *            algorithm.
1987:             * @param compressionRate
1988:             *            percentage of compression.
1989:             * @param nativeAcc
1990:             *            should we use native acceleration.
1991:             * @param paletted
1992:             *            should we write the png as 8 bits?
1993:             * @return this {@link ImageWorker}.
1994:             * @throws IOException
1995:             *             In case an error occurs during the search for an
1996:             *             {@link ImageOutputStream} or during the eoncding process.
1997:             *
1998:             * @todo Current code doesn't check if the writer already accepts the provided destination.
1999:             *       It wraps it in a {@link ImageOutputStream} inconditionnaly.
2000:             */
2001:            public final void writePNG(final Object destination,
2002:                    final String compression, final float compressionRate,
2003:                    final boolean nativeAcc, final boolean paletted)
2004:                    throws IOException {
2005:                // Reformatting this image for PNG.
2006:                tileCacheEnabled(false);
2007:                if (!paletted) {
2008:                    forceComponentColorModel();
2009:                } else {
2010:                    forceIndexColorModelForGIF(true);
2011:                }
2012:                LOGGER.finer("Encoded input image for png writer");
2013:
2014:                // Getting a writer.
2015:                LOGGER.finer("Getting a writer");
2016:                final Iterator it = ImageIO.getImageWritersByFormatName("PNG");
2017:                if (!it.hasNext()) {
2018:                    throw new IllegalStateException(Errors
2019:                            .format(ErrorKeys.NO_IMAGE_WRITER));
2020:                }
2021:                ImageWriter writer = (ImageWriter) it.next();
2022:
2023:                // Getting a stream.
2024:                LOGGER.finer("Setting write parameters for this writer");
2025:                ImageWriteParam iwp = null;
2026:                final ImageOutputStream memOutStream = ImageIO
2027:                        .createImageOutputStream(destination);
2028:                if (nativeAcc
2029:                        && writer
2030:                                .getClass()
2031:                                .getName()
2032:                                .equals(
2033:                                        "com.sun.media.imageioimpl.plugins.png.CLibPNGImageWriter")) {
2034:                    // Compressing with native.
2035:                    LOGGER.finer("Writer is native");
2036:                    iwp = writer.getDefaultWriteParam();
2037:                    // Define compression mode
2038:                    iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
2039:                    // best compression
2040:                    iwp.setCompressionType(compression);
2041:                    // we can control quality here
2042:                    iwp.setCompressionQuality(compressionRate);
2043:                    // destination image type
2044:                    iwp.setDestinationType(new ImageTypeSpecifier(image
2045:                            .getColorModel(), image.getSampleModel()));
2046:                } else {
2047:                    // Compressing with pure Java.
2048:                    // pure java from native
2049:                    if (nativeAcc
2050:                            && it.hasNext()
2051:                            && writer
2052:                                    .getClass()
2053:                                    .getName()
2054:                                    .equals(
2055:                                            "com.sun.media.imageioimpl.plugins.png.CLibPNGImageWriter")) {
2056:                        writer = (ImageWriter) it.next();
2057:                    }
2058:                    LOGGER.finer("Writer is NOT native");
2059:                    // Instantiating PNGImageWriteParam
2060:                    iwp = new PNGImageWriteParam();
2061:                    // Define compression mode
2062:                    iwp.setCompressionMode(ImageWriteParam.MODE_DEFAULT);
2063:                }
2064:                LOGGER.finer("About to write png image");
2065:                writer.setOutput(memOutStream);
2066:                writer.write(null, new IIOImage(image, null, null), iwp);
2067:                tileCacheEnabled(true);
2068:                memOutStream.flush();
2069:                writer.dispose();
2070:                memOutStream.close();
2071:            }
2072:
2073:            /**
2074:             * Writes outs the image contained into this {@link ImageWorker} as a GIF
2075:             * using the provided destination, compression and compression rate.
2076:             * <p>
2077:             * It is worth to point out that the only compressions algorithm availaible
2078:             * with the jdk {@link GIFImageWriter} is "LZW" while the compression rates
2079:             * have to be confined between 0 and 1. AN acceptable values is usally 0.75f.
2080:             * <p>
2081:             * The destination object can be anything providing that we have an
2082:             * {@link ImageOutputStreamSpi} that recognizes it.
2083:             *
2084:             * @param destination
2085:             *            where to write the internal {@link #image} as a gif.
2086:             * @param compression
2087:             *            The name of compression algorithm.
2088:             * @param compressionRate
2089:             *            percentage of compression, as a number between 0 and 1.
2090:             * @return this {@link ImageWorker}.
2091:             * @throws IOException
2092:             *             In case an error occurs during the search for an
2093:             *             {@link ImageOutputStream} or during the eoncding process.
2094:             *
2095:             * @see #forceIndexColorModelForGIF(boolean)
2096:             */
2097:            public final ImageWorker writeGIF(final Object destination,
2098:                    final String compression, final float compressionRate)
2099:                    throws IOException {
2100:                tileCacheEnabled(false);
2101:                forceIndexColorModelForGIF(true);
2102:                tileCacheEnabled(true);
2103:                final IIORegistry registry = IIORegistry.getDefaultInstance();
2104:                Iterator it = registry.getServiceProviders(
2105:                        ImageWriterSpi.class, true);
2106:                ImageWriterSpi spi = null;
2107:                while (it.hasNext()) {
2108:                    final ImageWriterSpi candidate = (ImageWriterSpi) it.next();
2109:                    if (containsFormatName(candidate.getFormatNames(), "gif")) {
2110:                        if (spi == null) {
2111:                            spi = candidate;
2112:                        } else {
2113:                            final String name = candidate.getClass().getName();
2114:                            if (name
2115:                                    .equals("com.sun.media.imageioimpl.plugins.gif.GIFImageWriterSpi")) {
2116:                                spi = candidate;
2117:                                break;
2118:                            }
2119:                        }
2120:                    }
2121:                }
2122:                if (spi == null) {
2123:                    throw new IIOException(Errors
2124:                            .format(ErrorKeys.NO_IMAGE_WRITER));
2125:                }
2126:                final ImageOutputStream stream = ImageIO
2127:                        .createImageOutputStream(destination);
2128:                final ImageWriter writer = spi.createWriterInstance();
2129:                final ImageWriteParam param = writer.getDefaultWriteParam();
2130:                param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
2131:                param.setCompressionType(compression);
2132:                param.setCompressionQuality(compressionRate);
2133:
2134:                writer.setOutput(stream);
2135:                writer.write(null, new IIOImage(image, null, null), param);
2136:                stream.close();
2137:                writer.dispose();
2138:                return this ;
2139:            }
2140:
2141:            /**
2142:             * Writes outs the image contained into this {@link ImageWorker} as a JPEG
2143:             * using the provided destination , compression and compression rate.
2144:             * <p>
2145:             * The destination object can be anything providing that we have an
2146:             * {@link ImageOutputStreamSpi} that recognizes it.
2147:             *
2148:             * @param destination
2149:             *            where to write the internal {@link #image} as a JPEG.
2150:             * @param compression
2151:             *            algorithm.
2152:             * @param compressionRate
2153:             *            percentage of compression.
2154:             * @param nativeAcc
2155:             *            should we use native acceleration.
2156:             * @return this {@link ImageWorker}.
2157:             * @throws IOException
2158:             *             In case an error occurs during the search for an
2159:             *             {@link ImageOutputStream} or during the eoncding process.
2160:             */
2161:            public final void writeJPEG(final Object destination,
2162:                    final String compression, final float compressionRate,
2163:                    final boolean nativeAcc) throws IOException {
2164:                // Reformatting this image for jpeg.
2165:                LOGGER.finer("Encoding input image to write out as JPEG.");
2166:                tileCacheEnabled(false);
2167:                final ColorModel cm = image.getColorModel();
2168:                final boolean indexColorModel = image.getColorModel() instanceof  IndexColorModel;
2169:                final int numBands = image.getSampleModel().getNumBands();
2170:                final boolean hasAlpha = cm.hasAlpha();
2171:                if (indexColorModel || hasAlpha) {
2172:                    if (indexColorModel) {
2173:                        forceComponentColorModel();
2174:                    }
2175:                    if (hasAlpha) {
2176:                        retainBands(numBands - 1);
2177:                    }
2178:                }
2179:                tileCacheEnabled(true);
2180:
2181:                // Getting a writer.
2182:                LOGGER.finer("Getting a JPEG writer and configuring it.");
2183:                final Iterator it = ImageIO.getImageWritersByFormatName("JPEG");
2184:                if (!it.hasNext()) {
2185:                    throw new IllegalStateException(Errors
2186:                            .format(ErrorKeys.NO_IMAGE_WRITER));
2187:                }
2188:                ImageWriter writer = (ImageWriter) it.next();
2189:                if (!nativeAcc
2190:                        && writer
2191:                                .getClass()
2192:                                .getName()
2193:                                .equals(
2194:                                        "com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageWriter")) {
2195:                    writer = (ImageWriter) it.next();
2196:                }
2197:
2198:                // Compression is available on both lib
2199:                final ImageWriteParam iwp = writer.getDefaultWriteParam();
2200:                final ImageOutputStream outStream = ImageIO
2201:                        .createImageOutputStream(destination);
2202:                iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
2203:                iwp.setCompressionType(compression); // Lossy compression.
2204:                iwp.setCompressionQuality(compressionRate); // We can control quality here.
2205:                writer.setOutput(outStream);
2206:                if (iwp instanceof  JPEGImageWriteParam) {
2207:                    final JPEGImageWriteParam param = (JPEGImageWriteParam) iwp;
2208:                    param.setOptimizeHuffmanTables(true);
2209:                    try {
2210:                        param
2211:                                .setProgressiveMode(JPEGImageWriteParam.MODE_DEFAULT);
2212:                    } catch (UnsupportedOperationException e) {
2213:                        throw (IOException) new IOException().initCause(e);
2214:                        // TODO: inline cause when we will be allowed to target Java 6.
2215:                    }
2216:                }
2217:                LOGGER.finer("Writing out...");
2218:                writer.write(null, new IIOImage(image, null, null), iwp);
2219:                outStream.flush();
2220:                writer.dispose();
2221:                outStream.close();
2222:            }
2223:
2224:            /**
2225:             * Writes the {@linkplain #image} to the specified output, trying all
2226:             * encoders in the specified iterator in the iteration order.
2227:             *
2228:             * @return this {@link ImageWorker}.
2229:             */
2230:            private ImageWorker write(final Object output,
2231:                    final Iterator encoders) throws IOException {
2232:                if (encoders != null) {
2233:                    while (encoders.hasNext()) {
2234:                        final ImageWriter writer = (ImageWriter) encoders
2235:                                .next();
2236:                        final ImageWriterSpi spi = writer
2237:                                .getOriginatingProvider();
2238:                        final Class[] outputTypes;
2239:                        if (spi == null) {
2240:                            outputTypes = ImageWriterSpi.STANDARD_OUTPUT_TYPE;
2241:                        } else {
2242:                            /*
2243:                             * If the encoder is for some format handled in a special way (e.g. GIF), apply
2244:                             * the required operation. Note that invoking the same method many time (e.g.
2245:                             * "forceIndexColorModelForGIF", which could occurs if there is more than one
2246:                             * GIF encoder registered) should not hurt - all method invocation after the
2247:                             * first one should be no-op.
2248:                             */
2249:                            final String[] formats = spi.getFormatNames();
2250:                            if (containsFormatName(formats, "gif")) {
2251:                                forceIndexColorModelForGIF(true);
2252:                            } else {
2253:                                tile();
2254:                            }
2255:                            if (!spi.canEncodeImage(image)) {
2256:                                continue;
2257:                            }
2258:                            outputTypes = spi.getOutputTypes();
2259:                        }
2260:                        /*
2261:                         * Now try to set the output directly (if possible), or as an ImageOutputStream if
2262:                         * the encoder doesn't accept directly the specified output. Note that some formats
2263:                         * like HDF may not support ImageOutputStream.
2264:                         */
2265:                        final ImageOutputStream stream;
2266:                        if (acceptInputType(outputTypes, output.getClass())) {
2267:                            writer.setOutput(output);
2268:                            stream = null;
2269:                        } else if (acceptInputType(outputTypes,
2270:                                ImageOutputStream.class)) {
2271:                            stream = ImageIO.createImageOutputStream(output);
2272:                            writer.setOutput(stream);
2273:                        } else {
2274:                            continue;
2275:                        }
2276:                        /*
2277:                         * Now saves the image.
2278:                         */
2279:                        writer.write(image);
2280:                        writer.dispose();
2281:                        if (stream != null) {
2282:                            stream.close();
2283:                        }
2284:                        return this ;
2285:                    }
2286:                }
2287:                throw new IIOException(Errors.format(ErrorKeys.NO_IMAGE_WRITER));
2288:            }
2289:
2290:            /**
2291:             * Returns {@code true} if the specified array contains the specified type.
2292:             */
2293:            private static boolean acceptInputType(final Class[] types,
2294:                    final Class searchFor) {
2295:                for (int i = types.length; --i >= 0;) {
2296:                    if (searchFor.isAssignableFrom(types[i])) {
2297:                        return true;
2298:                    }
2299:                }
2300:                return false;
2301:            }
2302:
2303:            /**
2304:             * Returns {@code true} if the specified array contains the specified string.
2305:             */
2306:            private static boolean containsFormatName(final String[] formats,
2307:                    final String searchFor) {
2308:                for (int i = formats.length; --i >= 0;) {
2309:                    if (searchFor.equalsIgnoreCase(formats[i])) {
2310:                        return true;
2311:                    }
2312:                }
2313:                return false;
2314:            }
2315:
2316:            ///////////////////////////////////////////////////////////////////////////////////////
2317:            ////////                                                                       ////////
2318:            ////////                             DEBUGING HELP                             ////////
2319:            ////////                                                                       ////////
2320:            ///////////////////////////////////////////////////////////////////////////////////////
2321:
2322:            /**
2323:             * Shows the current {@linkplain #image} in a window together with the operation chain as a
2324:             * {@linkplain javax.swing.JTree tree}. This method is provided mostly for debugging purpose.
2325:             * This method requires the {@code gt2-widgets-swing.jar} file in the classpath.
2326:             *
2327:             * @throws HeadlessException
2328:             *             if {@code gt2-widgets-swing.jar} is not on the classpath, or
2329:             *             if AWT can't create the window components.
2330:             * @return this {@link ImageWorker}.
2331:             *
2332:             * @see org.geotools.gui.swing.image.OperationTreeBrowser#show(RenderedImage)
2333:             */
2334:            public final ImageWorker show() throws HeadlessException {
2335:                /*
2336:                 * Uses reflection because the "gt2-widgets-swing.jar" dependency is optional and may not
2337:                 * be available in the classpath. All the complicated stuff below is simply doing this call:
2338:                 *
2339:                 *     OperationTreeBrowser.show(image);
2340:                 *
2341:                 * Tip: The @see tag in the above javadoc can be used as a check for the existence
2342:                 *      of class and method referenced below. Check for the javadoc warnings.
2343:                 */
2344:                final Class c;
2345:                try {
2346:                    c = Class
2347:                            .forName("org.geotools.gui.swing.image.OperationTreeBrowser");
2348:                } catch (ClassNotFoundException cause) {
2349:                    final HeadlessException e;
2350:                    e = new HeadlessException(
2351:                            "The \"gt2-widgets-swing.jar\" file is required.");
2352:                    e.initCause(cause);
2353:                    throw e;
2354:                }
2355:                try {
2356:                    c.getMethod("show", new Class[] { RenderedImage.class })
2357:                            .invoke(null, new Object[] { image });
2358:                } catch (InvocationTargetException e) {
2359:                    final Throwable cause = e.getCause();
2360:                    if (cause instanceof  RuntimeException) {
2361:                        throw (RuntimeException) cause;
2362:                    }
2363:                    if (cause instanceof  Error) {
2364:                        throw (Error) cause;
2365:                    }
2366:                    throw new AssertionError(e);
2367:                } catch (Exception e) {
2368:                    /*
2369:                     * ClassNotFoundException may be expected, but all other kinds of
2370:                     * checked exceptions (and they are numerous...) are errors.
2371:                     */
2372:                    throw new AssertionError(e);
2373:                }
2374:                return this ;
2375:            }
2376:
2377:            /**
2378:             * Loads the image from the specified file, and {@linkplain #show display}
2379:             * it in a window. This method is mostly as a convenient way to test
2380:             * operation chains. This method can be invoked from the command line. If an
2381:             * optional {@code -operation} argument is provided, the Java method (one of
2382:             * the image operations provided in this class) immediately following it is
2383:             * executed. Example:
2384:             *
2385:             * <blockquote><pre>
2386:             * java org.geotools.image.ImageWorker -operation binarize &lt;var&gt;&lt;filename&gt;&lt;/var&gt;
2387:             * </pre></blockquote>
2388:             */
2389:            public static void main(String[] args) {
2390:                final Arguments arguments = new Arguments(args);
2391:                final String operation = arguments
2392:                        .getOptionalString("-operation");
2393:                args = arguments.getRemainingArguments(1);
2394:                if (args.length != 0)
2395:                    try {
2396:                        final ImageWorker worker = new ImageWorker(new File(
2397:                                args[0]));
2398:                        // Force usage of tile cache for every operations, including intermediate steps.
2399:                        worker.setRenderingHint(JAI.KEY_TILE_CACHE, JAI
2400:                                .getDefaultInstance().getTileCache());
2401:                        if (operation != null) {
2402:                            worker.getClass().getMethod(operation,
2403:                                    (Class[]) null).invoke(worker,
2404:                                    (Object[]) null);
2405:                        }
2406:                        /*
2407:                         * TIP: Tests operations here (before the call to 'show()'), if wanted.
2408:                         */
2409:                        worker.show();
2410:                    } catch (FileNotFoundException e) {
2411:                        arguments.printSummary(e);
2412:                    } catch (NoSuchMethodException e) {
2413:                        arguments.printSummary(e);
2414:                    } catch (Exception e) {
2415:                        e.printStackTrace(arguments.err);
2416:                    }
2417:            }
2418:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.