tcuFuzzyImageCompare.cpp revision 74731a6adf5816339b00b854a513b1b950ad4357
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 32namespace tcu 33{ 34 35using std::vector; 36 37template<int Channel> 38static inline deUint8 getChannel (deUint32 color) 39{ 40 return (deUint8)((color >> (Channel*8)) & 0xff); 41} 42 43static inline deUint8 getChannel (deUint32 color, int channel) 44{ 45 return (deUint8)((color >> (channel*8)) & 0xff); 46} 47 48static inline deUint32 setChannel (deUint32 color, int channel, deUint8 val) 49{ 50 return (color & ~(0xffu << (8*channel))) | (val << (8*channel)); 51} 52 53static 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 58static inline deUint8 roundToUint8Sat (float v) 59{ 60 return (deUint8)de::clamp((int)(v + 0.5f), 0, 255); 61} 62 63static 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 68template<int NumChannels> 69static 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) 84template<> 85inline 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 91template<int NumChannels> 92static 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) 101template<> 102inline 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 108static 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 121template<int NumChannels> 122inline 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 159template<int DstChannels, int SrcChannels> 160static 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 210template<int NumChannels> 211static 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 262static inline float toGrayscale (const Vec4& c) 263{ 264 return 0.2126f*c[0] + 0.7152f*c[1] + 0.0722f*c[2]; 265} 266 267static bool isFormatSupported (const TextureFormat& format) 268{ 269 return format.type == TextureFormat::UNORM_INT8 && (format.order == TextureFormat::RGB || format.order == TextureFormat::RGBA); 270} 271 272float 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 float err = deFloatMin(compareToNeighbor<4>(params, rnd, readUnorm8<4>(refAccess, x, y), cmpAccess, x, y), 323 compareToNeighbor<4>(params, rnd, readUnorm8<4>(cmpAccess, x, y), refAccess, x, y)); 324 325 err = deFloatPow(err, params.errExp); 326 327 errSum += err; 328 numSamples += 1; 329 330 // Build error image. 331 float red = err * 500.0f; 332 float luma = toGrayscale(cmp.getPixel(x, y)); 333 float rF = 0.7f + 0.3f*luma; 334 errorMask.setPixel(Vec4(red*rF, (1.0f-red)*rF, 0.0f, 1.0f), x, y); 335 } 336 } 337 338 // Scale error sum based on number of samples taken 339 errSum *= (float)((width-2) * (height-2)) / (float)numSamples; 340 341 return errSum; 342} 343 344} // tcu 345