1 /*-------------------------------------------------------------------------
2 * drawElements Quality Program Tester Core
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.
21 * \brief Fuzzy image comparison.
22 *//*--------------------------------------------------------------------*/
24 #include "tcuFuzzyImageCompare.hpp"
25 #include "tcuTexture.hpp"
26 #include "tcuTextureUtil.hpp"
28 #include "deRandom.hpp"
37 MIN_ERR_THRESHOLD = 4 // Magic to make small differences go away
43 static inline deUint8 getChannel (deUint32 color)
45 return (deUint8)((color >> (Channel*8)) & 0xff);
48 static inline deUint8 getChannel (deUint32 color, int channel)
50 return (deUint8)((color >> (channel*8)) & 0xff);
53 static inline deUint32 setChannel (deUint32 color, int channel, deUint8 val)
55 return (color & ~(0xffu << (8*channel))) | (val << (8*channel));
58 static inline Vec4 toFloatVec (deUint32 color)
60 return Vec4((float)getChannel<0>(color), (float)getChannel<1>(color), (float)getChannel<2>(color), (float)getChannel<3>(color));
63 static inline deUint8 roundToUint8Sat (float v)
65 return (deUint8)de::clamp((int)(v + 0.5f), 0, 255);
68 static inline deUint32 toColor (Vec4 v)
70 return roundToUint8Sat(v[0]) | (roundToUint8Sat(v[1]) << 8) | (roundToUint8Sat(v[2]) << 16) | (roundToUint8Sat(v[3]) << 24);
73 template<int NumChannels>
74 static inline deUint32 readUnorm8 (const tcu::ConstPixelBufferAccess& src, int x, int y)
76 const deUint8* ptr = (const deUint8*)src.getDataPtr() + src.getRowPitch()*y + x*NumChannels;
79 for (int c = 0; c < NumChannels; c++)
88 #if (DE_ENDIANNESS == DE_LITTLE_ENDIAN)
90 inline deUint32 readUnorm8<4> (const tcu::ConstPixelBufferAccess& src, int x, int y)
92 return *(const deUint32*)((const deUint8*)src.getDataPtr() + src.getRowPitch()*y + x*4);
96 template<int NumChannels>
97 static inline void writeUnorm8 (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val)
99 deUint8* ptr = (deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*NumChannels;
101 for (int c = 0; c < NumChannels; c++)
102 ptr[c] = getChannel(val, c);
105 #if (DE_ENDIANNESS == DE_LITTLE_ENDIAN)
107 inline void writeUnorm8<4> (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val)
109 *(deUint32*)((deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*4) = val;
113 static inline deUint32 colorDistSquared (deUint32 pa, deUint32 pb)
115 const int r = de::max<int>(de::abs((int)getChannel<0>(pa) - (int)getChannel<0>(pb)) - MIN_ERR_THRESHOLD, 0);
116 const int g = de::max<int>(de::abs((int)getChannel<1>(pa) - (int)getChannel<1>(pb)) - MIN_ERR_THRESHOLD, 0);
117 const int b = de::max<int>(de::abs((int)getChannel<2>(pa) - (int)getChannel<2>(pb)) - MIN_ERR_THRESHOLD, 0);
118 const int a = de::max<int>(de::abs((int)getChannel<3>(pa) - (int)getChannel<3>(pb)) - MIN_ERR_THRESHOLD, 0);
120 return deUint32(r*r + g*g + b*b + a*a);
123 template<int NumChannels>
124 inline deUint32 bilinearSample (const ConstPixelBufferAccess& src, float u, float v)
126 int w = src.getWidth();
127 int h = src.getHeight();
129 int x0 = deFloorFloatToInt32(u-0.5f);
131 int y0 = deFloorFloatToInt32(v-0.5f);
134 int i0 = de::clamp(x0, 0, w-1);
135 int i1 = de::clamp(x1, 0, w-1);
136 int j0 = de::clamp(y0, 0, h-1);
137 int j1 = de::clamp(y1, 0, h-1);
139 float a = deFloatFrac(u-0.5f);
140 float b = deFloatFrac(v-0.5f);
142 deUint32 p00 = readUnorm8<NumChannels>(src, i0, j0);
143 deUint32 p10 = readUnorm8<NumChannels>(src, i1, j0);
144 deUint32 p01 = readUnorm8<NumChannels>(src, i0, j1);
145 deUint32 p11 = readUnorm8<NumChannels>(src, i1, j1);
149 for (int c = 0; c < NumChannels; c++)
151 float f = (getChannel(p00, c)*(1.0f-a)*(1.0f-b)) +
152 (getChannel(p10, c)*( a)*(1.0f-b)) +
153 (getChannel(p01, c)*(1.0f-a)*( b)) +
154 (getChannel(p11, c)*( a)*( b));
155 dst = setChannel(dst, c, roundToUint8Sat(f));
161 template<int DstChannels, int SrcChannels>
162 static void separableConvolve (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, int shiftX, int shiftY, const std::vector<float>& kernelX, const std::vector<float>& kernelY)
164 DE_ASSERT(dst.getWidth() == src.getWidth() && dst.getHeight() == src.getHeight());
166 TextureLevel tmp (dst.getFormat(), dst.getHeight(), dst.getWidth());
167 PixelBufferAccess tmpAccess = tmp.getAccess();
169 int kw = (int)kernelX.size();
170 int kh = (int)kernelY.size();
173 // \note Temporary surface is written in column-wise order
174 for (int j = 0; j < src.getHeight(); j++)
176 for (int i = 0; i < src.getWidth(); i++)
180 for (int kx = 0; kx < kw; kx++)
182 float f = kernelX[kw-kx-1];
183 deUint32 p = readUnorm8<SrcChannels>(src, de::clamp(i+kx-shiftX, 0, src.getWidth()-1), j);
185 sum += toFloatVec(p)*f;
188 writeUnorm8<DstChannels>(tmpAccess, j, i, toColor(sum));
193 for (int j = 0; j < src.getHeight(); j++)
195 for (int i = 0; i < src.getWidth(); i++)
199 for (int ky = 0; ky < kh; ky++)
201 float f = kernelY[kh-ky-1];
202 deUint32 p = readUnorm8<DstChannels>(tmpAccess, de::clamp(j+ky-shiftY, 0, tmp.getWidth()-1), i);
204 sum += toFloatVec(p)*f;
207 writeUnorm8<DstChannels>(dst, i, j, toColor(sum));
212 template<int NumChannels>
213 static deUint32 distSquaredToNeighbor (de::Random& rnd, deUint32 pixel, const ConstPixelBufferAccess& surface, int x, int y)
216 deUint32 minDist = colorDistSquared(pixel, readUnorm8<NumChannels>(surface, x, y));
221 // Area around (x, y)
222 static const int s_coords[][2] =
234 for (int d = 0; d < (int)DE_LENGTH_OF_ARRAY(s_coords); d++)
236 int dx = x + s_coords[d][0];
237 int dy = y + s_coords[d][1];
239 if (!deInBounds32(dx, 0, surface.getWidth()) || !deInBounds32(dy, 0, surface.getHeight()))
242 minDist = de::min(minDist, colorDistSquared(pixel, readUnorm8<NumChannels>(surface, dx, dy)));
247 // Random bilinear-interpolated samples around (x, y)
248 for (int s = 0; s < 32; s++)
250 float dx = (float)x + rnd.getFloat()*2.0f - 0.5f;
251 float dy = (float)y + rnd.getFloat()*2.0f - 0.5f;
253 deUint32 sample = bilinearSample<NumChannels>(surface, dx, dy);
255 minDist = de::min(minDist, colorDistSquared(pixel, sample));
263 static inline float toGrayscale (const Vec4& c)
265 return 0.2126f*c[0] + 0.7152f*c[1] + 0.0722f*c[2];
268 static bool isFormatSupported (const TextureFormat& format)
270 return format.type == TextureFormat::UNORM_INT8 && (format.order == TextureFormat::RGB || format.order == TextureFormat::RGBA);
273 float fuzzyCompare (const FuzzyCompareParams& params, const ConstPixelBufferAccess& ref, const ConstPixelBufferAccess& cmp, const PixelBufferAccess& errorMask)
275 DE_ASSERT(ref.getWidth() == cmp.getWidth() && ref.getHeight() == cmp.getHeight());
276 DE_ASSERT(errorMask.getWidth() == ref.getWidth() && errorMask.getHeight() == ref.getHeight());
278 if (!isFormatSupported(ref.getFormat()) || !isFormatSupported(cmp.getFormat()))
279 throw InternalError("Unsupported format in fuzzy comparison", DE_NULL, __FILE__, __LINE__);
281 int width = ref.getWidth();
282 int height = ref.getHeight();
283 de::Random rnd (667);
286 TextureLevel refFiltered(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), width, height);
287 TextureLevel cmpFiltered(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), width, height);
289 // Kernel = {0.1, 0.8, 0.1}
290 vector<float> kernel(3);
291 kernel[0] = kernel[2] = 0.1f; kernel[1]= 0.8f;
292 int shift = (int)(kernel.size() - 1) / 2;
294 switch (ref.getFormat().order)
296 case TextureFormat::RGBA: separableConvolve<4, 4>(refFiltered, ref, shift, shift, kernel, kernel); break;
297 case TextureFormat::RGB: separableConvolve<4, 3>(refFiltered, ref, shift, shift, kernel, kernel); break;
302 switch (cmp.getFormat().order)
304 case TextureFormat::RGBA: separableConvolve<4, 4>(cmpFiltered, cmp, shift, shift, kernel, kernel); break;
305 case TextureFormat::RGB: separableConvolve<4, 3>(cmpFiltered, cmp, shift, shift, kernel, kernel); break;
311 deUint64 distSum4 = 0ull;
313 // Clear error mask to green.
314 clear(errorMask, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
316 ConstPixelBufferAccess refAccess = refFiltered.getAccess();
317 ConstPixelBufferAccess cmpAccess = cmpFiltered.getAccess();
319 for (int y = 1; y < height-1; y++)
321 for (int x = 1; x < width-1; x += params.maxSampleSkip > 0 ? (int)rnd.getInt(0, params.maxSampleSkip) : 1)
323 const deUint32 minDist2RefToCmp = distSquaredToNeighbor<4>(rnd, readUnorm8<4>(refAccess, x, y), cmpAccess, x, y);
324 const deUint32 minDist2CmpToRef = distSquaredToNeighbor<4>(rnd, readUnorm8<4>(cmpAccess, x, y), refAccess, x, y);
325 const deUint32 minDist2 = de::min(minDist2RefToCmp, minDist2CmpToRef);
326 const deUint64 newSum4 = distSum4 + minDist2*minDist2;
328 distSum4 = (newSum4 >= distSum4) ? newSum4 : ~0ull; // In case of overflow
331 // Build error image.
333 const int scale = 255-MIN_ERR_THRESHOLD;
334 const float err2 = float(minDist2) / float(scale*scale);
335 const float err4 = err2*err2;
336 const float red = err4 * 500.0f;
337 const float luma = toGrayscale(cmp.getPixel(x, y));
338 const float rF = 0.7f + 0.3f*luma;
340 errorMask.setPixel(Vec4(red*rF, (1.0f-red)*rF, 0.0f, 1.0f), x, y);
346 // Scale error sum based on number of samples taken
347 const double pSamples = double((width-2) * (height-2)) / double(numSamples);
348 const deUint64 colScale = deUint64(255-MIN_ERR_THRESHOLD);
349 const deUint64 colScale4 = colScale*colScale*colScale*colScale;
351 return float(double(distSum4) / double(colScale4) * pSamples);