• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.robolectric.shadows.testing.bitmapcomparers;
17 
18 import android.graphics.Color;
19 import android.util.Log;
20 
21 /**
22  * Image comparison using Structural Similarity Index, developed by Wang, Bovik, Sheikh, and
23  * Simoncelli. Details can be read in their paper :
24  *
25  * <p>https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf
26  */
27 public class MSSIMComparer extends BitmapComparer {
28   // These values were taken from the publication
29   public static final String TAG_NAME = "MSSIM";
30   public static final double CONSTANT_L = 254;
31   public static final double CONSTANT_K1 = 0.00001;
32   public static final double CONSTANT_K2 = 0.00003;
33   public static final double CONSTANT_C1 = Math.pow(CONSTANT_L * CONSTANT_K1, 2);
34   public static final double CONSTANT_C2 = Math.pow(CONSTANT_L * CONSTANT_K2, 2);
35   public static final int WINDOW_SIZE = 10;
36 
37   private final double threshold;
38 
MSSIMComparer(double threshold)39   public MSSIMComparer(double threshold) {
40     this.threshold = threshold;
41   }
42 
43   /**
44    * Compute the size of the window. The window defaults to WINDOW_SIZE, but must be contained
45    * within dimension.
46    */
computeWindowSize(int coordinateStart, int dimension)47   private int computeWindowSize(int coordinateStart, int dimension) {
48     if (coordinateStart + WINDOW_SIZE <= dimension) {
49       return WINDOW_SIZE;
50     }
51     return dimension - coordinateStart;
52   }
53 
54   @Override
verifySame( int[] ideal, int[] given, int offset, int stride, int width, int height)55   public boolean verifySame(
56       int[] ideal, int[] given, int offset, int stride, int width, int height) {
57     double ssimTotal = 0;
58     int windows = 0;
59 
60     for (int currentWindowY = 0; currentWindowY < height; currentWindowY += WINDOW_SIZE) {
61       int windowHeight = computeWindowSize(currentWindowY, height);
62       for (int currentWindowX = 0; currentWindowX < width; currentWindowX += WINDOW_SIZE) {
63         int windowWidth = computeWindowSize(currentWindowX, width);
64         int start = indexFromXAndY(currentWindowX, currentWindowY, stride, offset);
65         if (isWindowWhite(ideal, start, stride, windowWidth, windowHeight)
66             && isWindowWhite(given, start, stride, windowWidth, windowHeight)) {
67           continue;
68         }
69         windows++;
70         double[] means = getMeans(ideal, given, start, stride, windowWidth, windowHeight);
71         double meanX = means[0];
72         double meanY = means[1];
73         double[] variances =
74             getVariances(ideal, given, meanX, meanY, start, stride, windowWidth, windowHeight);
75         double varX = variances[0];
76         double varY = variances[1];
77         double stdBoth = variances[2];
78         double ssim = ssim(meanX, meanY, varX, varY, stdBoth);
79         ssimTotal += ssim;
80       }
81     }
82 
83     if (windows == 0) {
84       return true;
85     }
86 
87     ssimTotal /= windows;
88 
89     Log.d(TAG_NAME, "MSSIM = " + ssimTotal);
90 
91     return (ssimTotal >= threshold);
92   }
93 
isWindowWhite( int[] colors, int start, int stride, int windowWidth, int windowHeight)94   private boolean isWindowWhite(
95       int[] colors, int start, int stride, int windowWidth, int windowHeight) {
96     for (int y = 0; y < windowHeight; y++) {
97       for (int x = 0; x < windowWidth; x++) {
98         if (colors[indexFromXAndY(x, y, stride, start)] != Color.WHITE) {
99           return false;
100         }
101       }
102     }
103     return true;
104   }
105 
ssim(double muX, double muY, double sigX, double sigY, double sigXY)106   private double ssim(double muX, double muY, double sigX, double sigY, double sigXY) {
107     double ssimDouble = (((2 * muX * muY) + CONSTANT_C1) * ((2 * sigXY) + CONSTANT_C2));
108     double denom = ((muX * muX) + (muY * muY) + CONSTANT_C1) * (sigX + sigY + CONSTANT_C2);
109     ssimDouble /= denom;
110     return ssimDouble;
111   }
112 
113   /**
114    * This method will find the mean of a window in both sets of pixels. The return is an array where
115    * the first double is the mean of the first set and the second double is the mean of the second
116    * set.
117    */
getMeans( int[] pixels0, int[] pixels1, int start, int stride, int windowWidth, int windowHeight)118   private double[] getMeans(
119       int[] pixels0, int[] pixels1, int start, int stride, int windowWidth, int windowHeight) {
120     double avg0 = 0;
121     double avg1 = 0;
122     for (int y = 0; y < windowHeight; y++) {
123       for (int x = 0; x < windowWidth; x++) {
124         int index = indexFromXAndY(x, y, stride, start);
125         avg0 += getIntensity(pixels0[index]);
126         avg1 += getIntensity(pixels1[index]);
127       }
128     }
129     avg0 /= windowWidth * windowHeight;
130     avg1 /= windowWidth * windowHeight;
131     return new double[] {avg0, avg1};
132   }
133 
134   /**
135    * Finds the variance of the two sets of pixels, as well as the covariance of the windows. The
136    * return value is an array of doubles, the first is the variance of the first set of pixels, the
137    * second is the variance of the second set of pixels, and the third is the covariance.
138    */
getVariances( int[] pixels0, int[] pixels1, double mean0, double mean1, int start, int stride, int windowWidth, int windowHeight)139   private double[] getVariances(
140       int[] pixels0,
141       int[] pixels1,
142       double mean0,
143       double mean1,
144       int start,
145       int stride,
146       int windowWidth,
147       int windowHeight) {
148     double var0 = 0;
149     double var1 = 0;
150     double varBoth = 0;
151     for (int y = 0; y < windowHeight; y++) {
152       for (int x = 0; x < windowWidth; x++) {
153         int index = indexFromXAndY(x, y, stride, start);
154         double v0 = getIntensity(pixels0[index]) - mean0;
155         double v1 = getIntensity(pixels1[index]) - mean1;
156         var0 += v0 * v0;
157         var1 += v1 * v1;
158         varBoth += v0 * v1;
159       }
160     }
161     var0 /= (windowWidth * windowHeight) - 1;
162     var1 /= (windowWidth * windowHeight) - 1;
163     varBoth /= (windowWidth * windowHeight) - 1;
164     return new double[] {var0, var1, varBoth};
165   }
166 
167   /**
168    * Gets the intensity of a given pixel in RGB using luminosity formula
169    *
170    * <p>l = 0.21R' + 0.72G' + 0.07B'
171    *
172    * <p>The prime symbols dictate a gamma correction of 1.
173    */
getIntensity(int pixel)174   private double getIntensity(int pixel) {
175     final double gamma = 1;
176     double l = 0;
177     l += (0.21f * Math.pow(Color.red(pixel) / 255f, gamma));
178     l += (0.72f * Math.pow(Color.green(pixel) / 255f, gamma));
179     l += (0.07f * Math.pow(Color.blue(pixel) / 255f, gamma));
180     return l;
181   }
182 }
183