EMMA Coverage Report (generated Sun Apr 20 22:38:01 CEST 2008)
[all classes][net.sf.jomic.tools]

COVERAGE SUMMARY FOR SOURCE FILE [ImageTools.java]

nameclass, %method, %block, %line, %
ImageTools.java100% (1/1)87%  (39/45)77%  (1635/2123)84%  (316.6/379)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ImageTools100% (1/1)87%  (39/45)77%  (1635/2123)84%  (316.6/379)
createBlurKernel (int): KernelJAI 0%   (0/1)0%   (0/94)0%   (0/13)
getBluredImage (RenderedImage, String): RenderedImage 0%   (0/1)0%   (0/57)0%   (0/9)
getNumBands (RenderedImage): int 0%   (0/1)0%   (0/13)0%   (0/2)
getRotatedImage (RenderedImage, float): RenderedImage 0%   (0/1)0%   (0/60)0%   (0/13)
isImageFile (File): boolean 0%   (0/1)0%   (0/15)0%   (0/2)
isValidBlurMode (String): boolean 0%   (0/1)0%   (0/7)0%   (0/1)
assertIsValidScaleMode (String): void 100% (1/1)37%  (7/19)67%  (1.3/2)
assertIsValidRotation (int): void 100% (1/1)43%  (9/21)70%  (1.4/2)
isImageFile (String): boolean 100% (1/1)73%  (11/15)75%  (1.5/2)
getRotatedImage (RenderedImage, int): RenderedImage 100% (1/1)74%  (46/62)88%  (14/16)
createColorBox (int, int, Color): BufferedImage 100% (1/1)74%  (49/66)86%  (10.4/12)
getImageFormat (File): String 100% (1/1)76%  (16/21)94%  (6.6/7)
getAsBufferedImage (RenderedImage): BufferedImage 100% (1/1)78%  (14/18)88%  (3.5/4)
isImageSuffix (String): boolean 100% (1/1)78%  (14/18)75%  (1.5/2)
getAsImageIcon (RenderedImage): ImageIcon 100% (1/1)79%  (15/19)88%  (3.5/4)
possiblyAddImageFormatCopy (String, String): void 100% (1/1)79%  (76/96)84%  (10.9/13)
getAsQuickBufferedImage (RenderedImage): BufferedImage 100% (1/1)80%  (35/44)88%  (8.8/10)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
createBrokenImage (int, int, Color, Color): RenderedImage 100% (1/1)82%  (96/117)90%  (17.9/20)
getSqueezed (RenderedImage, int, int, String): RenderedImage 100% (1/1)83%  (83/100)92%  (18.4/20)
createBusyImage (int, int, Color, Color): RenderedImage 100% (1/1)84%  (113/134)90%  (19/21)
createImageInputStream (File): ImageInputStream 100% (1/1)85%  (22/26)92%  (5.5/6)
getImageDimension (File): Dimension 100% (1/1)85%  (22/26)92%  (5.5/6)
readImage (File): RenderedImage 100% (1/1)90%  (94/104)95%  (22.8/24)
getScaleToSqueeze (int, int, int, int, String): double 100% (1/1)91%  (123/135)96%  (17.3/18)
fillColorValues (Color, ColorModel): double [] 100% (1/1)91%  (128/140)94%  (19.8/21)
getImageFormat (ImageInputStream): String 100% (1/1)92%  (44/48)98%  (11.7/12)
isValidRotation (int): boolean 100% (1/1)93%  (14/15)93%  (0.9/1)
ImageTools (): void 100% (1/1)94%  (370/395)98%  (66.7/68)
isValidScaleMode (String): boolean 100% (1/1)95%  (21/22)95%  (1/1)
getDeltaRotation (int, int): int 100% (1/1)100% (18/18)100% (5/5)
getFixedRotation (int): int 100% (1/1)100% (17/17)100% (5/5)
getImageDimension (ImageInputStream): Dimension 100% (1/1)100% (56/56)100% (13/13)
getImageProviderMap (): Map 100% (1/1)100% (3/3)100% (1/1)
getImageReader (ImageInputStream): ImageReader 100% (1/1)100% (5/5)100% (1/1)
getLeftRotation (int): int 100% (1/1)100% (11/11)100% (3/3)
getPossibleBlurModes (): String [] 100% (1/1)100% (3/3)100% (1/1)
getRenderingHints (): RenderingHints 100% (1/1)100% (3/3)100% (1/1)
getRightRotation (int): int 100% (1/1)100% (11/11)100% (3/3)
getSqueezedDimension (int, int, int, int, String): Dimension 100% (1/1)100% (34/34)100% (5/5)
instance (): ImageTools 100% (1/1)100% (8/8)100% (3/3)
isCompressedImageFormat (String): boolean 100% (1/1)100% (11/11)100% (2/2)
isLandscape (Dimension): boolean 100% (1/1)100% (7/7)100% (1/1)
isLandscape (RenderedImage): boolean 100% (1/1)100% (7/7)100% (1/1)
isLandscape (int, int): boolean 100% (1/1)100% (7/7)100% (1/1)

1// Jomic - a viewer for comic book archives.
2// Copyright (C) 2004-2008 Thomas Aglassinger
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16package net.sf.jomic.tools;
17 
18import java.awt.BasicStroke;
19import java.awt.Color;
20import java.awt.Dimension;
21import java.awt.Graphics2D;
22import java.awt.RenderingHints;
23import java.awt.color.ColorSpace;
24import java.awt.geom.AffineTransform;
25import java.awt.geom.Ellipse2D;
26import java.awt.image.BufferedImage;
27import java.awt.image.ColorModel;
28import java.awt.image.RenderedImage;
29import java.awt.image.renderable.ParameterBlock;
30import java.io.File;
31import java.io.FileNotFoundException;
32import java.io.IOException;
33import java.util.Arrays;
34import java.util.Collections;
35import java.util.Iterator;
36import java.util.LinkedList;
37import java.util.List;
38import java.util.Map;
39import java.util.NoSuchElementException;
40import java.util.TreeMap;
41 
42import javax.imageio.IIOException;
43import javax.imageio.ImageIO;
44import javax.imageio.ImageReadParam;
45import javax.imageio.ImageReader;
46import javax.imageio.ImageTypeSpecifier;
47import javax.imageio.spi.ImageReaderSpi;
48import javax.imageio.stream.ImageInputStream;
49import javax.media.jai.BorderExtender;
50import javax.media.jai.JAI;
51import javax.media.jai.KernelJAI;
52import javax.media.jai.OperationRegistry;
53import javax.media.jai.ParameterBlockJAI;
54import javax.media.jai.PlanarImage;
55import javax.media.jai.RenderedImageAdapter;
56import javax.media.jai.operator.TransposeDescriptor;
57import javax.media.jai.operator.TransposeType;
58import javax.media.jai.registry.RIFRegistry;
59import javax.media.jai.util.ImagingListener;
60import javax.swing.ImageIcon;
61 
62import net.sf.jomic.common.Settings;
63import net.sf.wraplog.Logger;
64 
65/**
66 *  Utility methods for working with images.
67 *
68 * @author    Thomas Aglassinger
69 */
70public final class ImageTools
71{
72    public static final String DIFFERENCE_BLUR = "ThresholdDifferenceBlur";
73    public static final int DISTINCT_ROTATIONS_COUNT = 4;
74    public static final int ROTATE_CLOCKWISE = 1;
75    public static final int ROTATE_COUNTERCLOCKWISE = -1;
76    public static final int ROTATE_NONE = 0;
77    public static final int ROTATE_UPSIDE_DOWN = 2;
78    public static final String SCALE_ACTUAL = "actual";
79    public static final String SCALE_FIT = "fit";
80    public static final String SCALE_HEIGHT = "fitHeight";
81    public static final String SCALE_WIDTH = "fitWidth";
82    public static final String THRESHOLD_BLUR = "ThresholdBlur";
83 
84    /**
85     *  Index of brightness in HSB arrays.
86     */
87    private static final int BRIGHTNESS_INDEX = 2;
88    private static final int BROKEN_IMAGE_STROKE_INSET_DIVISOR = 6;
89    private static final int BUSY_IMAGE_STROKE_INSET_DIVISOR = 3;
90    private static final int STROKE_WIDTH_DIVISOR = 10;
91 
92    private static ImageTools instance;
93    private String[] compressedImageFormats;
94 
95    private FileTools fileTools;
96    private Map imageProviderMap;
97    private List imageSuffixList;
98    private Map imageSuffixMap;
99    private LocaleTools localeTools;
100    private Logger logger;
101    private String[] possibleBlurModes;
102    private RenderingHints renderHints;
103    private StringTools stringTools;
104 
105    private ImageTools() {
106        logger = Logger.getLogger(ImageTools.class);
107        fileTools = FileTools.instance();
108        localeTools = LocaleTools.instance();
109        stringTools = StringTools.instance();
110        imageSuffixMap = new TreeMap();
111        imageProviderMap = new TreeMap();
112 
113        compressedImageFormats = new String[]{"gif", "jpeg", "png"};
114        Arrays.sort(compressedImageFormats);
115 
116        possibleBlurModes = new String[]{
117                ImageTools.DIFFERENCE_BLUR, ImageTools.THRESHOLD_BLUR};
118        Arrays.sort(possibleBlurModes);
119 
120        // HACK: Force scanning for plug ins. This should not be necessary, but
121        // otherwise Java Web Start on Mac OS X doesn't seem to find them.
122        ImageIO.scanForPlugins();
123 
124        String[] formats = ImageIO.getReaderFormatNames();
125        ImagingListener listener = new ImagingExceptionThrowingImagingListener();
126 
127        JAI.getDefaultInstance().setImagingListener(listener);
128 
129        for (int i = 0; i < formats.length; i += 1) {
130            String format = formats[i].toLowerCase();
131            Iterator rider = ImageIO.getImageReadersByFormatName(format);
132 
133            assert rider.hasNext() : "no reader for format \"" + format;
134            ImageReader reader = (ImageReader) rider.next();
135            ImageReaderSpi provider = reader.getOriginatingProvider();
136 
137            imageSuffixMap.put(format, format);
138            imageProviderMap.put(format, provider);
139        }
140        renderHints = new RenderingHints(null);
141        renderHints.add(new RenderingHints(
142                RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY));
143        renderHints.add(new RenderingHints(
144                RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
145        renderHints.add(new RenderingHints(
146                RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY));
147        renderHints.add(new RenderingHints(
148                RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE));
149        renderHints.add(new RenderingHints(
150                RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR));
151        renderHints.add(new RenderingHints(
152                RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
153        // HACK: work-around for missing suffix in Apple's TIFF reader.
154        possiblyAddImageFormatCopy("tiff", "tif");
155        possiblyAddImageFormatCopy("jpeg 2000", "jp2");
156        imageSuffixList = new LinkedList(imageSuffixMap.values());
157        Collections.sort(imageSuffixList);
158 
159        // Register "ThresholdBlur" operator and its RIFs.
160        OperationRegistry registry = JAI.getDefaultInstance().getOperationRegistry();
161 
162        registry.registerDescriptor(new ThresholdBlurDescriptor());
163        RIFRegistry.register(registry, "ThresholdBlur", "JWS", new ThresholdBlurRIF());
164        registry.registerDescriptor(new ThresholdDifferenceBlurDescriptor());
165        RIFRegistry.register(registry, "ThresholdDifferenceBlur", "JWS", new ThresholdDifferenceBlurRIF());
166 
167        if (logger.isInfoEnabled()) {
168            String suffixes = "";
169            Iterator suffixRider = imageSuffixList.iterator();
170 
171            while (suffixRider.hasNext()) {
172                String suffix = (String) suffixRider.next();
173 
174                if (suffixes.length() > 0) {
175                    suffixes += ", ";
176                }
177                suffixes += suffix;
178            }
179            logger.info("supported image file suffixes: " + imageSuffixList);
180            logger.info("supported image providers:");
181 
182            Iterator rider = imageProviderMap.entrySet().iterator();
183 
184            while (rider.hasNext()) {
185                Map.Entry entry = (Map.Entry) rider.next();
186                ImageReaderSpi provider = (ImageReaderSpi) entry.getValue();
187 
188                logger.info("  " + entry.getKey() + ": " + provider.getVendorName() + ", v" + provider.getVersion());
189            }
190 
191            for (int i = 0; i < formats.length; i += 1) {
192                String format = formats[i].toLowerCase();
193 
194                rider = ImageIO.getImageReadersByFormatName(format);
195 
196                ImageReader reader = (ImageReader) rider.next();
197 
198                while (rider.hasNext()) {
199                    reader = (ImageReader) rider.next();
200 
201                    ImageReaderSpi provider = reader.getOriginatingProvider();
202 
203                    logger.info("  " + format + ": " + provider.getVendorName() + ", v" + provider.getVersion());
204                }
205            }
206        }
207 
208        assert imageSuffixList.size() > 0;
209        assert imageProviderMap.size() > 0;
210    }
211 
212    /**
213     *  Convert <code>image</code> to <code>BufferedImage</code>. This is slow.
214     */
215    public BufferedImage getAsBufferedImage(RenderedImage image) {
216        assert image != null;
217        PlanarImage planar = new RenderedImageAdapter(image);
218        BufferedImage result = planar.getAsBufferedImage();
219 
220        return result;
221    }
222 
223    /**
224     *  Convert <code>image</code> to <code>ImageIcon</code>. This is slow.
225     */
226    public ImageIcon getAsImageIcon(RenderedImage image) {
227        assert image != null;
228        BufferedImage buffered = getAsBufferedImage(image);
229        ImageIcon result = new ImageIcon(buffered);
230 
231        return result;
232    }
233 
234    /**
235     *  Get image in a form that can be processed quickly by most Java rendering operations.
236     */
237    public BufferedImage getAsQuickBufferedImage(RenderedImage image) {
238        BufferedImage result;
239        int targetType = BufferedImage.TYPE_INT_RGB;
240 
241        if ((image instanceof BufferedImage) && ((BufferedImage) image).getType() == targetType) {
242            result = (BufferedImage) image;
243        } else {
244            result = new BufferedImage(image.getWidth(), image.getHeight(), targetType);
245 
246            Graphics2D g2d = result.createGraphics();
247 
248            try {
249                AffineTransform identityTransformation = new AffineTransform();
250 
251                g2d.drawRenderedImage(image, identityTransformation);
252            } finally {
253                g2d.dispose();
254            }
255        }
256        return result;
257    }
258 
259    public RenderedImage getBluredImage(RenderedImage image, String blurMode) {
260        assert isValidBlurMode(blurMode) : "blurMode=" + blurMode;
261 
262        ParameterBlockJAI bpBlur = new ParameterBlockJAI(blurMode);
263        Settings settings = Settings.instance();
264        int blur = settings.getBlurRadius();
265        int thresh = settings.getBlurThreshold();
266 
267        bpBlur.addSource(image);
268        bpBlur.setParameter("kernel", createBlurKernel(blur));
269        bpBlur.setParameter("threshold", thresh);
270 
271        return JAI.create(blurMode, bpBlur,
272                new RenderingHints(JAI.KEY_BORDER_EXTENDER,
273                BorderExtender.createInstance(BorderExtender.BORDER_COPY)));
274    }
275 
276    /**
277     *  Get relative Rotation needed to go from absolute rotation <code>oldRotation</code> to <code>newRotation</code>
278     *  .
279     */
280    public int getDeltaRotation(int oldRotation, int newRotation) {
281        assertIsValidRotation(oldRotation);
282        assertIsValidRotation(newRotation);
283 
284        int result = getFixedRotation(-(oldRotation - newRotation));
285 
286        assertIsValidRotation(result);
287        return result;
288    }
289 
290    /**
291     *  Get the image dimension of <code>imageStream</code> or <code>null</code> if no reader can be
292     *  found.
293     */
294    public Dimension getImageDimension(ImageInputStream imageStream)
295        throws IOException {
296 
297        Dimension result;
298 
299        try {
300            ImageReader reader = getImageReader(imageStream);
301 
302            try {
303                reader.setInput(imageStream);
304 
305                int height = reader.getHeight(0);
306                int width = reader.getWidth(0);
307 
308                result = new Dimension(width, height);
309                if (logger.isDebugEnabled()) {
310                    logger.debug("image dimension: " + result.width + "x" + result.height);
311                }
312            } finally {
313                reader.dispose();
314            }
315        } catch (NoSuchElementException error) {
316            result = null;
317        }
318        return result;
319    }
320 
321    public Dimension getImageDimension(File imageFile)
322        throws IOException {
323        assert imageFile != null;
324 
325        Dimension result;
326        ImageInputStream imageStream = createImageInputStream(imageFile);
327 
328        try {
329            result = getImageDimension(imageStream);
330        } finally {
331            imageStream.close();
332        }
333        return result;
334    }
335 
336    /**
337     *  Get the format of <code>imageStream</code> or <code>null</code> if no image stream or reader
338     *  is available.
339     *
340     * @see    ImageIO#getImageReaders(java.lang.Object)
341     * @see    ImageIO#createImageInputStream(Object)
342     * @see    ImageReader#getFormatName()
343     */
344    public String getImageFormat(ImageInputStream imageStream)
345        throws IOException {
346        String result = null;
347 
348        if (imageStream != null) {
349            Iterator readerRider = ImageIO.getImageReaders(imageStream);
350 
351            while ((result == null) && readerRider.hasNext()) {
352                ImageReader reader = (ImageReader) readerRider.next();
353 
354                result = reader.getFormatName();
355                assert result != null;
356                result = result.toLowerCase();
357                if (logger.isDebugEnabled()) {
358                    logger.debug("image format=" + result);
359                }
360            }
361        }
362        return result;
363    }
364 
365    /**
366     *  Get the format of <code>imageFile</code> or <code>null</code> if no image reader is
367     *  available for it.
368     *
369     * @see    ImageIO#getImageReaders(java.lang.Object)
370     * @see    ImageReader#getFormatName()
371     */
372    public String getImageFormat(File imageFile)
373        throws IOException {
374        String result = null;
375        ImageInputStream imageStream = ImageIO.createImageInputStream(imageFile);
376 
377        if (imageStream != null) {
378            try {
379                result = getImageFormat(imageStream);
380            } finally {
381                imageStream.close();
382            }
383        }
384        return result;
385    }
386 
387    /**
388     *  Get a map with the keys being an image file suffix, and the value being the name of the
389     *  provider handling it.
390     */
391    public Map getImageProviderMap() {
392        return imageProviderMap;
393    }
394 
395    /**
396     *  Get an reader for in the image in <code>imageStream</code>.
397     */
398    public ImageReader getImageReader(ImageInputStream imageStream) {
399        return (ImageReader) ImageIO.getImageReaders(imageStream).next();
400    }
401 
402    public int getLeftRotation(int rotation) {
403        assertIsValidRotation(rotation);
404 
405        int result = getFixedRotation(rotation - 1);
406 
407        return result;
408    }
409 
410    /**
411     *  Like <code>PlanarImage.getNumBands()</code>, but also works on <code>RenderedImage</code>.
412     *
413     * @see    javax.media.jai.PlanarImage#getNumBands()
414     */
415    public int getNumBands(RenderedImage image) {
416        assert image != null;
417        return image.getColorModel().getColorSpace().getNumComponents();
418    }
419 
420    /**
421     * @see    #getBluredImage(RenderedImage, String)
422     */
423    public String[] getPossibleBlurModes() {
424        return possibleBlurModes;
425    }
426 
427    /**
428     *  RenderingHints used by all render operations within ImageTools.
429     */
430    public RenderingHints getRenderingHints() {
431        return renderHints;
432    }
433 
434    public int getRightRotation(int rotation) {
435        assertIsValidRotation(rotation);
436 
437        int result = getFixedRotation(rotation + 1);
438 
439        return result;
440    }
441 
442    public RenderedImage getRotatedImage(RenderedImage image, float radians) {
443        assert image != null;
444        RenderedImage result;
445 
446        // Rotate image
447        ParameterBlock pbRotate = new ParameterBlock();
448 
449        pbRotate.addSource(image);
450        pbRotate.add(0.0F);
451        pbRotate.add(0.0F);
452        pbRotate.add(radians);
453 
454        RenderedImage rotated = JAI.create("Rotate", pbRotate);
455 
456        // Move top left back into visible top
457        ParameterBlock pbTranslate = new ParameterBlock();
458 
459        pbTranslate.addSource(rotated);
460        pbTranslate.add((float) -rotated.getMinX());
461        pbTranslate.add((float) -rotated.getMinY());
462 
463        result = JAI.create("Translate", pbTranslate);
464        return result;
465    }
466 
467    /**
468     *  Rotate image in steps of 90 degrees.
469     *
470     * @param  rotation  -1=counter clockwise, 0=no rotation, 1=clockwise, 2=upside down
471     */
472 
473    public RenderedImage getRotatedImage(RenderedImage image, int rotation) {
474        assertIsValidRotation(rotation);
475 
476        RenderedImage result;
477 
478        if (rotation == 0) {
479            result = image;
480        } else {
481            TransposeType transposeRotation;
482 
483            if (rotation == ROTATE_COUNTERCLOCKWISE) {
484                transposeRotation = TransposeDescriptor.ROTATE_270;
485            } else if (rotation == ROTATE_CLOCKWISE) {
486                transposeRotation = TransposeDescriptor.ROTATE_90;
487            } else if (rotation == ROTATE_UPSIDE_DOWN) {
488                transposeRotation = TransposeDescriptor.ROTATE_180;
489            } else {
490                assert false : "rotation=" + rotation;
491                transposeRotation = null;
492            }
493 
494            ParameterBlockJAI pbTranspose = new ParameterBlockJAI("Transpose");
495 
496            pbTranspose.addSource(image);
497            pbTranspose.setParameter("type", transposeRotation);
498            result = JAI.create("Transpose", pbTranspose);
499        }
500        return result;
501    }
502 
503    /**
504     *  Compute scale to squeeze into an area of size <code>areaWidth</code>x<code>areaHeight</code>
505     *  an image of size <code>sourceWidth</code>x<code>sourceHeight</code>.
506     */
507    public double getScaleToSqueeze(
508            int areaWidth, int areaHeight, int sourceWidth, int sourceHeight, String scaleMode) {
509        double result;
510 
511        if (scaleMode.equals(SCALE_FIT)) {
512            double sourceRatio = ((double) sourceWidth) / sourceHeight;
513            double areaRatio = ((double) areaWidth) / areaHeight;
514 
515            if (logger.isDebugEnabled()) {
516                logger.debug("area: " + areaWidth + " / " + areaHeight + " = " + areaRatio);
517                logger.debug("source: " + sourceWidth + " / " + sourceHeight + " = " + sourceRatio);
518            }
519 
520            if (sourceRatio < areaRatio) {
521                result = ((double) areaHeight) / sourceHeight;
522            } else {
523                result = ((double) areaWidth) / sourceWidth;
524            }
525        } else if (scaleMode.equals(SCALE_HEIGHT)) {
526            result = ((double) areaHeight) / sourceHeight;
527        } else if (scaleMode.equals(SCALE_WIDTH)) {
528            result = ((double) areaWidth) / sourceWidth;
529        } else {
530            assert scaleMode.equals(SCALE_ACTUAL) : "scaleMode=" + scaleMode;
531            result = 1.0;
532        }
533 
534        if (logger.isDebugEnabled()) {
535            logger.debug("-> scale = " + result);
536        }
537        return result;
538    }
539 
540    /**
541     *  Get a rescaled version of <code>source</code> that depending on <code>scaleMode</code>
542     *  possibly fits <code>width</code> and/or <code>height</code> while keeping proportions.
543     *
544     * @param  scaleMode  one of: SCALE_ACTUAL, SCALE_FIT, SCALE_WIDTH, SCALE_HEIGHT
545     */
546    public RenderedImage getSqueezed(
547            final RenderedImage source,
548            final int areaWidth,
549            final int areaHeight,
550            final String scaleMode) {
551        assert source != null;
552        assert areaWidth > 0;
553        assert areaHeight > 0;
554        assertIsValidScaleMode(scaleMode);
555 
556        RenderedImage result;
557        int sourceWidth = source.getWidth();
558        int sourceHeight = source.getHeight();
559 
560        if (scaleMode.equals(SCALE_ACTUAL)) {
561            result = source;
562        } else {
563            double scale = getScaleToSqueeze(areaWidth, areaHeight, sourceWidth, sourceHeight, scaleMode);
564 
565            if (scale == 1.0) {
566                result = source;
567            } else {
568                // create the scaled image
569                int targetWidth = (int) Math.round(scale * sourceWidth);
570                int targetHeight = (int) Math.round(scale * sourceHeight);
571                Graphics2D g2d;
572 
573                result = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB);
574                g2d = ((BufferedImage) result).createGraphics();
575                try {
576                    g2d.setRenderingHints(renderHints);
577                    g2d.drawRenderedImage(source, AffineTransform.getScaleInstance(scale, scale));
578                } finally {
579                    g2d.dispose();
580                }
581            }
582        }
583 
584        return result;
585    }
586 
587    /**
588     *  Get the size of the actual area used by a sourceWidth x sourceHeight image squeezed in an
589     *  areaWidth x areaHeight area.
590     */
591    public Dimension getSqueezedDimension(
592            int areaWidth, int areaHeight, int sourceWidth, int sourceHeight, String scaleMode) {
593        double scale = getScaleToSqueeze(areaWidth, areaHeight, sourceWidth, sourceHeight, scaleMode);
594        int squeezedWidth = Math.min(areaWidth, (int) Math.round(scale * sourceWidth));
595        int squeezedHeight = Math.min(areaHeight, (int) Math.round(scale * sourceHeight));
596        Dimension result = new Dimension(squeezedWidth, squeezedHeight);
597 
598        return result;
599    }
600 
601    /**
602     *  Yield <code>true</code> if <code>imageFormat</code> indicates a compressed reformat. Images
603     *  in such formats will not shrink significantly by compressing them using for example ZIP.
604     *
605     * @see    #getImageFormat(ImageInputStream)
606     */
607    public boolean isCompressedImageFormat(String imageFormat) {
608        boolean result = Arrays.binarySearch(compressedImageFormats, imageFormat) >= 0;
609 
610        return result;
611    }
612 
613    public boolean isImageFile(String fileName) {
614        assert fileName != null;
615        return isImageSuffix(fileTools.getSuffix(fileName));
616    }
617 
618    public boolean isImageFile(File file) {
619        assert file != null;
620        return isImageSuffix(fileTools.getSuffix(file));
621    }
622 
623    public boolean isImageSuffix(String suffix) {
624        assert suffix != null;
625        return Collections.binarySearch(imageSuffixList, suffix.toLowerCase()) >= 0;
626    }
627 
628    public boolean isLandscape(int width, int height) {
629        return (width > height);
630    }
631 
632    public boolean isLandscape(Dimension size) {
633        return isLandscape(size.width, size.height);
634    }
635 
636    public boolean isLandscape(RenderedImage image) {
637        return isLandscape(image.getWidth(), image.getHeight());
638    }
639 
640    public boolean isValidBlurMode(String some) {
641        return stringTools.equalsAnyOf(possibleBlurModes, some);
642    }
643 
644    public boolean isValidRotation(int rotation) {
645        return (rotation == ROTATE_NONE)
646                || (rotation == ROTATE_CLOCKWISE)
647                || (rotation == ROTATE_UPSIDE_DOWN)
648                || (rotation == ROTATE_COUNTERCLOCKWISE);
649    }
650 
651    public boolean isValidScaleMode(String mode) {
652        return (mode != null) && (mode.equals(ImageTools.SCALE_ACTUAL)
653                || mode.equals(ImageTools.SCALE_FIT)
654                || mode.equals(ImageTools.SCALE_HEIGHT)
655                || mode.equals(ImageTools.SCALE_WIDTH));
656    }
657 
658    /**
659     *  Get adjusted rotation so that it fits in valid range.
660     */
661    int getFixedRotation(int possiblyInvalidRotation) {
662        int result = ((possiblyInvalidRotation + 1) % DISTINCT_ROTATIONS_COUNT) - 1;
663 
664        if (result < -1) {
665            result += DISTINCT_ROTATIONS_COUNT;
666        }
667        assertIsValidRotation(result);
668        return result;
669    }
670 
671 
672    /**
673     *  Get accessor to unique instance.
674     */
675    public static synchronized ImageTools instance() {
676        if (instance == null) {
677            instance = new ImageTools();
678        }
679        return instance;
680    }
681 
682    public void assertIsValidRotation(int rotation) {
683        assert (rotation >= -1) && (rotation <= 2) : "rotation = " + rotation;
684    }
685 
686    public void assertIsValidScaleMode(String scaleMode) {
687        assert isValidScaleMode(scaleMode) : "scaleMode = " + scaleMode;
688    }
689 
690    /**
691     *  Create image to represent a broken item.
692     */
693    public RenderedImage createBrokenImage(int width, int height, Color background, Color foreground) {
694        assert width > 0;
695        assert height > 0;
696        assert background != null;
697        assert foreground != null;
698 
699        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
700        Graphics2D g = result.createGraphics();
701 
702        try {
703            float strokeWidth = Math.max(1f, ((float) Math.min(width, height)) / STROKE_WIDTH_DIVISOR);
704            int insetX = width / BROKEN_IMAGE_STROKE_INSET_DIVISOR;
705            int insetY = height / BROKEN_IMAGE_STROKE_INSET_DIVISOR;
706 
707            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
708            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
709            g.setColor(background);
710            g.fillRect(0, 0, width, height);
711            g.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
712            g.setColor(foreground);
713            g.drawLine(insetX, insetY, width - insetX, height - insetY);
714            g.drawLine(width - insetX, insetY, insetX, height - insetY);
715        } finally {
716            g.dispose();
717        }
718        return result;
719    }
720 
721    /**
722     *  Create image to represent a busy image that is in process of being rendered.
723     */
724    public RenderedImage createBusyImage(int width, int height, Color background, Color foreground) {
725        assert width > 0;
726        assert height > 0;
727        assert background != null;
728        assert foreground != null;
729 
730        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
731        Graphics2D g = result.createGraphics();
732 
733        try {
734            float strokeWidth = Math.max(1f, ((float) Math.min(width, height)) / STROKE_WIDTH_DIVISOR);
735            float baseDiameter = Math.min(width, height);
736            int inset = Math.min(width / BUSY_IMAGE_STROKE_INSET_DIVISOR,
737                    height / BUSY_IMAGE_STROKE_INSET_DIVISOR);
738            float radius = baseDiameter - 2 * inset;
739            Ellipse2D.Double ellipse = new Ellipse2D.Double(
740                    (width - radius) / 2, (height - radius) / 2, radius, radius);
741 
742            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
743            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
744            g.setColor(background);
745            g.fillRect(0, 0, width, height);
746            g.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
747            g.setColor(foreground);
748            g.draw(ellipse);
749        } finally {
750            g.dispose();
751        }
752        return result;
753    }
754 
755    /**
756     *  Create a rectangle of a certain color.
757     */
758    public BufferedImage createColorBox(int width, int height, Color color) {
759        assert width > 0;
760        assert height > 0;
761        assert color != null;
762        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
763        Graphics2D g = result.createGraphics();
764 
765        try {
766            g.setColor(color);
767            g.fillRect(0, 0, width, height);
768            g.setColor(Color.BLACK);
769            g.drawRect(0, 0, width - 1, height - 1);
770        } finally {
771            g.dispose();
772        }
773        return result;
774    }
775 
776    /**
777     *  Same as ImageIO.createImageInputStream, but throws a <code>FileNotFoundException</code> if
778     *  <code>imageFile</code> cannot be found (instead of returning <code>null</code>). Why the
779     *  original does not work that way already is beyond me.<p>
780     *
781     *  Note that a non-image (such as a text file) still returns proper stream.
782     */
783    public ImageInputStream createImageInputStream(File imageFile)
784        throws IOException {
785        assert imageFile != null;
786        ImageInputStream result = ImageIO.createImageInputStream(imageFile);
787 
788        if (result == null) {
789            String message = localeTools.getMessage("errors.cannotFindImageFile", imageFile);
790 
791            throw new FileNotFoundException(message);
792        }
793        return result;
794    }
795 
796    /**
797     *  Compute fill values for <code>color</code> when it should be used by the "border" operator
798     *  on an image with a ColorModel of <code>colorModel</code>.
799     */
800    public double[] fillColorValues(Color color, ColorModel colorModel) {
801        assert color != null;
802        assert colorModel != null;
803 
804        double[] result;
805        ColorSpace colorSpace = colorModel.getColorSpace();
806        boolean isGray = colorSpace.getType() == ColorSpace.TYPE_GRAY;
807        float[] fillValuesFloat;
808 
809        if (isGray) {
810            // In case of grayscale images, use brightness instead of color
811            float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
812            float brightness = hsb[BRIGHTNESS_INDEX];
813 
814            assert colorSpace.getNumComponents() == 1;
815            float minValue = colorSpace.getMinValue(0);
816            float maxValue = colorSpace.getMaxValue(0);
817            float range = maxValue - minValue;
818 
819            fillValuesFloat = new float[]{brightness * range + minValue};
820        } else {
821            // Otherwise, just use the color components
822            fillValuesFloat = color.getColorComponents(colorSpace, null);
823        }
824 
825        // Convert float[] to double[]
826        result = new double[fillValuesFloat.length];
827        for (int i = 0; i < fillValuesFloat.length; i += 1) {
828            result[i] = ((1 << colorModel.getComponentSize(i)) - 1) * fillValuesFloat[i];
829        }
830        if (logger.isInfoEnabled()) {
831            String colorText = stringTools.colorString(color.getRGB());
832 
833            logger.info("fillValues for " + colorText + ": " + stringTools.arrayToString(result));
834        }
835        return result;
836    }
837 
838    /**
839     *  Read the specified <code>imageFile</code>.
840     */
841    public RenderedImage readImage(File imageFile)
842        throws IOException {
843        assert imageFile != null;
844        RenderedImage result;
845        ImageInputStream in = createImageInputStream(imageFile);
846 
847        try {
848            ImageReader reader = getImageReader(in);
849 
850            try {
851                reader.setInput(in);
852                try {
853                    ImageReadParam readParameters = reader.getDefaultReadParam();
854 
855                    readParameters.setDestinationType(
856                            ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
857                    result = reader.read(0);
858                } finally {
859                    reader.dispose();
860                }
861            } finally {
862                in.close();
863            }
864        } catch (Exception error) {
865            Throwable cause = error.getCause();
866 
867            if ((cause == null) || (!(cause instanceof IOException))) {
868                logger.warn("cannot read image using ImageIO; reverting to JAI: " + imageFile, error);
869                result = JAI.create("fileload", imageFile.getAbsolutePath());
870            } else {
871                // HACK: workaround the fact that JAI somehow manages to parse incomplete images
872                // and returns a black image.
873                result = null;
874            }
875            // HACK: Consider images with width or height 0 to be broken because
876            // JAI seems to return 0x0 images on empty files.
877            if ((result == null) || (result.getHeight() == 0) || (result.getWidth() == 0)) {
878                String message = localeTools.getMessage("errors.cannotReadImageFile", imageFile);
879 
880                throw new IIOException(message, error);
881            }
882        }
883        if (result != null) {
884            result = getAsQuickBufferedImage(result);
885        }
886        return result;
887    }
888 
889    private KernelJAI createBlurKernel(int radius) {
890        float[] kernel = new float[radius * radius];
891        float sum = 0;
892        float sigma = ((float) radius) / Settings.MIN_BLUR_RADIUS;
893 
894        for (int y = 0; y < radius; y += 1) {
895            for (int x = 0; x < radius; x += 1) {
896                int off = y * radius + x;
897                int xx = x - radius / 2;
898                int yy = y - radius / 2;
899 
900                kernel[off] = (float) Math.pow(Math.E, -(xx * xx + yy * yy) / (2 * sigma * sigma));
901                sum += kernel[off];
902            }
903        }
904        for (int i = 0; i < kernel.length; i += 1) {
905            kernel[i] /= sum;
906        }
907        return new KernelJAI(radius, radius, kernel);
908    }
909 
910    /**
911     *  If there is a provider for suffix <code>original</code> but none for suffix <code>copy</code>
912     *  , add the same provider for it.
913     */
914    private void possiblyAddImageFormatCopy(String original, String copy) {
915        assert original != null;
916        assert copy != null;
917        assert original.equals(original.toLowerCase());
918        assert copy.equals(copy.toLowerCase());
919        assert !original.equals(copy);
920 
921        Object originalProvider = imageProviderMap.get(original);
922 
923        if (originalProvider != null) {
924            Object copyProvider = imageProviderMap.get(copy);
925 
926            if (copyProvider == null) {
927                logger.warn("adding suffix " + stringTools.sourced(copy)
928                        + " to broken provider for suffix " + stringTools.sourced(original));
929                imageProviderMap.put(copy, originalProvider);
930                imageSuffixMap.put(copy, copy);
931            }
932        }
933    }
934}

[all classes][net.sf.jomic.tools]
EMMA 2.0.4217 (C) Vladimir Roubtsov