1 /*-------------------------------------------------------------------------
2 * drawElements Quality Program Tester Core
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 * \file
21 * \brief Fuzzy image comparison.
22 *//*--------------------------------------------------------------------*/
23
24 #include "tcuFuzzyImageCompare.hpp"
25 #include "tcuTexture.hpp"
26 #include "tcuTextureUtil.hpp"
27 #include "deMath.h"
28 #include "deRandom.hpp"
29
30 #include <vector>
31
32 namespace tcu
33 {
34
35 using std::vector;
36
37 template<int Channel>
getChannel(deUint32 color)38 static inline deUint8 getChannel (deUint32 color)
39 {
40 return (deUint8)((color >> (Channel*8)) & 0xff);
41 }
42
getChannel(deUint32 color,int channel)43 static inline deUint8 getChannel (deUint32 color, int channel)
44 {
45 return (deUint8)((color >> (channel*8)) & 0xff);
46 }
47
setChannel(deUint32 color,int channel,deUint8 val)48 static inline deUint32 setChannel (deUint32 color, int channel, deUint8 val)
49 {
50 return (color & ~(0xffu << (8*channel))) | (val << (8*channel));
51 }
52
toFloatVec(deUint32 color)53 static inline Vec4 toFloatVec (deUint32 color)
54 {
55 return Vec4((float)getChannel<0>(color), (float)getChannel<1>(color), (float)getChannel<2>(color), (float)getChannel<3>(color));
56 }
57
roundToUint8Sat(float v)58 static inline deUint8 roundToUint8Sat (float v)
59 {
60 return (deUint8)de::clamp((int)(v + 0.5f), 0, 255);
61 }
62
toColor(Vec4 v)63 static inline deUint32 toColor (Vec4 v)
64 {
65 return roundToUint8Sat(v[0]) | (roundToUint8Sat(v[1]) << 8) | (roundToUint8Sat(v[2]) << 16) | (roundToUint8Sat(v[3]) << 24);
66 }
67
68 template<int NumChannels>
readUnorm8(const tcu::ConstPixelBufferAccess & src,int x,int y)69 static inline deUint32 readUnorm8 (const tcu::ConstPixelBufferAccess& src, int x, int y)
70 {
71 const deUint8* ptr = (const deUint8*)src.getDataPtr() + src.getRowPitch()*y + x*NumChannels;
72 deUint32 v = 0;
73
74 for (int c = 0; c < NumChannels; c++)
75 v |= ptr[c] << (c*8);
76
77 if (NumChannels < 4)
78 v |= 0xffu << 24;
79
80 return v;
81 }
82
83 #if (DE_ENDIANNESS == DE_LITTLE_ENDIAN)
84 template<>
readUnorm8(const tcu::ConstPixelBufferAccess & src,int x,int y)85 inline deUint32 readUnorm8<4> (const tcu::ConstPixelBufferAccess& src, int x, int y)
86 {
87 return *(const deUint32*)((const deUint8*)src.getDataPtr() + src.getRowPitch()*y + x*4);
88 }
89 #endif
90
91 template<int NumChannels>
writeUnorm8(const tcu::PixelBufferAccess & dst,int x,int y,deUint32 val)92 static inline void writeUnorm8 (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val)
93 {
94 deUint8* ptr = (deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*NumChannels;
95
96 for (int c = 0; c < NumChannels; c++)
97 ptr[c] = getChannel(val, c);
98 }
99
100 #if (DE_ENDIANNESS == DE_LITTLE_ENDIAN)
101 template<>
writeUnorm8(const tcu::PixelBufferAccess & dst,int x,int y,deUint32 val)102 inline void writeUnorm8<4> (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val)
103 {
104 *(deUint32*)((deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*4) = val;
105 }
106 #endif
107
compareColors(deUint32 pa,deUint32 pb,int minErrThreshold)108 static inline float compareColors (deUint32 pa, deUint32 pb, int minErrThreshold)
109 {
110 int r = de::max<int>(de::abs((int)getChannel<0>(pa) - (int)getChannel<0>(pb)) - minErrThreshold, 0);
111 int g = de::max<int>(de::abs((int)getChannel<1>(pa) - (int)getChannel<1>(pb)) - minErrThreshold, 0);
112 int b = de::max<int>(de::abs((int)getChannel<2>(pa) - (int)getChannel<2>(pb)) - minErrThreshold, 0);
113 int a = de::max<int>(de::abs((int)getChannel<3>(pa) - (int)getChannel<3>(pb)) - minErrThreshold, 0);
114
115 float scale = 1.0f/(255-minErrThreshold);
116 float sqSum = (float)(r*r + g*g + b*b + a*a) * (scale*scale);
117
118 return deFloatSqrt(sqSum);
119 }
120
121 template<int NumChannels>
bilinearSample(const ConstPixelBufferAccess & src,float u,float v)122 inline deUint32 bilinearSample (const ConstPixelBufferAccess& src, float u, float v)
123 {
124 int w = src.getWidth();
125 int h = src.getHeight();
126
127 int x0 = deFloorFloatToInt32(u-0.5f);
128 int x1 = x0+1;
129 int y0 = deFloorFloatToInt32(v-0.5f);
130 int y1 = y0+1;
131
132 int i0 = de::clamp(x0, 0, w-1);
133 int i1 = de::clamp(x1, 0, w-1);
134 int j0 = de::clamp(y0, 0, h-1);
135 int j1 = de::clamp(y1, 0, h-1);
136
137 float a = deFloatFrac(u-0.5f);
138 float b = deFloatFrac(v-0.5f);
139
140 deUint32 p00 = readUnorm8<NumChannels>(src, i0, j0);
141 deUint32 p10 = readUnorm8<NumChannels>(src, i1, j0);
142 deUint32 p01 = readUnorm8<NumChannels>(src, i0, j1);
143 deUint32 p11 = readUnorm8<NumChannels>(src, i1, j1);
144 deUint32 dst = 0;
145
146 // Interpolate.
147 for (int c = 0; c < NumChannels; c++)
148 {
149 float f = (getChannel(p00, c)*(1.0f-a)*(1.0f-b)) +
150 (getChannel(p10, c)*( a)*(1.0f-b)) +
151 (getChannel(p01, c)*(1.0f-a)*( b)) +
152 (getChannel(p11, c)*( a)*( b));
153 dst = setChannel(dst, c, roundToUint8Sat(f));
154 }
155
156 return dst;
157 }
158
159 template<int DstChannels, int SrcChannels>
separableConvolve(const PixelBufferAccess & dst,const ConstPixelBufferAccess & src,int shiftX,int shiftY,const std::vector<float> & kernelX,const std::vector<float> & kernelY)160 static void separableConvolve (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, int shiftX, int shiftY, const std::vector<float>& kernelX, const std::vector<float>& kernelY)
161 {
162 DE_ASSERT(dst.getWidth() == src.getWidth() && dst.getHeight() == src.getHeight());
163
164 TextureLevel tmp (dst.getFormat(), dst.getHeight(), dst.getWidth());
165 PixelBufferAccess tmpAccess = tmp.getAccess();
166
167 int kw = (int)kernelX.size();
168 int kh = (int)kernelY.size();
169
170 // Horizontal pass
171 // \note Temporary surface is written in column-wise order
172 for (int j = 0; j < src.getHeight(); j++)
173 {
174 for (int i = 0; i < src.getWidth(); i++)
175 {
176 Vec4 sum(0);
177
178 for (int kx = 0; kx < kw; kx++)
179 {
180 float f = kernelX[kw-kx-1];
181 deUint32 p = readUnorm8<SrcChannels>(src, de::clamp(i+kx-shiftX, 0, src.getWidth()-1), j);
182
183 sum += toFloatVec(p)*f;
184 }
185
186 writeUnorm8<DstChannels>(tmpAccess, j, i, toColor(sum));
187 }
188 }
189
190 // Vertical pass
191 for (int j = 0; j < src.getHeight(); j++)
192 {
193 for (int i = 0; i < src.getWidth(); i++)
194 {
195 Vec4 sum(0.0f);
196
197 for (int ky = 0; ky < kh; ky++)
198 {
199 float f = kernelY[kh-ky-1];
200 deUint32 p = readUnorm8<DstChannels>(tmpAccess, de::clamp(j+ky-shiftY, 0, tmp.getWidth()-1), i);
201
202 sum += toFloatVec(p)*f;
203 }
204
205 writeUnorm8<DstChannels>(dst, i, j, toColor(sum));
206 }
207 }
208 }
209
210 template<int NumChannels>
compareToNeighbor(const FuzzyCompareParams & params,de::Random & rnd,deUint32 pixel,const ConstPixelBufferAccess & surface,int x,int y)211 static float compareToNeighbor (const FuzzyCompareParams& params, de::Random& rnd, deUint32 pixel, const ConstPixelBufferAccess& surface, int x, int y)
212 {
213 float minErr = +100.f;
214
215 // (x, y) + (0, 0)
216 minErr = deFloatMin(minErr, compareColors(pixel, readUnorm8<NumChannels>(surface, x, y), params.minErrThreshold));
217 if (minErr == 0.0f)
218 return minErr;
219
220 // Area around (x, y)
221 static const int s_coords[][2] =
222 {
223 {-1, -1},
224 { 0, -1},
225 {+1, -1},
226 {-1, 0},
227 {+1, 0},
228 {-1, +1},
229 { 0, +1},
230 {+1, +1}
231 };
232
233 for (int d = 0; d < (int)DE_LENGTH_OF_ARRAY(s_coords); d++)
234 {
235 int dx = x + s_coords[d][0];
236 int dy = y + s_coords[d][1];
237
238 if (!deInBounds32(dx, 0, surface.getWidth()) || !deInBounds32(dy, 0, surface.getHeight()))
239 continue;
240
241 minErr = deFloatMin(minErr, compareColors(pixel, readUnorm8<NumChannels>(surface, dx, dy), params.minErrThreshold));
242 if (minErr == 0.0f)
243 return minErr;
244 }
245
246 // Random bilinear-interpolated samples around (x, y)
247 for (int s = 0; s < 32; s++)
248 {
249 float dx = (float)x + rnd.getFloat()*2.0f - 0.5f;
250 float dy = (float)y + rnd.getFloat()*2.0f - 0.5f;
251
252 deUint32 sample = bilinearSample<NumChannels>(surface, dx, dy);
253
254 minErr = deFloatMin(minErr, compareColors(pixel, sample, params.minErrThreshold));
255 if (minErr == 0.0f)
256 return minErr;
257 }
258
259 return minErr;
260 }
261
toGrayscale(const Vec4 & c)262 static inline float toGrayscale (const Vec4& c)
263 {
264 return 0.2126f*c[0] + 0.7152f*c[1] + 0.0722f*c[2];
265 }
266
isFormatSupported(const TextureFormat & format)267 static bool isFormatSupported (const TextureFormat& format)
268 {
269 return format.type == TextureFormat::UNORM_INT8 && (format.order == TextureFormat::RGB || format.order == TextureFormat::RGBA);
270 }
271
fuzzyCompare(const FuzzyCompareParams & params,const ConstPixelBufferAccess & ref,const ConstPixelBufferAccess & cmp,const PixelBufferAccess & errorMask)272 float fuzzyCompare (const FuzzyCompareParams& params, const ConstPixelBufferAccess& ref, const ConstPixelBufferAccess& cmp, const PixelBufferAccess& errorMask)
273 {
274 DE_ASSERT(ref.getWidth() == cmp.getWidth() && ref.getHeight() == cmp.getHeight());
275 DE_ASSERT(errorMask.getWidth() == ref.getWidth() && errorMask.getHeight() == ref.getHeight());
276
277 if (!isFormatSupported(ref.getFormat()) || !isFormatSupported(cmp.getFormat()))
278 throw InternalError("Unsupported format in fuzzy comparison", DE_NULL, __FILE__, __LINE__);
279
280 int width = ref.getWidth();
281 int height = ref.getHeight();
282 de::Random rnd (667);
283
284 // Filtered
285 TextureLevel refFiltered(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), width, height);
286 TextureLevel cmpFiltered(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), width, height);
287
288 // Kernel = {0.15, 0.7, 0.15}
289 vector<float> kernel(3);
290 kernel[0] = kernel[2] = 0.1f; kernel[1]= 0.8f;
291 int shift = (int)(kernel.size() - 1) / 2;
292
293 switch (ref.getFormat().order)
294 {
295 case TextureFormat::RGBA: separableConvolve<4, 4>(refFiltered, ref, shift, shift, kernel, kernel); break;
296 case TextureFormat::RGB: separableConvolve<4, 3>(refFiltered, ref, shift, shift, kernel, kernel); break;
297 default:
298 DE_ASSERT(DE_FALSE);
299 }
300
301 switch (cmp.getFormat().order)
302 {
303 case TextureFormat::RGBA: separableConvolve<4, 4>(cmpFiltered, cmp, shift, shift, kernel, kernel); break;
304 case TextureFormat::RGB: separableConvolve<4, 3>(cmpFiltered, cmp, shift, shift, kernel, kernel); break;
305 default:
306 DE_ASSERT(DE_FALSE);
307 }
308
309 int numSamples = 0;
310 float errSum = 0.0f;
311
312 // Clear error mask to green.
313 clear(errorMask, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
314
315 ConstPixelBufferAccess refAccess = refFiltered.getAccess();
316 ConstPixelBufferAccess cmpAccess = cmpFiltered.getAccess();
317
318 for (int y = 1; y < height-1; y++)
319 {
320 for (int x = 1; x < width-1; x += params.maxSampleSkip > 0 ? (int)rnd.getInt(0, params.maxSampleSkip) : 1)
321 {
322 const float err0 = compareToNeighbor<4>(params, rnd, readUnorm8<4>(refAccess, x, y), cmpAccess, x, y);
323 const float err1 = compareToNeighbor<4>(params, rnd, readUnorm8<4>(cmpAccess, x, y), refAccess, x, y);
324 float err = deFloatMin(err0, err1);
325
326 err = deFloatPow(err, params.errExp);
327
328 errSum += err;
329 numSamples += 1;
330
331 // Build error image.
332 float red = err * 500.0f;
333 float luma = toGrayscale(cmp.getPixel(x, y));
334 float rF = 0.7f + 0.3f*luma;
335 errorMask.setPixel(Vec4(red*rF, (1.0f-red)*rF, 0.0f, 1.0f), x, y);
336 }
337 }
338
339 // Scale error sum based on number of samples taken
340 errSum *= (float)((width-2) * (height-2)) / (float)numSamples;
341
342 return errSum;
343 }
344
345 } // tcu
346