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 android.uirendering.cts.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 * 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 double mThreshold; 38 MSSIMComparer(double threshold)39 public MSSIMComparer(double threshold) { 40 mThreshold = threshold; 41 } 42 43 @Override verifySame(int[] ideal, int[] given, int offset, int stride, int width, int height)44 public boolean verifySame(int[] ideal, int[] given, int offset, int stride, int width, 45 int height) { 46 double SSIMTotal = 0; 47 int windows = 0; 48 49 for (int currentWindowY = 0 ; currentWindowY < height ; currentWindowY += WINDOW_SIZE) { 50 for (int currentWindowX = 0 ; currentWindowX < width ; currentWindowX += WINDOW_SIZE) { 51 int start = indexFromXAndY(currentWindowX, currentWindowY, stride, offset); 52 if (isWindowWhite(ideal, start, stride) && isWindowWhite(given, start, stride)) { 53 continue; 54 } 55 windows++; 56 double[] means = getMeans(ideal, given, start, stride); 57 double meanX = means[0]; 58 double meanY = means[1]; 59 double[] variances = getVariances(ideal, given, meanX, meanY, start, stride); 60 double varX = variances[0]; 61 double varY = variances[1]; 62 double stdBoth = variances[2]; 63 double SSIM = SSIM(meanX, meanY, varX, varY, stdBoth); 64 SSIMTotal += SSIM; 65 } 66 } 67 68 if (windows == 0) { 69 return true; 70 } 71 72 SSIMTotal /= windows; 73 74 Log.d(TAG_NAME, "MSSIM = " + SSIMTotal); 75 76 return (SSIMTotal >= mThreshold); 77 } 78 isWindowWhite(int[] colors, int start, int stride)79 private boolean isWindowWhite(int[] colors, int start, int stride) { 80 for (int y = 0 ; y < WINDOW_SIZE ; y++) { 81 for (int x = 0 ; x < WINDOW_SIZE ; x++) { 82 if (colors[indexFromXAndY(x, y, stride, start)] != Color.WHITE) { 83 return false; 84 } 85 } 86 } 87 return true; 88 } 89 SSIM(double muX, double muY, double sigX, double sigY, double sigXY)90 private double SSIM(double muX, double muY, double sigX, double sigY, double sigXY) { 91 double SSIM = (((2 * muX * muY) + CONSTANT_C1) * ((2 * sigXY) + CONSTANT_C2)); 92 double denom = ((muX * muX) + (muY * muY) + CONSTANT_C1) 93 * (sigX + sigY + CONSTANT_C2); 94 SSIM /= denom; 95 return SSIM; 96 } 97 98 99 /** 100 * This method will find the mean of a window in both sets of pixels. The return is an array 101 * where the first double is the mean of the first set and the second double is the mean of the 102 * second set. 103 */ getMeans(int[] pixels0, int[] pixels1, int start, int stride)104 private double[] getMeans(int[] pixels0, int[] pixels1, int start, int stride) { 105 double avg0 = 0; 106 double avg1 = 0; 107 for (int y = 0 ; y < WINDOW_SIZE ; y++) { 108 for (int x = 0 ; x < WINDOW_SIZE ; x++) { 109 int index = indexFromXAndY(x, y, stride, start); 110 avg0 += getIntensity(pixels0[index]); 111 avg1 += getIntensity(pixels1[index]); 112 } 113 } 114 avg0 /= WINDOW_SIZE * WINDOW_SIZE; 115 avg1 /= WINDOW_SIZE * WINDOW_SIZE; 116 return new double[] {avg0, avg1}; 117 } 118 119 /** 120 * Finds the variance of the two sets of pixels, as well as the covariance of the windows. The 121 * return value is an array of doubles, the first is the variance of the first set of pixels, 122 * the second is the variance of the second set of pixels, and the third is the covariance. 123 */ getVariances(int[] pixels0, int[] pixels1, double mean0, double mean1, int start, int stride)124 private double[] getVariances(int[] pixels0, int[] pixels1, double mean0, double mean1, 125 int start, int stride) { 126 double var0 = 0; 127 double var1 = 0; 128 double varBoth = 0; 129 for (int y = 0 ; y < WINDOW_SIZE ; y++) { 130 for (int x = 0 ; x < WINDOW_SIZE ; x++) { 131 int index = indexFromXAndY(x, y, stride, start); 132 double v0 = getIntensity(pixels0[index]) - mean0; 133 double v1 = getIntensity(pixels1[index]) - mean1; 134 var0 += v0 * v0; 135 var1 += v1 * v1; 136 varBoth += v0 * v1; 137 } 138 } 139 var0 /= (WINDOW_SIZE * WINDOW_SIZE) - 1; 140 var1 /= (WINDOW_SIZE * WINDOW_SIZE) - 1; 141 varBoth /= (WINDOW_SIZE * WINDOW_SIZE) - 1; 142 return new double[] {var0, var1, varBoth}; 143 } 144 145 /** 146 * Gets the intensity of a given pixel in RGB using luminosity formula 147 * 148 * l = 0.21R' + 0.72G' + 0.07B' 149 * 150 * The prime symbols dictate a gamma correction of 1. 151 */ getIntensity(int pixel)152 private double getIntensity(int pixel) { 153 final double gamma = 1; 154 double l = 0; 155 l += (0.21f * Math.pow(Color.red(pixel) / 255f, gamma)); 156 l += (0.72f * Math.pow(Color.green(pixel) / 255f, gamma)); 157 l += (0.07f * Math.pow(Color.blue(pixel) / 255f, gamma)); 158 return l; 159 } 160 } 161