Home | History | Annotate | Download | only in common
      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>
     38 static inline deUint8 getChannel (deUint32 color)
     39 {
     40 	return (deUint8)((color >> (Channel*8)) & 0xff);
     41 }
     42 
     43 static inline deUint8 getChannel (deUint32 color, int channel)
     44 {
     45 	return (deUint8)((color >> (channel*8)) & 0xff);
     46 }
     47 
     48 static inline deUint32 setChannel (deUint32 color, int channel, deUint8 val)
     49 {
     50 	return (color & ~(0xffu << (8*channel))) | (val << (8*channel));
     51 }
     52 
     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 
     58 static inline deUint8 roundToUint8Sat (float v)
     59 {
     60 	return (deUint8)de::clamp((int)(v + 0.5f), 0, 255);
     61 }
     62 
     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>
     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<>
     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>
     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<>
    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 
    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>
    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>
    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>
    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 
    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 
    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 
    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 			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