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-2009 the original author or authors.
014     */
015    package org.fest.assertions;
016    
017    import static java.lang.String.valueOf;
018    import static org.fest.assertions.ErrorMessages.unexpectedEqual;
019    import static org.fest.assertions.ErrorMessages.unexpectedNotEqual;
020    import static org.fest.assertions.Formatting.inBrackets;
021    import static org.fest.assertions.Threshold.threshold;
022    import static org.fest.util.Objects.areEqual;
023    import static org.fest.util.Strings.concat;
024    import static org.fest.util.Strings.quote;
025    
026    import java.awt.Dimension;
027    import java.awt.image.BufferedImage;
028    import java.io.File;
029    import java.io.IOException;
030    
031    /**
032     * Understands assertion methods for images. To create a new instance of this class use the method
033     * <code>{@link Assertions#assertThat(BufferedImage)}</code>.
034     *
035     * @author Yvonne Wang
036     * @author Alex Ruiz
037     * @author Ansgar Konermann
038     */
039    public class ImageAssert extends GenericAssert<BufferedImage> {
040    
041      private static final Threshold ZERO_THRESHOLD = threshold(0);
042    
043      private static ImageReader imageReader = new ImageReader();
044    
045      /**
046       * Reads the image in the specified path.
047       * @param imageFilePath the path of the image to read.
048       * @return the read image.
049       * @throws NullPointerException if the given path is <code>null</code>.
050       * @throws IllegalArgumentException if the given path does not belong to a file.
051       * @throws IOException if any I/O error occurred while reading the image.
052       */
053      public static BufferedImage read(String imageFilePath) throws IOException {
054        if (imageFilePath == null) throw new NullPointerException("The path of the image to read should not be null");
055        File imageFile = new File(imageFilePath);
056        if (!imageFile.isFile())
057          throw new IllegalArgumentException(concat("The path ", quote(imageFilePath), " does not belong to a file"));
058        return imageReader.read(imageFile);
059      }
060    
061      /**
062       * Creates a new </code>{@link ImageAssert}</code>.
063       * @param actual the target to verify.
064       */
065      protected ImageAssert(BufferedImage actual) {
066        super(actual);
067      }
068    
069      /** {@inheritDoc} */
070      public ImageAssert as(String description) {
071        description(description);
072        return this;
073      }
074    
075      /** {@inheritDoc} */
076      public ImageAssert describedAs(String description) {
077        return as(description);
078      }
079    
080      /** {@inheritDoc} */
081      public ImageAssert as(Description description) {
082        description(description);
083        return this;
084      }
085    
086      /** {@inheritDoc} */
087      public ImageAssert describedAs(Description description) {
088        return as(description);
089      }
090    
091      /**
092       * Verifies that the actual image satisfies the given condition.
093       * @param condition the given condition.
094       * @return this assertion object.
095       * @throws NullPointerException if the given condition is <code>null</code>.
096       * @throws AssertionError if the actual image does not satisfy the given condition.
097       * @see #is(Condition)
098       */
099      public ImageAssert satisfies(Condition<BufferedImage> condition) {
100        assertSatisfies(condition);
101        return this;
102      }
103    
104      /**
105       * Verifies that the actual image does not satisfy the given condition.
106       * @param condition the given condition.
107       * @return this assertion object.
108       * @throws NullPointerException if the given condition is <code>null</code>.
109       * @throws AssertionError if the actual image satisfies the given condition.
110       * @see #isNot(Condition)
111       */
112      public ImageAssert doesNotSatisfy(Condition<BufferedImage> condition) {
113        assertDoesNotSatisfy(condition);
114        return this;
115      }
116    
117      /**
118       * Alias for <code>{@link #satisfies(Condition)}</code>.
119       * @param condition the given condition.
120       * @return this assertion object.
121       * @throws NullPointerException if the given condition is <code>null</code>.
122       * @throws AssertionError if the actual image does not satisfy the given condition.
123       * @since 1.2
124       */
125      public ImageAssert is(Condition<BufferedImage> condition) {
126        assertIs(condition);
127        return this;
128      }
129    
130      /**
131       * Alias for <code>{@link #doesNotSatisfy(Condition)}</code>.
132       * @param condition the given condition.
133       * @return this assertion object.
134       * @throws NullPointerException if the given condition is <code>null</code>.
135       * @throws AssertionError if the actual image satisfies the given condition.
136       * @since 1.2
137       */
138      public ImageAssert isNot(Condition<BufferedImage> condition) {
139        assertIsNot(condition);
140        return this;
141      }
142    
143      /**
144       * Verifies that the actual image is equal to the given one. Two images are equal if they have the same size and the
145       * pixels at the same coordinates have the same color.
146       * @param expected the given image to compare the actual image to.
147       * @return this assertion object.
148       * @throws AssertionError if the actual image is not equal to the given one.
149       */
150      public ImageAssert isEqualTo(BufferedImage expected) {
151        return isEqualTo(expected, ZERO_THRESHOLD);
152      }
153    
154      /**
155       * Verifies that the actual image is equal to the given one. Two images are equal if:
156       * <ol>
157       * <li>they have the same size</li>
158       * <li>the difference between the RGB values of the color of each pixel is less than or equal to the given
159       * threshold</li>
160       * </ol>
161       * @param expected the given image to compare the actual image to.
162       * @param threshold the threshold to use to decide if the color of two pixels are similar: two pixels that are
163       * identical to the human eye may still have slightly different color values. For example, by using a threshold of 1
164       * we can indicate that a blue value of 60 is similar to a blue value of 61.
165       * @return this assertion object.
166       * @throws AssertionError if the actual image is not equal to the given one.
167       * @since 1.1
168       */
169      public ImageAssert isEqualTo(BufferedImage expected, Threshold threshold) {
170        if (areEqual(actual, expected)) return this;
171        failIfNull(expected);
172        failIfNotEqual(sizeOf(actual), sizeOf(expected));
173        failIfNotEqualColor(expected, threshold);
174        return this;
175      }
176    
177      private void failIfNull(BufferedImage expected) {
178        if (expected != null) return;
179        failIfCustomMessageIsSet();
180        fail(unexpectedNotEqual(actual, null));
181      }
182    
183      private void failIfNotEqual(Dimension a, Dimension e) {
184        if (areEqual(a, e)) return;
185        failIfCustomMessageIsSet();
186        fail(concat("image size, expected:", inBrackets(e), " but was:", inBrackets(a)));
187      }
188    
189      private void failIfNotEqualColor(BufferedImage expected, Threshold threshold) {
190        int w = actual.getWidth();
191        int h = actual.getHeight();
192        for (int x = 0; x < w; x++)
193          for (int y = 0; y < h; y++)
194            failIfNotEqual(new RGBColor(actual.getRGB(x, y)), new RGBColor(expected.getRGB(x, y)), threshold, x, y);
195      }
196    
197      private void failIfNotEqual(RGBColor a, RGBColor e, Threshold threshold, int x, int y) {
198        if (a.isEqualTo(e, threshold.value())) return;
199        failIfCustomMessageIsSet();
200        fail(concat("expected:", inBrackets(a), " but was:", inBrackets(e), " at pixel [", valueOf(x), ",", valueOf(y), "]"));
201      }
202    
203      /**
204       * Verifies that the actual image is not equal to the given one. Two images are equal if they have the same size and
205       * the pixels at the same coordinates have the same color.
206       * @param image the given image to compare the actual image to.
207       * @return this assertion object.
208       * @throws AssertionError if the actual image is equal to the given one.
209       */
210      public ImageAssert isNotEqualTo(BufferedImage image) {
211        if (areEqual(actual, image)) {
212          failIfCustomMessageIsSet();
213          throw failure(unexpectedEqual(actual, image));
214        }
215        if (image == null) return this;
216        if (areEqual(sizeOf(actual), sizeOf(image)) && hasEqualColor(image)) {
217          failIfCustomMessageIsSet();
218          throw failure("images are equal");
219        }
220        return this;
221      }
222    
223      private static Dimension sizeOf(BufferedImage image) {
224        return new Dimension(image.getWidth(), image.getHeight());
225      }
226    
227      private boolean hasEqualColor(BufferedImage expected) {
228        int w = actual.getWidth();
229        int h = actual.getHeight();
230        for (int x = 0; x < w; x++)
231          for (int y = 0; y < h; y++)
232            if (actual.getRGB(x, y) != expected.getRGB(x, y)) return false;
233        return true;
234      }
235    
236      /**
237       * Verifies that the actual image is not <code>null</code>.
238       * @return this assertion object.
239       * @throws AssertionError if the actual image is <code>null</code>.
240       */
241      public ImageAssert isNotNull() {
242        assertNotNull();
243        return this;
244      }
245    
246      /**
247       * Verifies that the actual image is not the same as the given one.
248       * @param expected the given image to compare the actual image to.
249       * @return this assertion object.
250       * @throws AssertionError if the actual image is the same as the given one.
251       */
252      public ImageAssert isNotSameAs(BufferedImage expected) {
253        assertNotSameAs(expected);
254        return this;
255      }
256    
257      /**
258       * Verifies that the actual image is the same as the given one.
259       * @param expected the given image to compare the actual image to.
260       * @return this assertion object.
261       * @throws AssertionError if the actual image is not the same as the given one.
262       */
263      public ImageAssert isSameAs(BufferedImage expected) {
264        assertSameAs(expected);
265        return this;
266      }
267    
268      /**
269       * Verifies that the size of the actual image is equal to the given one.
270       * @param expected the expected size of the actual image.
271       * @return this assertion object.
272       * @throws AssertionError if the actual image is <code>null</code>.
273       * @throws NullPointerException if the given size is <code>null</code>.
274       * @throws AssertionError if the size of the actual image is not equal to the given one.
275       */
276      public ImageAssert hasSize(Dimension expected) {
277        isNotNull();
278        if (expected == null)
279          throw new NullPointerException(formattedErrorMessage("The size to compare to should not be null"));
280        Dimension actualDimension = new Dimension(actual.getWidth(), actual.getHeight());
281        Fail.failIfNotEqual(customErrorMessage(), rawDescription(), actualDimension, expected);
282        return this;
283      }
284    
285      /** {@inheritDoc} */
286      public ImageAssert overridingErrorMessage(String message) {
287        replaceDefaultErrorMessagesWith(message);
288        return this;
289      }
290    
291      static void imageReader(ImageReader newImageReader) {
292        imageReader = newImageReader;
293      }
294    }