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