tcuFuzzyImageCompare.js (14104B)
1 /*------------------------------------------------------------------------- 2 * drawElements Quality Program OpenGL ES Utilities 3 * ------------------------------------------------ 4 * 5 * Copyright 2014 The Android Open Source Project 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 */ 20 21 'use strict'; 22 goog.provide('framework.common.tcuFuzzyImageCompare'); 23 goog.require('framework.common.tcuTexture'); 24 goog.require('framework.common.tcuTextureUtil'); 25 goog.require('framework.delibs.debase.deMath'); 26 goog.require('framework.delibs.debase.deRandom'); 27 28 goog.scope(function() { 29 30 var tcuFuzzyImageCompare = framework.common.tcuFuzzyImageCompare; 31 var deMath = framework.delibs.debase.deMath; 32 var deRandom = framework.delibs.debase.deRandom; 33 var tcuTexture = framework.common.tcuTexture; 34 var tcuTextureUtil = framework.common.tcuTextureUtil; 35 36 var DE_ASSERT = function(x) { 37 if (!x) 38 throw new Error('Assert failed'); 39 }; 40 41 /** 42 * tcuFuzzyImageCompare.FuzzyCompareParams struct 43 * @constructor 44 * @param {number=} maxSampleSkip_ 45 * @param {number=} minErrThreshold_ 46 * @param {number=} errExp_ 47 */ 48 tcuFuzzyImageCompare.FuzzyCompareParams = function(maxSampleSkip_, minErrThreshold_, errExp_) { 49 /** @type {number} */ this.maxSampleSkip = maxSampleSkip_ === undefined ? 8 : maxSampleSkip_; 50 /** @type {number} */ this.minErrThreshold = minErrThreshold_ === undefined ? 4 : minErrThreshold_; 51 /** @type {number} */ this.errExp = errExp_ === undefined ? 4.0 : errExp_; 52 }; 53 54 /** 55 * @param {Array<number>} v 56 * @return {Array<number>} 57 */ 58 tcuFuzzyImageCompare.roundArray4ToUint8Sat = function(v) { 59 return [ 60 deMath.clamp(Math.trunc(v[0] + 0.5), 0, 255), 61 deMath.clamp(Math.trunc(v[1] + 0.5), 0, 255), 62 deMath.clamp(Math.trunc(v[2] + 0.5), 0, 255), 63 deMath.clamp(Math.trunc(v[3] + 0.5), 0, 255) 64 ]; 65 }; 66 67 /** 68 * @param {Array<number>} pa 69 * @param {Array<number>} pb 70 * @param {number} minErrThreshold 71 * @return {number} 72 */ 73 tcuFuzzyImageCompare.compareColors = function(pa, pb, minErrThreshold) { 74 /** @type {number}*/ var r = Math.max(Math.abs(pa[0] - pb[0]) - minErrThreshold, 0); 75 /** @type {number}*/ var g = Math.max(Math.abs(pa[1] - pb[1]) - minErrThreshold, 0); 76 /** @type {number}*/ var b = Math.max(Math.abs(pa[2] - pb[2]) - minErrThreshold, 0); 77 /** @type {number}*/ var a = Math.max(Math.abs(pa[3] - pb[3]) - minErrThreshold, 0); 78 79 /** @type {number}*/ var scale = 1.0 / (255 - minErrThreshold); 80 /** @type {number}*/ var sqSum = (r * r + g * g + b * b + a * a) * (scale * scale); 81 82 return Math.sqrt(sqSum); 83 }; 84 85 /** 86 * @param {tcuTexture.RGBA8View} src 87 * @param {number} u 88 * @param {number} v 89 * @param {number} NumChannels 90 * @return {Array<number>} 91 */ 92 tcuFuzzyImageCompare.bilinearSample = function(src, u, v, NumChannels) { 93 /** @type {number}*/ var w = src.width; 94 /** @type {number}*/ var h = src.height; 95 96 /** @type {number}*/ var x0 = Math.floor(u - 0.5); 97 /** @type {number}*/ var x1 = x0 + 1; 98 /** @type {number}*/ var y0 = Math.floor(v - 0.5); 99 /** @type {number}*/ var y1 = y0 + 1; 100 101 /** @type {number}*/ var i0 = deMath.clamp(x0, 0, w - 1); 102 /** @type {number}*/ var i1 = deMath.clamp(x1, 0, w - 1); 103 /** @type {number}*/ var j0 = deMath.clamp(y0, 0, h - 1); 104 /** @type {number}*/ var j1 = deMath.clamp(y1, 0, h - 1); 105 106 /** @type {number}*/ var a = (u - 0.5) - Math.floor(u - 0.5); 107 /** @type {number}*/ var b = (v - 0.5) - Math.floor(v - 0.5); 108 109 /** @type {Array<number>} */ var p00 = src.read(i0, j0, NumChannels); 110 /** @type {Array<number>} */ var p10 = src.read(i1, j0, NumChannels); 111 /** @type {Array<number>} */ var p01 = src.read(i0, j1, NumChannels); 112 /** @type {Array<number>} */ var p11 = src.read(i1, j1, NumChannels); 113 /** @type {number} */ var dst = 0; 114 115 // Interpolate. 116 /** @type {Array<number>}*/ var f = []; 117 for (var c = 0; c < NumChannels; c++) { 118 f[c] = p00[c] * (1.0 - a) * (1.0 - b) + 119 (p10[c] * a * (1.0 - b)) + 120 (p01[c] * (1.0 - a) * b) + 121 (p11[c] * a * b); 122 } 123 124 return tcuFuzzyImageCompare.roundArray4ToUint8Sat(f); 125 }; 126 127 /** 128 * @param {tcuTexture.RGBA8View} dst 129 * @param {tcuTexture.RGBA8View} src 130 * @param {number} shiftX 131 * @param {number} shiftY 132 * @param {Array<number>} kernelX 133 * @param {Array<number>} kernelY 134 * @param {number} DstChannels 135 * @param {number} SrcChannels 136 */ 137 tcuFuzzyImageCompare.separableConvolve = function(dst, src, shiftX, shiftY, kernelX, kernelY, DstChannels, SrcChannels) { 138 DE_ASSERT(dst.width == src.width && dst.height == src.height); 139 140 /** @type {tcuTexture.TextureLevel} */ var tmp = new tcuTexture.TextureLevel(dst.getFormat(), dst.height, dst.width); 141 var tmpView = new tcuTexture.RGBA8View(tmp.getAccess()); 142 143 /** @type {number} */ var kw = kernelX.length; 144 /** @type {number} */ var kh = kernelY.length; 145 146 /** @type {Array<number>} */ var sum = []; 147 /** @type {number} */ var f; 148 /** @type {Array<number>} */ var p; 149 150 // Horizontal pass 151 // \note Temporary surface is written in column-wise order 152 for (var j = 0; j < src.height; j++) { 153 for (var i = 0; i < src.width; i++) { 154 sum[0] = sum[1] = sum[2] = sum[3] = 0; 155 for (var kx = 0; kx < kw; kx++) { 156 f = kernelX[kw - kx - 1]; 157 p = src.read(deMath.clamp(i + kx - shiftX, 0, src.width - 1), j, SrcChannels); 158 sum = deMath.add(sum, deMath.scale(p, f)); 159 } 160 161 sum = tcuFuzzyImageCompare.roundArray4ToUint8Sat(sum); 162 tmpView.write(j, i, sum, DstChannels); 163 } 164 } 165 166 // Vertical pass 167 for (var j = 0; j < src.height; j++) { 168 for (var i = 0; i < src.width; i++) { 169 sum[0] = sum[1] = sum[2] = sum[3] = 0; 170 for (var ky = 0; ky < kh; ky++) { 171 f = kernelY[kh - ky - 1]; 172 p = tmpView.read(deMath.clamp(j + ky - shiftY, 0, tmpView.width - 1), i, DstChannels); 173 sum = deMath.add(sum, deMath.scale(p, f)); 174 } 175 176 sum = tcuFuzzyImageCompare.roundArray4ToUint8Sat(sum); 177 dst.write(i, j, sum, DstChannels); 178 } 179 } 180 }; 181 182 /** 183 * @param {tcuFuzzyImageCompare.FuzzyCompareParams} params 184 * @param {deRandom.Random} rnd 185 * @param {Array<number>} pixel 186 * @param {tcuTexture.RGBA8View} surface 187 * @param {number} x 188 * @param {number} y 189 * @param {number} NumChannels 190 * @return {number} 191 */ 192 tcuFuzzyImageCompare.compareToNeighbor = function(params, rnd, pixel, surface, x, y, NumChannels) { 193 /** @type {number} */ var minErr = 100; 194 195 // (x, y) + (0, 0) 196 minErr = Math.min(minErr, tcuFuzzyImageCompare.compareColors(pixel, surface.read(x, y, NumChannels), params.minErrThreshold)); 197 if (minErr == 0.0) 198 return minErr; 199 200 // Area around (x, y) 201 /** @type {Array<Array.<number>>} */ var s_coords = 202 [ 203 [-1, -1], 204 [0, -1], 205 [1, -1], 206 [-1, 0], 207 [1, 0], 208 [-1, 1], 209 [0, 1], 210 [1, 1] 211 ]; 212 213 /** @type {number} */ var dx; 214 /** @type {number} */ var dy; 215 216 for (var d = 0; d < s_coords.length; d++) { 217 dx = x + s_coords[d][0]; 218 dy = y + s_coords[d][1]; 219 220 if (!deMath.deInBounds32(dx, 0, surface.width) || !deMath.deInBounds32(dy, 0, surface.height)) 221 continue; 222 223 minErr = Math.min(minErr, tcuFuzzyImageCompare.compareColors(pixel, surface.read(dx, dy, NumChannels), params.minErrThreshold)); 224 if (minErr == 0.0) 225 return minErr; 226 } 227 228 // Random bilinear-interpolated samples around (x, y) 229 for (var s = 0; s < 32; s++) { 230 dx = x + rnd.getFloat() * 2.0 - 0.5; 231 dy = y + rnd.getFloat() * 2.0 - 0.5; 232 233 /** @type {Array<number>} */ var sample = tcuFuzzyImageCompare.bilinearSample(surface, dx, dy, NumChannels); 234 235 minErr = Math.min(minErr, tcuFuzzyImageCompare.compareColors(pixel, sample, params.minErrThreshold)); 236 if (minErr == 0.0) 237 return minErr; 238 } 239 240 return minErr; 241 }; 242 243 /** 244 * @param {Array<number>} c 245 * @return {number} 246 */ 247 tcuFuzzyImageCompare.toGrayscale = function(c) { 248 return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; 249 }; 250 251 /** 252 * @param {tcuTexture.TextureFormat} format 253 * @return {boolean} 254 */ 255 tcuFuzzyImageCompare.isFormatSupported = function(format) { 256 return format.type == tcuTexture.ChannelType.UNORM_INT8 && (format.order == tcuTexture.ChannelOrder.RGB || format.order == tcuTexture.ChannelOrder.RGBA); 257 }; 258 259 /** 260 * @param {tcuFuzzyImageCompare.FuzzyCompareParams} params 261 * @param {tcuTexture.ConstPixelBufferAccess} ref 262 * @param {tcuTexture.ConstPixelBufferAccess} cmp 263 * @param {tcuTexture.PixelBufferAccess} errorMask 264 * @return {number} 265 */ 266 tcuFuzzyImageCompare.fuzzyCompare = function(params, ref, cmp, errorMask) { 267 assertMsgOptions(ref.getWidth() == cmp.getWidth() && ref.getHeight() == cmp.getHeight(), 268 'Reference and result images have different dimensions', false, true); 269 270 assertMsgOptions(ref.getWidth() == errorMask.getWidth() && ref.getHeight() == errorMask.getHeight(), 271 'Reference and error mask images have different dimensions', false, true); 272 273 if (!tcuFuzzyImageCompare.isFormatSupported(ref.getFormat()) || !tcuFuzzyImageCompare.isFormatSupported(cmp.getFormat())) 274 throw new Error('Unsupported format in fuzzy comparison'); 275 276 /** @type {number} */ var width = ref.getWidth(); 277 /** @type {number} */ var height = ref.getHeight(); 278 /** @type {deRandom.Random} */ var rnd = new deRandom.Random(667); 279 280 // Filtered 281 /** @type {tcuTexture.TextureLevel} */ var refFiltered = new tcuTexture.TextureLevel(new tcuTexture.TextureFormat(tcuTexture.ChannelOrder.RGBA, tcuTexture.ChannelType.UNORM_INT8), width, height); 282 /** @type {tcuTexture.TextureLevel} */ var cmpFiltered = new tcuTexture.TextureLevel(new tcuTexture.TextureFormat(tcuTexture.ChannelOrder.RGBA, tcuTexture.ChannelType.UNORM_INT8), width, height); 283 284 var refView = new tcuTexture.RGBA8View(ref); 285 var cmpView = new tcuTexture.RGBA8View(cmp); 286 var refFilteredView = new tcuTexture.RGBA8View(tcuTexture.PixelBufferAccess.newFromTextureLevel(refFiltered)); 287 var cmpFilteredView = new tcuTexture.RGBA8View(tcuTexture.PixelBufferAccess.newFromTextureLevel(cmpFiltered)); 288 289 // Kernel = {0.15, 0.7, 0.15} 290 /** @type {Array<number>} */ var kernel = [0.1, 0.8, 0.1]; 291 /** @type {number} */ var shift = Math.floor((kernel.length - 1) / 2); 292 293 switch (ref.getFormat().order) { 294 case tcuTexture.ChannelOrder.RGBA: tcuFuzzyImageCompare.separableConvolve(refFilteredView, refView, shift, shift, kernel, kernel, 4, 4); break; 295 case tcuTexture.ChannelOrder.RGB: tcuFuzzyImageCompare.separableConvolve(refFilteredView, refView, shift, shift, kernel, kernel, 4, 3); break; 296 default: 297 throw new Error('tcuFuzzyImageCompare.fuzzyCompare - Invalid ChannelOrder'); 298 } 299 300 switch (cmp.getFormat().order) { 301 case tcuTexture.ChannelOrder.RGBA: tcuFuzzyImageCompare.separableConvolve(cmpFilteredView, cmpView, shift, shift, kernel, kernel, 4, 4); break; 302 case tcuTexture.ChannelOrder.RGB: tcuFuzzyImageCompare.separableConvolve(cmpFilteredView, cmpView, shift, shift, kernel, kernel, 4, 3); break; 303 default: 304 throw new Error('tcuFuzzyImageCompare.fuzzyCompare - Invalid ChannelOrder'); 305 } 306 307 /** @type {number} */ var numSamples = 0; 308 /** @type {number} */ var errSum = 0.0; 309 310 // Clear error mask to green. 311 errorMask.clear([0.0, 1.0, 0.0, 1.0]); 312 313 for (var y = 1; y < height - 1; y++) { 314 for (var x = 1; x < width - 1; x += params.maxSampleSkip > 0 ? rnd.getInt(0, params.maxSampleSkip) : 1) { 315 /** @type {number} */ var err = Math.min(tcuFuzzyImageCompare.compareToNeighbor(params, rnd, refFilteredView.read(x, y, 4), cmpFilteredView, x, y, 4), 316 tcuFuzzyImageCompare.compareToNeighbor(params, rnd, cmpFilteredView.read(x, y, 4), refFilteredView, x, y, 4)); 317 318 err = Math.pow(err, params.errExp); 319 320 errSum += err; 321 numSamples += 1; 322 323 // Build error image. 324 /** @type {number} */ var red = err * 500.0; 325 /** @type {number} */ var luma = tcuFuzzyImageCompare.toGrayscale(cmp.getPixel(x, y)); 326 /** @type {number} */ var rF = 0.7 + 0.3 * luma; 327 errorMask.setPixel([red * rF, (1.0 - red) * rF, 0.0, 1.0], x, y); 328 329 } 330 } 331 332 // Scale error sum based on number of samples taken 333 errSum *= ((width - 2) * (height - 2)) / numSamples; 334 335 return errSum; 336 }; 337 338 });