001    /*
002     * Created on Jun 7, 2007
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2007-2011 the original author or authors.
014     */
015    package org.fest.assertions;
016    
017    import static org.fest.assertions.ErrorMessages.*;
018    import static org.fest.assertions.Formatting.format;
019    import static org.fest.assertions.Threshold.threshold;
020    import static org.fest.util.Objects.areEqual;
021    
022    import java.awt.Dimension;
023    import java.awt.image.BufferedImage;
024    import java.io.File;
025    import java.io.IOException;
026    
027    /**
028     * Assertions for <code>{@link BufferedImage}</code>s.
029     * <p>
030     * To create a new instance of this class invoke <code>{@link Assertions#assertThat(BufferedImage)}</code>.
031     * </p>
032     *
033     * @author Yvonne Wang
034     * @author Alex Ruiz
035     * @author Ansgar Konermann
036     */
037    public class ImageAssert extends GenericAssert<ImageAssert, BufferedImage> {
038    
039      private static final Threshold ZERO_THRESHOLD = threshold(0);
040    
041      private static ImageReader imageReader = new ImageReader();
042    
043      /**
044       * Reads the image in the specified path.
045       * @param imageFilePath the path of the image to read.
046       * @return the read image.
047       * @throws NullPointerException if the given path is {@code null}.
048       * @throws IllegalArgumentException if the given path does not belong to a file.
049       * @throws IOException if any I/O error occurred while reading the image.
050       */
051      public static BufferedImage read(String imageFilePath) throws IOException {
052        if (imageFilePath == null) throw new NullPointerException("The path of the image to read should not be null");
053        File imageFile = new File(imageFilePath);
054        if (!imageFile.isFile())
055          throw new IllegalArgumentException(format("The path <%s> does not belong to a file", imageFilePath));
056        return imageReader.read(imageFile);
057      }
058    
059      /**
060       * Creates a new </code>{@link ImageAssert}</code>.
061       * @param actual the target to verify.
062       */
063      protected ImageAssert(BufferedImage actual) {
064        super(ImageAssert.class, actual);
065      }
066    
067      /**
068       * Verifies that the actual image is equal to the given one. Two images are equal if they have the same size and the
069       * pixels at the same coordinates have the same color.
070       * @param expected the given image to compare the actual image to.
071       * @return this assertion object.
072       * @throws AssertionError if the actual image is not equal to the given one.
073       */
074      @Override public ImageAssert isEqualTo(BufferedImage expected) {
075        return isEqualTo(expected, ZERO_THRESHOLD);
076      }
077    
078      /**
079       * Verifies that the actual image is equal to the given one. Two images are equal if:
080       * <ol>
081       * <li>they have the same size</li>
082       * <li>the difference between the RGB values of the color of each pixel is less than or equal to the given
083       * threshold</li>
084       * </ol>
085       * @param expected the given image to compare the actual image to.
086       * @param threshold the threshold to use to decide if the color of two pixels are similar: two pixels that are
087       * identical to the human eye may still have slightly different color values. For example, by using a threshold of 1
088       * we can indicate that a blue value of 60 is similar to a blue value of 61.
089       * @return this assertion object.
090       * @throws AssertionError if the actual image is not equal to the given one.
091       * @since 1.1
092       */
093      public ImageAssert isEqualTo(BufferedImage expected, Threshold threshold) {
094        if (areEqual(actual, expected)) return this;
095        failIfNull(expected);
096        failIfNotEqual(sizeOf(actual), sizeOf(expected));
097        failIfNotEqualColor(expected, threshold);
098        return this;
099      }
100    
101      private void failIfNull(BufferedImage expected) {
102        if (expected != null) return;
103        failIfCustomMessageIsSet();
104        fail(unexpectedNotEqual(actual, null));
105      }
106    
107      private void failIfNotEqual(Dimension a, Dimension e) {
108        if (areEqual(a, e)) return;
109        failIfCustomMessageIsSet();
110        fail(format("image size: expected:<%s> but was:<%s>", e, a));
111      }
112    
113      private void failIfNotEqualColor(BufferedImage expected, Threshold threshold) {
114        int w = actual.getWidth();
115        int h = actual.getHeight();
116        for (int x = 0; x < w; x++)
117          for (int y = 0; y < h; y++)
118            failIfNotEqual(new RGBColor(actual.getRGB(x, y)), new RGBColor(expected.getRGB(x, y)), threshold, x, y);
119      }
120    
121      private void failIfNotEqual(RGBColor a, RGBColor e, Threshold threshold, int x, int y) {
122        if (a.isEqualTo(e, threshold.value())) return;
123        failIfCustomMessageIsSet();
124        fail(String.format("expected:<%s> but was:<%s> at pixel [%d,%d]", a, e, x, y));
125      }
126    
127      /**
128       * Verifies that the actual image is not equal to the given one. Two images are equal if they have the same size and
129       * the pixels at the same coordinates have the same color.
130       * @param image the given image to compare the actual image to.
131       * @return this assertion object.
132       * @throws AssertionError if the actual image is equal to the given one.
133       */
134      @Override public ImageAssert isNotEqualTo(BufferedImage image) {
135        if (areEqual(actual, image)) {
136          failIfCustomMessageIsSet();
137          throw failure(unexpectedEqual(actual, image));
138        }
139        if (image == null) return this;
140        if (areEqual(sizeOf(actual), sizeOf(image)) && hasEqualColor(image)) {
141          failIfCustomMessageIsSet();
142          throw failure("images are equal");
143        }
144        return this;
145      }
146    
147      private static Dimension sizeOf(BufferedImage image) {
148        return new Dimension(image.getWidth(), image.getHeight());
149      }
150    
151      private boolean hasEqualColor(BufferedImage expected) {
152        int w = actual.getWidth();
153        int h = actual.getHeight();
154        for (int x = 0; x < w; x++)
155          for (int y = 0; y < h; y++)
156            if (actual.getRGB(x, y) != expected.getRGB(x, y)) return false;
157        return true;
158      }
159    
160      /**
161       * Verifies that the size of the actual image is equal to the given one.
162       * @param expected the expected size of the actual image.
163       * @return this assertion object.
164       * @throws AssertionError if the actual image is {@code null}.
165       * @throws NullPointerException if the given size is {@code null}.
166       * @throws AssertionError if the size of the actual image is not equal to the given one.
167       */
168      public ImageAssert hasSize(Dimension expected) {
169        isNotNull();
170        if (expected == null)
171          throw new NullPointerException(formattedErrorMessage("The size to compare to should not be null"));
172        Dimension actualDimension = new Dimension(actual.getWidth(), actual.getHeight());
173        Fail.failIfNotEqual(customErrorMessage(), rawDescription(), actualDimension, expected);
174        return this;
175      }
176    
177      static void imageReader(ImageReader newImageReader) {
178        imageReader = newImageReader;
179      }
180    }