1 /*-------------------------------------------------------------------------
2 * drawElements Quality Program OpenGL ES Utilities
3 * ------------------------------------------------
5 * Copyright 2014 The Android Open Source Project
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
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');
28 goog
.scope(function() {
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
;
36 var DE_ASSERT = function(x
) {
38 throw new Error('Assert failed');
42 * tcuFuzzyImageCompare.FuzzyCompareParams struct
44 * @param {number=} maxSampleSkip_
45 * @param {number=} minErrThreshold_
46 * @param {number=} errExp_
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_
;
55 * @param {Array<number>} v
56 * @return {Array<number>}
58 tcuFuzzyImageCompare
.roundArray4ToUint8Sat = function(v
) {
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)
68 * @param {Array<number>} pa
69 * @param {Array<number>} pb
70 * @param {number} minErrThreshold
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);
79 /** @type {number}*/ var scale
= 1.0 / (255 - minErrThreshold
);
80 /** @type {number}*/ var sqSum
= (r
* r
+ g
* g
+ b
* b
+ a
* a
) * (scale
* scale
);
82 return Math
.sqrt(sqSum
);
86 * @param {tcuTexture.RGBA8View} src
89 * @param {number} NumChannels
90 * @return {Array<number>}
92 tcuFuzzyImageCompare
.bilinearSample = function(src
, u
, v
, NumChannels
) {
93 /** @type {number}*/ var w
= src
.width
;
94 /** @type {number}*/ var h
= src
.height
;
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;
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);
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);
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;
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
) +
124 return tcuFuzzyImageCompare
.roundArray4ToUint8Sat(f
);
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
137 tcuFuzzyImageCompare
.separableConvolve = function(dst
, src
, shiftX
, shiftY
, kernelX
, kernelY
, DstChannels
, SrcChannels
) {
138 DE_ASSERT(dst
.width
== src
.width
&& dst
.height
== src
.height
);
140 /** @type {tcuTexture.TextureLevel} */ var tmp
= new tcuTexture
.TextureLevel(dst
.getFormat(), dst
.height
, dst
.width
);
141 var tmpView
= new tcuTexture
.RGBA8View(tmp
.getAccess());
143 /** @type {number} */ var kw
= kernelX
.length
;
144 /** @type {number} */ var kh
= kernelY
.length
;
146 /** @type {Array<number>} */ var sum
= [];
147 /** @type {number} */ var f
;
148 /** @type {Array<number>} */ var p
;
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
));
161 sum
= tcuFuzzyImageCompare
.roundArray4ToUint8Sat(sum
);
162 tmpView
.write(j
, i
, sum
, DstChannels
);
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
));
176 sum
= tcuFuzzyImageCompare
.roundArray4ToUint8Sat(sum
);
177 dst
.write(i
, j
, sum
, DstChannels
);
183 * @param {tcuFuzzyImageCompare.FuzzyCompareParams} params
184 * @param {deRandom.Random} rnd
185 * @param {Array<number>} pixel
186 * @param {tcuTexture.RGBA8View} surface
189 * @param {number} NumChannels
192 tcuFuzzyImageCompare
.compareToNeighbor = function(params
, rnd
, pixel
, surface
, x
, y
, NumChannels
) {
193 /** @type {number} */ var minErr
= 100;
196 minErr
= Math
.min(minErr
, tcuFuzzyImageCompare
.compareColors(pixel
, surface
.read(x
, y
, NumChannels
), params
.minErrThreshold
));
200 // Area around (x, y)
201 /** @type {Array<Array.<number>>} */ var s_coords
=
213 /** @type {number} */ var dx
;
214 /** @type {number} */ var dy
;
216 for (var d
= 0; d
< s_coords
.length
; d
++) {
217 dx
= x
+ s_coords
[d
][0];
218 dy
= y
+ s_coords
[d
][1];
220 if (!deMath
.deInBounds32(dx
, 0, surface
.width
) || !deMath
.deInBounds32(dy
, 0, surface
.height
))
223 minErr
= Math
.min(minErr
, tcuFuzzyImageCompare
.compareColors(pixel
, surface
.read(dx
, dy
, NumChannels
), params
.minErrThreshold
));
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;
233 /** @type {Array<number>} */ var sample
= tcuFuzzyImageCompare
.bilinearSample(surface
, dx
, dy
, NumChannels
);
235 minErr
= Math
.min(minErr
, tcuFuzzyImageCompare
.compareColors(pixel
, sample
, params
.minErrThreshold
));
244 * @param {Array<number>} c
247 tcuFuzzyImageCompare
.toGrayscale = function(c
) {
248 return 0.2126 * c
[0] + 0.7152 * c
[1] + 0.0722 * c
[2];
252 * @param {tcuTexture.TextureFormat} format
255 tcuFuzzyImageCompare
.isFormatSupported = function(format
) {
256 return format
.type
== tcuTexture
.ChannelType
.UNORM_INT8
&& (format
.order
== tcuTexture
.ChannelOrder
.RGB
|| format
.order
== tcuTexture
.ChannelOrder
.RGBA
);
260 * @param {tcuFuzzyImageCompare.FuzzyCompareParams} params
261 * @param {tcuTexture.ConstPixelBufferAccess} ref
262 * @param {tcuTexture.ConstPixelBufferAccess} cmp
263 * @param {tcuTexture.PixelBufferAccess} errorMask
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);
270 assertMsgOptions(ref
.getWidth() == errorMask
.getWidth() && ref
.getHeight() == errorMask
.getHeight(),
271 'Reference and error mask images have different dimensions', false, true);
273 if (!tcuFuzzyImageCompare
.isFormatSupported(ref
.getFormat()) || !tcuFuzzyImageCompare
.isFormatSupported(cmp
.getFormat()))
274 throw new Error('Unsupported format in fuzzy comparison');
276 /** @type {number} */ var width
= ref
.getWidth();
277 /** @type {number} */ var height
= ref
.getHeight();
278 /** @type {deRandom.Random} */ var rnd
= new deRandom
.Random(667);
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
);
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
));
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);
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;
297 throw new Error('tcuFuzzyImageCompare.fuzzyCompare - Invalid ChannelOrder');
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;
304 throw new Error('tcuFuzzyImageCompare.fuzzyCompare - Invalid ChannelOrder');
307 /** @type {number} */ var numSamples
= 0;
308 /** @type {number} */ var errSum
= 0.0;
310 // Clear error mask to green.
311 errorMask
.clear([0.0, 1.0, 0.0, 1.0]);
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));
318 err
= Math
.pow(err
, params
.errExp
);
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
);
332 // Scale error sum based on number of samples taken
333 errSum
*= ((width
- 2) * (height
- 2)) / numSamples
;