001    /*
002     * Created on Oct 20, 2010
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 @2010-2011 the original author or authors.
014     */
015    package org.fest.assertions.internal;
016    
017    import static org.fest.assertions.data.Offset.offset;
018    import static org.fest.assertions.data.RgbColor.color;
019    import static org.fest.assertions.error.ShouldBeEqualColors.shouldBeEqualColors;
020    import static org.fest.assertions.error.ShouldBeEqualImages.shouldBeEqualImages;
021    import static org.fest.assertions.error.ShouldHaveSize.shouldHaveSize;
022    import static org.fest.assertions.error.ShouldNotBeEqualImages.shouldNotBeEqualImages;
023    import static org.fest.assertions.internal.ColorComparisonResult.*;
024    import static org.fest.assertions.internal.CommonValidations.checkOffsetIsNotNull;
025    import static org.fest.util.Objects.areEqual;
026    
027    import java.awt.Dimension;
028    import java.awt.image.BufferedImage;
029    
030    import org.fest.assertions.core.AssertionInfo;
031    import org.fest.assertions.data.*;
032    import org.fest.assertions.error.ErrorMessageFactory;
033    import org.fest.util.VisibleForTesting;
034    
035    /**
036     * Reusable assertions for <code>{@link BufferedImage}</code>s.
037     *
038     * @author Yvonne Wang
039     */
040    public class Images {
041    
042      private static final Images INSTANCE = new Images();
043      private static final Offset<Integer> ZERO = offset(0);
044    
045      /**
046       * Returns the singleton instance of this class.
047       * @return the singleton instance of this class.
048       */
049      public static Images instance() {
050        return INSTANCE;
051      }
052    
053      @VisibleForTesting Failures failures = Failures.instance();
054    
055      @VisibleForTesting Images() {}
056    
057      /**
058       * Asserts that two images are equal. Two images are equal if:
059       * <ol>
060       * <li>they have equal size</li>
061       * <li>the the RGB values of the color at each pixel are equal</li>
062       * </ol>
063       * @param info contains information about the assertion.
064       * @param actual the actual image.
065       * @param expected the expected image.
066       * @throws AssertionError if the actual image is not equal to the expected one.
067       */
068      public void assertEqual(AssertionInfo info, BufferedImage actual, BufferedImage expected) {
069        assertEqual(info, actual, expected, ZERO);
070      }
071    
072      /**
073       * Asserts that two images are equal. Two images are equal if:
074       * <ol>
075       * <li>they have the same size</li>
076       * <li>the difference between the RGB values of the color at each pixel is less than or equal to the given
077       * offset</li>
078       * </ol>
079       * @param info contains information about the assertion.
080       * @param actual the actual image.
081       * @param expected the expected image.
082       * @param offset helps decide if the color of two pixels are similar: two pixels that are identical to the human eye
083       * may still have slightly different color values. For example, by using an offset of 1 we can indicate that a blue
084       * value of 60 is similar to a blue value of 61.
085       * @throws NullPointerException if the given offset is {@code null}.
086       * @throws AssertionError if the actual image is not equal to the expected one.
087       */
088      public void assertEqual(AssertionInfo info, BufferedImage actual, BufferedImage expected, Offset<Integer> offset) {
089        checkOffsetIsNotNull(offset);
090        if (areEqual(actual, expected)) return;
091        if (actual == null || expected == null) throw imagesShouldBeEqual(info, offset);
092        // BufferedImage does not have an implementation of 'equals,' which means that "equality" is verified by identity.
093        // We need to verify that two images are equal ourselves.
094        if (!haveEqualSize(actual, expected)) throw imageShouldHaveSize(info, actual, sizeOf(actual), sizeOf(expected));
095        ColorComparisonResult haveEqualColor = haveEqualColor(actual, expected, offset);
096        if (haveEqualColor == ARE_EQUAL) return;
097        throw failures.failure(info, imagesShouldHaveEqualColor(haveEqualColor, offset));
098      }
099    
100      private AssertionError imagesShouldBeEqual(AssertionInfo info, Offset<Integer> offset) {
101        return failures.failure(info, shouldBeEqualImages(offset));
102      }
103    
104      private ErrorMessageFactory imagesShouldHaveEqualColor(ColorComparisonResult r, Offset<Integer> offset) {
105        return shouldBeEqualColors(r.color2, r.color1, r.point, offset);
106      }
107    
108      /**
109       * Asserts that two images are not equal.
110       * @param info contains information about the assertion.
111       * @param actual the given image.
112       * @param other the object to compare {@code actual} to.
113       * @throws AssertionError if {@code actual} is equal to {@code other}.
114       */
115      public void assertNotEqual(AssertionInfo info, BufferedImage actual, BufferedImage other) {
116        if (areEqual(actual, other)) throw imagesShouldNotBeEqual(info);
117        if (actual == null || other == null) return;
118        if (!(haveEqualSize(actual, other))) return;
119        ColorComparisonResult haveEqualColor = haveEqualColor(actual, other, ZERO);
120        if (haveEqualColor != ARE_EQUAL) return;
121        throw imagesShouldNotBeEqual(info);
122      }
123    
124      private AssertionError imagesShouldNotBeEqual(AssertionInfo info) {
125        return failures.failure(info, shouldNotBeEqualImages());
126      }
127    
128      private boolean haveEqualSize(BufferedImage i1, BufferedImage i2) {
129        return i1.getWidth() == i2.getWidth() && i1.getHeight() == i2.getHeight();
130      }
131    
132      private ColorComparisonResult haveEqualColor(BufferedImage i1, BufferedImage i2, Offset<Integer> offset) {
133        int w = i1.getWidth();
134        int h = i1.getHeight();
135        for (int x = 0; x < w; x++) {
136          for (int y = 0; y < h; y++) {
137            RgbColor c1 = color(i1.getRGB(x, y));
138            RgbColor c2 = color(i2.getRGB(x, y));
139            if (c1.isEqualTo(c2, offset)) continue;
140            return notEqual(c1, c2, x, y);
141          }
142        }
143        return ARE_EQUAL;
144      }
145    
146      /**
147       * Asserts that the size of the given image is equal to the given size.
148       * @param info contains information about the assertion.
149       * @param actual the given image.
150       * @param size the expected size of {@code actual}.
151       * @throws NullPointerException if the given size is {@code null}.
152       * @throws AssertionError if the size of the given image is not equal to the given size.
153       */
154      public void assertHasSize(AssertionInfo info, BufferedImage actual, Dimension size) {
155        if (size == null) throw new NullPointerException("The given size should not be null");
156        Objects.instance().assertNotNull(info, actual);
157        Dimension sizeOfActual = sizeOf(actual);
158        if (areEqual(sizeOfActual, size)) return;
159        throw imageShouldHaveSize(info, actual, sizeOfActual, size);
160      }
161    
162      private AssertionError imageShouldHaveSize(AssertionInfo info, BufferedImage image, Dimension actual, Dimension expected) {
163        return failures.failure(info, shouldHaveSize(image, actual, expected));
164      }
165    
166      @VisibleForTesting static Dimension sizeOf(BufferedImage image) {
167        return new Dimension(image.getWidth(), image.getHeight());
168      }
169    }