Package org.geotools.image.test

Source Code of org.geotools.image.test.ImageComparator$Pixel

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2014, Open Source Geospatial Foundation (OSGeo)
*
*    This library is free software; you can redistribute it and/or
*    modify it under the terms of the GNU Lesser General Public
*    License as published by the Free Software Foundation;
*    version 2.1 of the License.
*
*    This library is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    Lesser General Public License for more details.
*/
package org.geotools.image.test;

import java.awt.image.RenderedImage;

import javax.media.jai.PlanarImage;
import javax.media.jai.iterator.RandomIter;
import javax.media.jai.iterator.RandomIterFactory;

import org.geotools.image.ImageWorker;

/**
* Utility to compare two images and verify if the are equal to the human eye, or not. See the
* {@link Mode} enumeration for comparison modes. The image comparison logic has been ported to Java
* from Resemble.js, https://github.com/Huddle/Resemble.js
*
* @author Andrea Aime - GeoSolutions
*
*/
public class ImageComparator {

    public enum Mode {
        /**
         * Checks if the images are equal taking into account the full color and all pixels. Some
         * light difference between the two images is still being tolerated
         */
        IgnoreNothing,
        /**
         * Same as above, but if a pixel is found to be anti-aliased, only brightness will be
         * compared, instead of the full color component
         */
        IgnoreAntialiasing,
        /**
         * Ignores the colors and compares only the brightness
         */
        IgnoreColors
    };

    final class Pixel {
        int r;

        int g;

        int b;

        int a;

        private int brightness;

        private double hue;

        public Pixel() {
        }

        public void init(int[] px) {
            if (bands < 2) {
                r = g = b = px[0];
                if (bands == 2) {
                    a = px[1];
                } else {
                    a = 255;
                }
            } else {
                r = px[0];
                g = px[1];
                b = px[2];
                if (bands == 4) {
                    a = px[3];
                } else {
                    a = 255;
                }
            }
            brightness = Integer.MIN_VALUE;
            hue = Double.NaN;
        }

        int getBrightness() {
            if (brightness == Integer.MIN_VALUE) {
                brightness = (int) Math.round(0.3 * r + 0.59 * g + 0.11 * b);
            }
            return brightness;
        }

        double getHue() {
            if (hue == Double.NaN) {
                double r = this.r / 255d;
                double g = this.g / 255d;
                double b = this.b / 255d;
                double max = Math.max(r, Math.max(g, b));
                double min = Math.min(r, Math.max(g, b));

                if (max == min) {
                    hue = 0; // achromatic
                } else {
                    double d = max - min;
                    if (max == r) {
                        hue = (g - b) / d + (g < b ? 6 : 0);
                    } else if (max == g) {
                        hue = (b - r) / d + 2;
                    } else {
                        hue = (r - g) / d + 4;
                    }
                    hue /= 6;
                }
            }
            return hue;
        }

        public boolean isRGBSame(Pixel other) {
            if (a != other.a) {
                return false;
            }
            if (b != other.b)
                return false;
            if (g != other.g)
                return false;
            if (r != other.r)
                return false;
            return true;
        }

        public boolean isSimilar(Pixel other) {
            return isColorSimilar(r, other.r, RED) && //
                    isColorSimilar(g, other.g, GREEN) && //
                    isColorSimilar(b, other.b, BLUE) && //
                    isColorSimilar(a, other.a, ALPHA);
        }

        public boolean isConstrasting(Pixel other) {
            return Math.abs(getBrightness() - other.getBrightness()) > tolerance[MAX_BRIGHTNESS];
        }

        private boolean isColorSimilar(int a, int b, int color) {
            final int diff = Math.abs(a - b);
            return diff == 0 || diff < tolerance[color];
        }

        public boolean isBrightnessSimilar(Pixel other) {
            return isColorSimilar(a, other.a, ALPHA)
                    && isColorSimilar(getBrightness(), other.getBrightness(), MIN_BRIGHTNESS);
        }

        public boolean hasDifferentHue(Pixel cursor) {
            return Math.abs(getHue() - cursor.getHue()) > 0.3;
        }

        @Override
        public String toString() {
            return "Pixel [r=" + r + ", g=" + g + ", b=" + b + ", a=" + a + "]";
        }

    }

    static final int RED = 0;

    static final int GREEN = 1;

    static final int BLUE = 2;

    static final int ALPHA = 3;

    static final int MIN_BRIGHTNESS = 4;

    static final int MAX_BRIGHTNESS = 5;

    int[] tolerance = new int[MAX_BRIGHTNESS + 1];

    Mode mode;

    long mismatchCount = 0;

    double mismatchPercent;

    int bands;

    public ImageComparator(Mode mode, RenderedImage image1, RenderedImage image2) {

        int height = image1.getHeight();
        int width = image1.getWidth();
        if (width != image2.getWidth() || height != image2.getHeight()) {
            mismatchCount = Integer.MAX_VALUE;
            mismatchPercent = 1d;
            return;
        }

        // switch to rbg/rgba/gray/gray-alpha
        image1 = normalizeImage(image1);
        image2 = normalizeImage(image2);

        this.bands = image1.getSampleModel().getNumBands();
        final boolean hasAlpha = image1.getColorModel().hasAlpha();
        if (bands > 4 || (bands == 2 && !hasAlpha) || (bands == 3 && hasAlpha)) {
            throw new IllegalArgumentException(
                    "Images have the wrong type, this code only supports gray, gray/alpha, "
                            + "RGB, RGBA images, or images that can be transformed in those models");
        }

        this.mode = mode;
        switch (mode) {
        case IgnoreNothing:
            tolerance[RED] = 16;
            tolerance[GREEN] = 16;
            tolerance[BLUE] = 16;
            tolerance[ALPHA] = 16;
            tolerance[MIN_BRIGHTNESS] = 16;
            tolerance[MAX_BRIGHTNESS] = 240;
            break;
        case IgnoreAntialiasing:
            tolerance[RED] = 32;
            tolerance[GREEN] = 32;
            tolerance[BLUE] = 32;
            tolerance[ALPHA] = 128;
            tolerance[MIN_BRIGHTNESS] = 64;
            tolerance[MAX_BRIGHTNESS] = 98;
            break;
        case IgnoreColors:
            tolerance[ALPHA] = 16;
            tolerance[MIN_BRIGHTNESS] = 16;
            tolerance[MAX_BRIGHTNESS] = 240;
            break;
        }

        computeDifference(image1, image2);
        mismatchPercent = mismatchCount * 1d / (width * image2.getHeight());
    }

    /**
     * Forces the image to start in the origin and have a rgb/rbga/gray/gray+alpha structure
     *
     * @param image1
     * @return
     */
    private RenderedImage normalizeImage(RenderedImage image1) {
        image1 = new ImageWorker(image1).forceColorSpaceRGB().forceComponentColorModel()
                .getRenderedImage();
        if (image1.getMinX() != 0 || image1.getMinY() != 0) {
            image1 = PlanarImage.wrapRenderedImage(image1).getAsBufferedImage();
        }
        return image1;
    }

    public double getMismatchPercent() {
        return mismatchPercent;
    }

    public long getMismatchCount() {
        return mismatchCount;
    }

    void computeDifference(RenderedImage image1, RenderedImage image2) {
        int[] components = new int[bands];
        Pixel px1 = new Pixel();
        Pixel px2 = new Pixel();

        final int width = image1.getWidth();
        final int height = image1.getHeight();
        RandomIter it1 = RandomIterFactory.create(image1, null);
        RandomIter it2 = RandomIterFactory.create(image2, null);
        Pixel cursor = new Pixel();
        try {
            for (int r = 0; r < height; r++) {
                for (int c = 0; c < width; c++) {
                    it1.getPixel(c, r, components);
                    px1.init(components);
                    it2.getPixel(c, r, components);
                    px2.init(components);

                    if (mode == Mode.IgnoreColors) {
                        if (!px1.isBrightnessSimilar(px2)) {
                            mismatchCount++;
                        }
                    } else if (!px1.isSimilar(px2)) {
                        if (mode == Mode.IgnoreAntialiasing) {
                            if (isAntialised(px1, it1, r, c, width, height, components, cursor)
                                    || isAntialised(px2, it2, r, c, width, height, components,
                                            cursor)) {
                                if (!px1.isBrightnessSimilar(px2)) {
                                    mismatchCount++;
                                }
                            } else {
                                mismatchCount++;
                            }
                        } else {
                            mismatchCount++;
                        }
                    }
                }
            }
        } finally {
            it1.done();
            it2.done();
        }

    }

    private boolean isAntialised(Pixel source, RandomIter it, int row, int col, int width,
            int height, int[] pixel, Pixel cursor) {
        final int DISTANCE = 1;

        int highContrastSibling = 0;
        int siblingWithDifferentHue = 0;
        int equivalentSibling = 0;

        final int rowMin = Math.max(row - DISTANCE, 0);
        final int rowMax = Math.min(row + DISTANCE, width);
        final int colMin = Math.max(col - DISTANCE, 0);
        final int colMax = Math.min(col + DISTANCE, height);
        for (int c = colMin; c < colMax; c++) {
            for (int r = rowMin; r < rowMax; r++) {
                if (c == col && r == row) {
                    // ignore source pixel
                    continue;
                } else {
                    it.getPixel(c, r, pixel);
                    cursor.init(pixel);

                    if (source.isRGBSame(cursor)) {
                        equivalentSibling++;
                    } else if (source.isConstrasting(cursor)) {
                        highContrastSibling++;
                    }

                    if (source.hasDifferentHue(cursor)) {
                        siblingWithDifferentHue++;
                    }

                    if (siblingWithDifferentHue > 1 || highContrastSibling > 1) {
                        return true;
                    }
                }

            }
        }

        if (equivalentSibling < 2) {
            return true;
        }

        return false;

    }

}
TOP

Related Classes of org.geotools.image.test.ImageComparator$Pixel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.