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