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 35enum 36{ 37 MIN_ERR_THRESHOLD = 4 // Magic to make small differences go away 38}; 39 40using std::vector; 41 42template<int Channel> 43static inline deUint8 getChannel (deUint32 color) 44{ 45 return (deUint8)((color >> (Channel*8)) & 0xff); 46} 47 48static inline deUint8 getChannel (deUint32 color, int channel) 49{ 50 return (deUint8)((color >> (channel*8)) & 0xff); 51} 52 53static inline deUint32 setChannel (deUint32 color, int channel, deUint8 val) 54{ 55 return (color & ~(0xffu << (8*channel))) | (val << (8*channel)); 56} 57 58static inline Vec4 toFloatVec (deUint32 color) 59{ 60 return Vec4((float)getChannel<0>(color), (float)getChannel<1>(color), (float)getChannel<2>(color), (float)getChannel<3>(color)); 61} 62 63static inline deUint8 roundToUint8Sat (float v) 64{ 65 return (deUint8)de::clamp((int)(v + 0.5f), 0, 255); 66} 67 68static inline deUint32 toColor (Vec4 v) 69{ 70 return roundToUint8Sat(v[0]) | (roundToUint8Sat(v[1]) << 8) | (roundToUint8Sat(v[2]) << 16) | (roundToUint8Sat(v[3]) << 24); 71} 72 73template<int NumChannels> 74static inline deUint32 readUnorm8 (const tcu::ConstPixelBufferAccess& src, int x, int y) 75{ 76 const deUint8* ptr = (const deUint8*)src.getDataPtr() + src.getRowPitch()*y + x*NumChannels; 77 deUint32 v = 0; 78 79 for (int c = 0; c < NumChannels; c++) 80 v |= ptr[c] << (c*8); 81 82 if (NumChannels < 4) 83 v |= 0xffu << 24; 84 85 return v; 86} 87 88#if (DE_ENDIANNESS == DE_LITTLE_ENDIAN) 89template<> 90inline deUint32 readUnorm8<4> (const tcu::ConstPixelBufferAccess& src, int x, int y) 91{ 92 return *(const deUint32*)((const deUint8*)src.getDataPtr() + src.getRowPitch()*y + x*4); 93} 94#endif 95 96template<int NumChannels> 97static inline void writeUnorm8 (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val) 98{ 99 deUint8* ptr = (deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*NumChannels; 100 101 for (int c = 0; c < NumChannels; c++) 102 ptr[c] = getChannel(val, c); 103} 104 105#if (DE_ENDIANNESS == DE_LITTLE_ENDIAN) 106template<> 107inline void writeUnorm8<4> (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val) 108{ 109 *(deUint32*)((deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*4) = val; 110} 111#endif 112 113static inline deUint32 colorDistSquared (deUint32 pa, deUint32 pb) 114{ 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); 119 120 return deUint32(r*r + g*g + b*b + a*a); 121} 122 123template<int NumChannels> 124inline deUint32 bilinearSample (const ConstPixelBufferAccess& src, float u, float v) 125{ 126 int w = src.getWidth(); 127 int h = src.getHeight(); 128 129 int x0 = deFloorFloatToInt32(u-0.5f); 130 int x1 = x0+1; 131 int y0 = deFloorFloatToInt32(v-0.5f); 132 int y1 = y0+1; 133 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); 138 139 float a = deFloatFrac(u-0.5f); 140 float b = deFloatFrac(v-0.5f); 141 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); 146 deUint32 dst = 0; 147 148 // Interpolate. 149 for (int c = 0; c < NumChannels; c++) 150 { 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)); 156 } 157 158 return dst; 159} 160 161template<int DstChannels, int SrcChannels> 162static void separableConvolve (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, int shiftX, int shiftY, const std::vector<float>& kernelX, const std::vector<float>& kernelY) 163{ 164 DE_ASSERT(dst.getWidth() == src.getWidth() && dst.getHeight() == src.getHeight()); 165 166 TextureLevel tmp (dst.getFormat(), dst.getHeight(), dst.getWidth()); 167 PixelBufferAccess tmpAccess = tmp.getAccess(); 168 169 int kw = (int)kernelX.size(); 170 int kh = (int)kernelY.size(); 171 172 // Horizontal pass 173 // \note Temporary surface is written in column-wise order 174 for (int j = 0; j < src.getHeight(); j++) 175 { 176 for (int i = 0; i < src.getWidth(); i++) 177 { 178 Vec4 sum(0); 179 180 for (int kx = 0; kx < kw; kx++) 181 { 182 float f = kernelX[kw-kx-1]; 183 deUint32 p = readUnorm8<SrcChannels>(src, de::clamp(i+kx-shiftX, 0, src.getWidth()-1), j); 184 185 sum += toFloatVec(p)*f; 186 } 187 188 writeUnorm8<DstChannels>(tmpAccess, j, i, toColor(sum)); 189 } 190 } 191 192 // Vertical pass 193 for (int j = 0; j < src.getHeight(); j++) 194 { 195 for (int i = 0; i < src.getWidth(); i++) 196 { 197 Vec4 sum(0.0f); 198 199 for (int ky = 0; ky < kh; ky++) 200 { 201 float f = kernelY[kh-ky-1]; 202 deUint32 p = readUnorm8<DstChannels>(tmpAccess, de::clamp(j+ky-shiftY, 0, tmp.getWidth()-1), i); 203 204 sum += toFloatVec(p)*f; 205 } 206 207 writeUnorm8<DstChannels>(dst, i, j, toColor(sum)); 208 } 209 } 210} 211 212template<int NumChannels> 213static deUint32 distSquaredToNeighbor (de::Random& rnd, deUint32 pixel, const ConstPixelBufferAccess& surface, int x, int y) 214{ 215 // (x, y) + (0, 0) 216 deUint32 minDist = colorDistSquared(pixel, readUnorm8<NumChannels>(surface, x, y)); 217 218 if (minDist == 0) 219 return minDist; 220 221 // Area around (x, y) 222 static const int s_coords[8][2] = 223 { 224 {-1, -1}, 225 { 0, -1}, 226 {+1, -1}, 227 {-1, 0}, 228 {+1, 0}, 229 {-1, +1}, 230 { 0, +1}, 231 {+1, +1} 232 }; 233 234 for (int d = 0; d < (int)DE_LENGTH_OF_ARRAY(s_coords); d++) 235 { 236 int dx = x + s_coords[d][0]; 237 int dy = y + s_coords[d][1]; 238 239 if (!deInBounds32(dx, 0, surface.getWidth()) || !deInBounds32(dy, 0, surface.getHeight())) 240 continue; 241 242 minDist = de::min(minDist, colorDistSquared(pixel, readUnorm8<NumChannels>(surface, dx, dy))); 243 if (minDist == 0) 244 return minDist; 245 } 246 247 // Random bilinear-interpolated samples around (x, y) 248 for (int s = 0; s < 32; s++) 249 { 250 float dx = (float)x + rnd.getFloat()*2.0f - 0.5f; 251 float dy = (float)y + rnd.getFloat()*2.0f - 0.5f; 252 253 deUint32 sample = bilinearSample<NumChannels>(surface, dx, dy); 254 255 minDist = de::min(minDist, colorDistSquared(pixel, sample)); 256 if (minDist == 0) 257 return minDist; 258 } 259 260 return minDist; 261} 262 263static inline float toGrayscale (const Vec4& c) 264{ 265 return 0.2126f*c[0] + 0.7152f*c[1] + 0.0722f*c[2]; 266} 267 268static bool isFormatSupported (const TextureFormat& format) 269{ 270 return format.type == TextureFormat::UNORM_INT8 && (format.order == TextureFormat::RGB || format.order == TextureFormat::RGBA); 271} 272 273float fuzzyCompare (const FuzzyCompareParams& params, const ConstPixelBufferAccess& ref, const ConstPixelBufferAccess& cmp, const PixelBufferAccess& errorMask) 274{ 275 DE_ASSERT(ref.getWidth() == cmp.getWidth() && ref.getHeight() == cmp.getHeight()); 276 DE_ASSERT(errorMask.getWidth() == ref.getWidth() && errorMask.getHeight() == ref.getHeight()); 277 278 if (!isFormatSupported(ref.getFormat()) || !isFormatSupported(cmp.getFormat())) 279 throw InternalError("Unsupported format in fuzzy comparison", DE_NULL, __FILE__, __LINE__); 280 281 int width = ref.getWidth(); 282 int height = ref.getHeight(); 283 de::Random rnd (667); 284 285 // Filtered 286 TextureLevel refFiltered(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), width, height); 287 TextureLevel cmpFiltered(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), width, height); 288 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; 293 294 switch (ref.getFormat().order) 295 { 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; 298 default: 299 DE_ASSERT(DE_FALSE); 300 } 301 302 switch (cmp.getFormat().order) 303 { 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; 306 default: 307 DE_ASSERT(DE_FALSE); 308 } 309 310 int numSamples = 0; 311 deUint64 distSum4 = 0ull; 312 313 // Clear error mask to green. 314 clear(errorMask, Vec4(0.0f, 1.0f, 0.0f, 1.0f)); 315 316 ConstPixelBufferAccess refAccess = refFiltered.getAccess(); 317 ConstPixelBufferAccess cmpAccess = cmpFiltered.getAccess(); 318 319 for (int y = 1; y < height-1; y++) 320 { 321 for (int x = 1; x < width-1; x += params.maxSampleSkip > 0 ? (int)rnd.getInt(1, params.maxSampleSkip) : 1) 322 { 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; 327 328 distSum4 = (newSum4 >= distSum4) ? newSum4 : ~0ull; // In case of overflow 329 numSamples += 1; 330 331 // Build error image. 332 { 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; 339 340 errorMask.setPixel(Vec4(red*rF, (1.0f-red)*rF, 0.0f, 1.0f), x, y); 341 } 342 } 343 } 344 345 { 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; 350 351 return float(double(distSum4) / double(colScale4) * pSamples); 352 } 353} 354 355} // tcu 356