OXYGENHUB
◆ main openmsx / src / video / PixelOperations.hh
10.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef PIXELOPERATIONS_HH
2
#define PIXELOPERATIONS_HH
3
4
#include <bit>
5
#include <cstdint>
6
#include <span>
7
8
namespace openmsx {
9
10
using Pixel = uint32_t;
11
12
class PixelOperations
13
{
14
public:
15
	[[nodiscard]] Pixel getRmask() const { return 0x000000FF; }
16
	[[nodiscard]] Pixel getGmask() const { return 0x0000FF00; }
17
	[[nodiscard]] Pixel getBmask() const { return 0x00FF0000; }
18
	[[nodiscard]] Pixel getAmask() const { return 0xFF000000; }
19
20
	/** Extract RGBA components, each in range [0..255]
21
	  */
22
	[[nodiscard]] unsigned red(Pixel p) const;
23
	[[nodiscard]] unsigned green(Pixel p) const;
24
	[[nodiscard]] unsigned blue(Pixel p) const;
25
	[[nodiscard]] unsigned alpha(Pixel p) const;
26
27
	/** Combine RGB components to a pixel
28
	  */
29
	[[nodiscard]] Pixel combine(unsigned r, unsigned g, unsigned b) const;
30
31
	/** Blend the given colors into a single color.
32
	  * The special case for blending between two colors with
33
	  * an equal blend weight has an optimized implementation.
34
	  */
35
	template<unsigned w1, unsigned w2>
36
	[[nodiscard]] Pixel blend(Pixel p1, Pixel p2) const;
37
	template<unsigned w1, unsigned w2, unsigned w3>
38
	[[nodiscard]] Pixel blend(Pixel p1, Pixel p2, Pixel p3) const;
39
	template<unsigned w1, unsigned w2, unsigned w3, unsigned w4>
40
	[[nodiscard]] Pixel blend(Pixel p1, Pixel p2, Pixel p3, Pixel p4) const;
41
	template<unsigned w1, unsigned w2, unsigned w3,
42
	         unsigned w4, unsigned w5, unsigned w6>
43
	[[nodiscard]] Pixel blend(Pixel p1, Pixel p2, Pixel p3,
44
	                          Pixel p4, Pixel p5, Pixel p6) const;
45
46
	template<unsigned w1, unsigned w2>
47
	[[nodiscard]] Pixel blend(std::span<const Pixel, 2> p) const;
48
	template<unsigned w1, unsigned w2, unsigned w3>
49
	[[nodiscard]] Pixel blend(std::span<const Pixel, 3> p) const;
50
	template<unsigned w1, unsigned w2, unsigned w3, unsigned w4>
51
	[[nodiscard]] Pixel blend(std::span<const Pixel, 4> p) const;
52
	template<unsigned w1, unsigned w2, unsigned w3,
53
	         unsigned w4, unsigned w5, unsigned w6>
54
	[[nodiscard]] Pixel blend(std::span<const Pixel, 6> p) const;
55
56
	/** Perform a component wise multiplication of a pixel with an 8-bit
57
	  * fractional value:
58
	  *     result = (pixel * x) / 256  [component wise]
59
	  * 'x' must be in range [0..256].
60
	  * For x=0   the result is 0.
61
	  * For x=255 the result in the original value.
62
	  * Note: ATM only implemented for 32bpp.
63
	  */
64
	[[nodiscard]] static Pixel multiply(Pixel p, unsigned x);
65
66
	/** Perform linear interpolation between two pixels.
67
	 * This calculates component-wise:
68
	 *   (c1 * (256 - x) + c2 * x) / 256
69
	 * with c1, c2 the R,B,G,A components of the pixel.
70
	 * 'x' must be in range [0..256].
71
	 * For x=0   the result is p1.
72
	 * For x=256 the result is p2.
73
	 */
74
	[[nodiscard]] Pixel lerp(Pixel p1, Pixel p2, unsigned x) const;
75
76
	/** Perform alpha blending of two pixels.
77
	 * Pixel p1 contains the alpha value. For maximal alpha p1 is
78
	 * returned, for minimal alpha p2.
79
	 */
80
	[[nodiscard]] Pixel alphaBlend(Pixel p1, Pixel p2) const;
81
82
private:
83
	[[nodiscard]] Pixel avgDown(Pixel p1, Pixel p2) const;
84
	[[nodiscard]] Pixel avgUp  (Pixel p1, Pixel p2) const;
85
};
86
87
88
inline unsigned PixelOperations::red(Pixel p) const
89
{
90
	return (p >> 0) & 0xFF;
91
}
92
inline unsigned PixelOperations::green(Pixel p) const
93
{
94
	return (p >> 8) & 0xFF;
95
}
96
inline unsigned PixelOperations::blue(Pixel p) const
97
{
98
	return (p >> 16) & 0xFF;
99
}
100
inline unsigned PixelOperations::alpha(Pixel p) const
101
{
102
	return (p >> 24) & 0xFF;
103
}
104
105
inline Pixel PixelOperations::combine(
106
		unsigned r, unsigned g, unsigned b) const
107
{
108
	return Pixel((r << 0) | (g << 8) | (b << 16) | (0xFF << 24));
109
}
110
111
inline Pixel PixelOperations::avgDown(Pixel p1, Pixel p2) const
112
{
113
	// Average can be calculated as:
114
	//    floor((x + y) / 2.0) = (x & y) + (x ^ y) / 2
115
	// see "Average of Integers" on http://aggregate.org/MAGIC/
116
	return (p1 & p2) + (((p1 ^ p2) & 0xFEFEFEFE) >> 1);
117
}
118
inline Pixel PixelOperations::avgUp(Pixel p1, Pixel p2) const
119
{
120
	// Similar to above, but rounds up
121
	//    ceil((x + y) / 2.0) = (x | y) - (x ^ y) / 2
122
	return (p1 | p2) - (((p1 ^ p2) & 0xFEFEFEFE) >> 1);
123
}
124
125
template<unsigned w1, unsigned w2>
126
inline Pixel PixelOperations::blend(Pixel p1, Pixel p2) const
127
{
128
	constexpr unsigned total = w1 + w2;
129
	if constexpr (w1 == 0) {
130
		return p2;
131
	} else if constexpr (w1 > w2) {
132
		return blend<w2, w1>(p2, p1);
133
134
	} else if constexpr (w1 == w2) {
135
		// <1,1>
136
		return avgDown(p1, p2);
137
	} else if constexpr ((3 * w1) == w2) {
138
		// <1,3>
139
		Pixel p11 = avgDown(p1, p2);
140
		return avgUp(p11, p2);
141
	} else if constexpr ((7 * w1) == w2) {
142
		// <1,7>
143
		Pixel p11 = avgDown(p1, p2);
144
		Pixel p13 = avgDown(p11, p2);
145
		return avgUp(p13, p2);
146
	} else if constexpr ((5 * w1) == (3 * w2)) {
147
		// <3,5>   mix rounding up/down to get a more accurate result
148
		Pixel p11 = avgUp  (p1, p2);
149
		Pixel p13 = avgDown(p11, p2);
150
		return avgDown(p11, p13);
151
152
	} else if constexpr (!std::has_single_bit(total)) {
153
		// approximate with weights that sum to 256
154
		// e.g. approximate <1,2> as <85,171> (or <21,43>)
155
		//  ww1 = round(256 * w1 / total)   ww2 = 256 - ww1
156
		constexpr unsigned newTotal = 256;
157
		constexpr unsigned ww1 = (2 * w1 * newTotal + total) / (2 * total);
158
		constexpr unsigned ww2 = 256 - ww1;
159
		return blend<ww1, ww2>(p1, p2);
160
161
	} else {
162
		// 32bpp
163
		constexpr unsigned l2 = std::bit_width(total) - 1;
164
		unsigned c1 = (((p1 & 0x00FF00FF) * w1 +
165
				(p2 & 0x00FF00FF) * w2
166
			       ) >> l2) & 0x00FF00FF;
167
		unsigned c2 = (((p1 & 0xFF00FF00) >> l2) * w1 +
168
			       ((p2 & 0xFF00FF00) >> l2) * w2
169
			      ) & 0xFF00FF00;
170
		return c1 | c2;
171
	}
172
}
173
174
template<unsigned w1, unsigned w2, unsigned w3>
175
inline Pixel PixelOperations::blend(Pixel p1, Pixel p2, Pixel p3) const
176
{
177
	constexpr unsigned total = w1 + w2 + w3;
178
	if constexpr (std::has_single_bit(total)) {
179
		constexpr unsigned l2 = std::bit_width(total) - 1;
180
		unsigned c1 = (((p1 & 0x00FF00FF) * w1 +
181
		                (p2 & 0x00FF00FF) * w2 +
182
		                (p3 & 0x00FF00FF) * w3) >> l2) & 0x00FF00FF;
183
		unsigned c2 = (((p1 & 0xFF00FF00) >> l2) * w1 +
184
		               ((p2 & 0xFF00FF00) >> l2) * w2 +
185
		               ((p3 & 0xFF00FF00) >> l2) * w3) & 0xFF00FF00;
186
		return c1 | c2;
187
	} else {
188
		unsigned r = (red  (p1) * w1 + red  (p2) * w2 + red  (p3) * w3) / total;
189
		unsigned g = (green(p1) * w1 + green(p2) * w2 + green(p3) * w3) / total;
190
		unsigned b = (blue (p1) * w1 + blue (p2) * w2 + blue (p3) * w3) / total;
191
		return combine(r, g, b);
192
	}
193
}
194
195
template<unsigned w1, unsigned w2, unsigned w3, unsigned w4>
196
inline Pixel PixelOperations::blend(
197
		Pixel p1, Pixel p2, Pixel p3, Pixel p4) const
198
{
199
	constexpr unsigned total = w1 + w2 + w3 + w4;
200
	if constexpr (std::has_single_bit(total)) {
201
		constexpr unsigned l2 = std::bit_width(total) - 1;
202
		unsigned c1 = (((p1 & 0x00FF00FF) * w1 +
203
		                (p2 & 0x00FF00FF) * w2 +
204
		                (p3 & 0x00FF00FF) * w3 +
205
		                (p4 & 0x00FF00FF) * w4) >> l2) & 0x00FF00FF;
206
		unsigned c2 = (((p1 & 0xFF00FF00) >> l2) * w1 +
207
		               ((p2 & 0xFF00FF00) >> l2) * w2 +
208
		               ((p3 & 0xFF00FF00) >> l2) * w3 +
209
		               ((p4 & 0xFF00FF00) >> l2) * w4) & 0xFF00FF00;
210
		return c1 | c2;
211
	} else {
212
		unsigned r = (red  (p1) * w1 + red  (p2) * w2 +
213
			      red  (p3) * w3 + red  (p4) * w4) / total;
214
		unsigned g = (green(p1) * w1 + green(p2) * w2 +
215
			      green(p3) * w3 + green(p4) * w4) / total;
216
		unsigned b = (blue (p1) * w1 + blue (p2) * w2 +
217
			      blue (p3) * w3 + blue (p4) * w4) / total;
218
		return combine(r, g, b);
219
	}
220
}
221
222
template<unsigned w1, unsigned w2, unsigned w3,
223
          unsigned w4, unsigned w5, unsigned w6>
224
inline Pixel PixelOperations::blend(
225
	Pixel p1, Pixel p2, Pixel p3, Pixel p4, Pixel p5, Pixel p6) const
226
{
227
	constexpr unsigned total = w1 + w2 + w3 + w4 + w5 + w6;
228
	if constexpr (std::has_single_bit(total)) {
229
		constexpr unsigned l2 = std::bit_width(total) - 1;
230
		unsigned c1 = (((p1 & 0x00FF00FF) * w1 +
231
		                (p2 & 0x00FF00FF) * w2 +
232
		                (p3 & 0x00FF00FF) * w3 +
233
		                (p4 & 0x00FF00FF) * w4 +
234
		                (p5 & 0x00FF00FF) * w5 +
235
		                (p6 & 0x00FF00FF) * w6) >> l2) & 0x00FF00FF;
236
		unsigned c2 = (((p1 & 0xFF00FF00) >> l2) * w1 +
237
		               ((p2 & 0xFF00FF00) >> l2) * w2 +
238
		               ((p3 & 0xFF00FF00) >> l2) * w3 +
239
		               ((p4 & 0xFF00FF00) >> l2) * w4 +
240
		               ((p5 & 0xFF00FF00) >> l2) * w5 +
241
		               ((p6 & 0xFF00FF00) >> l2) * w6) & 0xFF00FF00;
242
		return c1 | c2;
243
	} else {
244
		unsigned r = (red  (p1) * w1 + red  (p2) * w2 +
245
		              red  (p3) * w3 + red  (p4) * w4 +
246
		              red  (p5) * w5 + red  (p6) * w6) / total;
247
		unsigned g = (green(p1) * w1 + green(p2) * w2 +
248
		              green(p3) * w3 + green(p4) * w4 +
249
		              green(p5) * w5 + green(p6) * w6) / total;
250
		unsigned b = (blue (p1) * w1 + blue (p2) * w2 +
251
		              blue (p3) * w3 + blue (p4) * w4 +
252
		              blue (p5) * w5 + blue (p6) * w6) / total;
253
		return combine(r, g, b);
254
	}
255
}
256
257
258
template<unsigned w1, unsigned w2>
259
inline Pixel PixelOperations::blend(std::span<const Pixel, 2> p) const
260
{
261
	return blend<w1, w2>(p[0], p[1]);
262
}
263
264
template<unsigned w1, unsigned w2, unsigned w3>
265
inline Pixel PixelOperations::blend(std::span<const Pixel, 3> p) const
266
{
267
	return blend<w1, w2, w3>(p[0], p[1], p[2]);
268
}
269
270
template<unsigned w1, unsigned w2, unsigned w3, unsigned w4>
271
inline Pixel PixelOperations::blend(std::span<const Pixel, 4> p) const
272
{
273
	return blend<w1, w2, w3, w4>(p[0], p[1], p[2], p[3]);
274
}
275
276
template<unsigned w1, unsigned w2, unsigned w3,
277
          unsigned w4, unsigned w5, unsigned w6>
278
inline Pixel PixelOperations::blend(std::span<const Pixel, 6> p) const
279
{
280
	return blend<w1, w2, w3, w4, w5, w6>(p[0], p[1], p[2], p[3], p[4], p[5]);
281
}
282
283
inline Pixel PixelOperations::multiply(Pixel p, unsigned x)
284
{
285
	return ((((p       & 0x00FF00FF) * x) & 0xFF00FF00) >> 8)
286
	     | ((((p >> 8) & 0x00FF00FF) * x) & 0xFF00FF00);
287
}
288
289
inline Pixel PixelOperations::lerp(Pixel p1, Pixel p2, unsigned x) const
290
{
291
	// 32bpp
292
	unsigned rb1 = (p1 >> 0) & 0x00FF00FF;
293
	unsigned ag1 = (p1 >> 8) & 0x00FF00FF;
294
	unsigned rb2 = (p2 >> 0) & 0x00FF00FF;
295
	unsigned ag2 = (p2 >> 8) & 0x00FF00FF;
296
297
	// Note: the subtraction for the lower component can 'borrow' from
298
	// the higher component. Though in the full calculation this error
299
	// magically cancels out.
300
	unsigned trb = ((rb2 - rb1) * x) >> 8;
301
	unsigned tag = ((ag2 - ag1) * x) >> 0;
302
303
	unsigned rb  = ((trb + rb1) << 0) & 0x00FF00FF;
304
	unsigned ag  = (tag + (ag1 << 8)) & 0xFF00FF00;
305
306
	return rb | ag;
307
}
308
309
inline Pixel PixelOperations::alphaBlend(Pixel p1, Pixel p2) const
310
{
311
	unsigned a = alpha(p1);
312
	// Note: 'a' is [0..255], while lerp() expects [0..256].
313
	//       We ignore this small error.
314
	return lerp(p2, p1, a);
315
}
316
317
} // namespace openmsx
318
319
#endif