openMSX
MLAAScaler.cc
Go to the documentation of this file.
1#include "MLAAScaler.hh"
2#include "FrameSource.hh"
3#include "ScalerOutput.hh"
4#include "MemBuffer.hh"
5#include "aligned.hh"
6#include "narrow.hh"
7#include "vla.hh"
8#include "build-info.hh"
9#include <algorithm>
10#include <vector>
11#include <cassert>
12#include <cstdint>
13
14namespace openmsx {
15
16template<std::unsigned_integral Pixel>
18 unsigned dstWidth_, const PixelOperations<Pixel>& pixelOps_)
19 : pixelOps(pixelOps_)
20 , dstWidth(dstWidth_)
21{
22}
23
24template<std::unsigned_integral Pixel>
26 FrameSource& src, const RawFrame* superImpose,
27 unsigned srcStartY, unsigned srcEndY, unsigned srcWidth,
28 ScalerOutput<Pixel>& dst, unsigned dstStartY, unsigned dstEndY)
29{
30 (void)superImpose; // TODO: Support superimpose.
31 //fprintf(stderr, "scale line [%d..%d) to [%d..%d), width %d to %d\n",
32 // srcStartY, srcEndY, dstStartY, dstEndY, srcWidth, dstWidth);
33
34 // TODO: Support non-integer zoom factors.
35 const unsigned zoomFactorX = dstWidth / srcWidth;
36 const unsigned zoomFactorY = (dstEndY - dstStartY) / (srcEndY - srcStartY);
37 const auto zoomFactorXF = narrow<float>(zoomFactorX);
38 const auto zoomFactorYF = narrow<float>(zoomFactorY);
39
40 // Retrieve line pointers.
41 // We allow lookups one line before and after the scaled area.
42 // This is not just a trick to avoid range checks: to properly handle
43 // pixels at the top/bottom of the display area we must compare them to
44 // the border color.
45 const int srcNumLines = narrow<int>(srcEndY - srcStartY);
46 VLA(const Pixel*, srcLinePtrsArray, srcNumLines + 2);
47 auto** srcLinePtrs = &srcLinePtrsArray[1];
48 std::vector<MemBuffer<Pixel, SSE_ALIGNMENT>> workBuffer;
49 const Pixel* linePtr = nullptr;
50 Pixel* work = nullptr;
51 for (auto y : xrange(-1, srcNumLines + 1)) {
52 if (linePtr == work) {
53 // Allocate new workBuffer when needed
54 // e.g. when used in previous iteration
55 work = workBuffer.emplace_back(srcWidth).data();
56 }
57 auto line = src.getLine(srcStartY + y, std::span{work, srcWidth});
58 linePtr = line.data();
59 srcLinePtrs[y] = linePtr;
60 }
61
62 enum { UP = 1 << 0, RIGHT = 1 << 1, DOWN = 1 << 2, LEFT = 1 << 3 };
63 MemBuffer<uint8_t> edges(srcNumLines * size_t(srcWidth));
64 uint8_t* edgeGenPtr = edges.data();
65 for (auto y : xrange(srcNumLines)) {
66 auto* srcLinePtr = srcLinePtrs[y];
67 for (auto x : xrange(srcWidth)) {
68 Pixel colMid = srcLinePtr[x];
69 uint8_t pixEdges = 0;
70 if (x > 0 && srcLinePtr[x - 1] != colMid) {
71 pixEdges |= LEFT;
72 }
73 if (x < srcWidth - 1 && srcLinePtr[x + 1] != colMid) {
74 pixEdges |= RIGHT;
75 }
76 if (srcLinePtrs[y - 1][x] != colMid) {
77 pixEdges |= UP;
78 }
79 if (srcLinePtrs[y + 1][x] != colMid) {
80 pixEdges |= DOWN;
81 }
82 *edgeGenPtr++ = pixEdges;
83 }
84 }
85
86 enum {
87 // Is this pixel part of an edge?
88 // And if so, where on the edge is it?
89 EDGE_START = 3 << 28,
90 EDGE_END = 2 << 28,
91 EDGE_INNER = 1 << 28,
92 EDGE_NONE = 0 << 28,
93 EDGE_MASK = 3 << 28,
94 // Is the edge is part of one or more slopes?
95 // And if so, what is the direction of the slope(s)?
96 SLOPE_TOP_LEFT = 1 << 27,
97 SLOPE_TOP_RIGHT = 1 << 26,
98 SLOPE_BOT_LEFT = 1 << 25,
99 SLOPE_BOT_RIGHT = 1 << 24,
100 // How long is this edge?
101 // For the start and end, these bits contain the length.
102 // For inner pixels, these bits contain the distance to the start pixel.
103 SPAN_BITS = 12,
104 SPAN_SHIFT_1 = 0,
105 SPAN_SHIFT_2 = SPAN_SHIFT_1 + SPAN_BITS,
106 SPAN_MASK = (1 << SPAN_BITS) - 1,
107 };
108 assert(srcWidth <= SPAN_MASK);
109
110 // Find horizontal edges.
111 MemBuffer<unsigned> horizontals(srcNumLines * size_t(srcWidth));
112 unsigned* horizontalGenPtr = horizontals.data();
113 const uint8_t* edgePtr = edges.data();
114 for (auto y : xrange(srcNumLines)) {
115 unsigned x = 0;
116 while (x < srcWidth) {
117 // Check which corners are part of a slope.
118 bool slopeTopLeft = false;
119 bool slopeTopRight = false;
120 bool slopeBotLeft = false;
121 bool slopeBotRight = false;
122
123 // Search for slopes on the top edge.
124 unsigned topEndX = x + 1;
125 // TODO: Making the slopes end in the middle of the edge segment
126 // is simple but inaccurate. Can we do better?
127 // Four cases:
128 // -- no slope
129 // /- left slope
130 // -\ right slope
131 // /\ U-shape
132 if (edgePtr[x] & UP) {
133 while (topEndX < srcWidth
134 && (edgePtr[topEndX] & (UP | LEFT)) == UP) topEndX++;
135 slopeTopLeft = (edgePtr[x] & LEFT)
136 && srcLinePtrs[y + 1][x - 1] == srcLinePtrs[y][x]
137 && srcLinePtrs[y][x - 1] == srcLinePtrs[y - 1][x];
138 slopeTopRight = (edgePtr[topEndX - 1] & RIGHT)
139 && srcLinePtrs[y + 1][topEndX] == srcLinePtrs[y][topEndX - 1]
140 && srcLinePtrs[y][topEndX] == srcLinePtrs[y - 1][topEndX - 1];
141 }
142
143 // Search for slopes on the bottom edge.
144 unsigned botEndX = x + 1;
145 if (edgePtr[x] & DOWN) {
146 while (botEndX < srcWidth
147 && (edgePtr[botEndX] & (DOWN | LEFT)) == DOWN) botEndX++;
148 slopeBotLeft = (edgePtr[x] & LEFT)
149 && srcLinePtrs[y - 1][x - 1] == srcLinePtrs[y][x]
150 && srcLinePtrs[y][x - 1] == srcLinePtrs[y + 1][x];
151 slopeBotRight = (edgePtr[botEndX - 1] & RIGHT)
152 && srcLinePtrs[y - 1][botEndX] == srcLinePtrs[y][botEndX - 1]
153 && srcLinePtrs[y][botEndX] == srcLinePtrs[y + 1][botEndX - 1];
154 }
155
156 // Store info about edge and determine next pixel to check.
157 if (!(slopeTopLeft || slopeTopRight ||
158 slopeBotLeft || slopeBotRight)) {
159 *horizontalGenPtr++ = EDGE_NONE;
160 x++;
161 } else {
162 unsigned slopes =
163 (slopeTopLeft ? SLOPE_TOP_LEFT : 0)
164 | (slopeTopRight ? SLOPE_TOP_RIGHT : 0)
165 | (slopeBotLeft ? SLOPE_BOT_LEFT : 0)
166 | (slopeBotRight ? SLOPE_BOT_RIGHT : 0);
167 const unsigned lengths =
168 ((topEndX - x) << SPAN_SHIFT_1) |
169 ((botEndX - x) << SPAN_SHIFT_2);
170 // Determine edge start and end points.
171 assert(!slopeTopRight || !slopeBotRight || topEndX == botEndX);
172 const unsigned endX = slopeTopRight ? topEndX : (
173 slopeBotRight ? botEndX : std::max(topEndX, botEndX)
174 );
175 const unsigned length = endX - x;
176 if (length == 1) {
177 *horizontalGenPtr++ = EDGE_START | EDGE_END | slopes | lengths;
178 } else {
179 *horizontalGenPtr++ = EDGE_START | slopes | lengths;
180 for (auto i : xrange(1u, length - 1)) {
181 *horizontalGenPtr++ = EDGE_INNER | slopes | i;
182 }
183 *horizontalGenPtr++ = EDGE_END | slopes | lengths;
184 }
185 x = endX;
186 }
187 }
188 assert(x == srcWidth);
189 edgePtr += srcWidth;
190 }
191 assert(unsigned(edgePtr - edges.data()) == srcNumLines * srcWidth);
192 assert(unsigned(horizontalGenPtr - horizontals.data()) == srcNumLines * srcWidth);
193
194 // Find vertical edges.
195 MemBuffer<unsigned> verticals(srcNumLines * size_t(srcWidth));
196 edgePtr = edges.data();
197 for (auto x : xrange(srcWidth)) {
198 unsigned* verticalGenPtr = &verticals[x];
199 int y = 0;
200 while (y < srcNumLines) {
201 // Check which corners are part of a slope.
202 bool slopeTopLeft = false;
203 bool slopeTopRight = false;
204 bool slopeBotLeft = false;
205 bool slopeBotRight = false;
206
207 // Search for slopes on the left edge.
208 int leftEndY = y + 1;
209 if (edgePtr[y * size_t(srcWidth)] & LEFT) {
210 while (leftEndY < srcNumLines
211 && (edgePtr[leftEndY * size_t(srcWidth)] & (LEFT | UP)) == LEFT) leftEndY++;
212 assert(x > 0); // implied by having a left edge
213 const unsigned nextX = std::min(x + 1, srcWidth - 1);
214 slopeTopLeft = (edgePtr[y * size_t(srcWidth)] & UP)
215 && srcLinePtrs[y - 1][nextX] == srcLinePtrs[y][x]
216 && srcLinePtrs[y - 1][x] == srcLinePtrs[y][x - 1];
217 slopeBotLeft = (edgePtr[(leftEndY - 1) * size_t(srcWidth)] & DOWN)
218 && srcLinePtrs[leftEndY][nextX] == srcLinePtrs[leftEndY - 1][x]
219 && srcLinePtrs[leftEndY][x] == srcLinePtrs[leftEndY - 1][x - 1];
220 }
221
222 // Search for slopes on the right edge.
223 int rightEndY = y + 1;
224 if (edgePtr[y * size_t(srcWidth)] & RIGHT) {
225 while (rightEndY < srcNumLines
226 && (edgePtr[rightEndY * size_t(srcWidth)] & (RIGHT | UP)) == RIGHT) rightEndY++;
227 assert(x < srcWidth); // implied by having a right edge
228 const unsigned prevX = x == 0 ? 0 : x - 1;
229 slopeTopRight = (edgePtr[y * size_t(srcWidth)] & UP)
230 && srcLinePtrs[y - 1][prevX] == srcLinePtrs[y][x]
231 && srcLinePtrs[y - 1][x] == srcLinePtrs[y][x + 1];
232 slopeBotRight = (edgePtr[(rightEndY - 1) * size_t(srcWidth)] & DOWN)
233 && srcLinePtrs[rightEndY][prevX] == srcLinePtrs[rightEndY - 1][x]
234 && srcLinePtrs[rightEndY][x] == srcLinePtrs[rightEndY - 1][x + 1];
235 }
236
237 // Store info about edge and determine next pixel to check.
238 if (!(slopeTopLeft || slopeTopRight ||
239 slopeBotLeft || slopeBotRight)) {
240 *verticalGenPtr = EDGE_NONE;
241 verticalGenPtr += srcWidth;
242 y++;
243 } else {
244 unsigned slopes =
245 (slopeTopLeft ? SLOPE_TOP_LEFT : 0)
246 | (slopeTopRight ? SLOPE_TOP_RIGHT : 0)
247 | (slopeBotLeft ? SLOPE_BOT_LEFT : 0)
248 | (slopeBotRight ? SLOPE_BOT_RIGHT : 0);
249 const unsigned lengths =
250 ((leftEndY - y) << SPAN_SHIFT_1) |
251 ((rightEndY - y) << SPAN_SHIFT_2);
252 // Determine edge start and end points.
253 assert(!slopeBotLeft || !slopeBotRight || leftEndY == rightEndY);
254 const unsigned endY = slopeBotLeft ? leftEndY : (
255 slopeBotRight ? rightEndY : std::max(leftEndY, rightEndY)
256 );
257 const unsigned length = endY - y;
258 if (length == 1) {
259 *verticalGenPtr = EDGE_START | EDGE_END | slopes | lengths;
260 verticalGenPtr += srcWidth;
261 } else {
262 *verticalGenPtr = EDGE_START | slopes | lengths;
263 verticalGenPtr += srcWidth;
264 for (auto i : xrange(1u, length - 1)) {
265 // TODO: To be fully accurate we need to have separate
266 // start/stop points for the two possible edges
267 // of this pixel. No code uses the inner info yet
268 // though, so this can be fixed later.
269 *verticalGenPtr = EDGE_INNER | slopes | i;
270 verticalGenPtr += srcWidth;
271 }
272 *verticalGenPtr = EDGE_END | slopes | lengths;
273 verticalGenPtr += srcWidth;
274 }
275 y = narrow<int>(endY);
276 }
277 }
278 assert(y == srcNumLines);
279 assert(unsigned(verticalGenPtr - verticals.data()) == x + srcNumLines * srcWidth);
280 edgePtr++;
281 }
282 assert(unsigned(edgePtr - edges.data()) == srcWidth);
283
284 VLA(std::span<Pixel>, dstLines, dst.getHeight());
285 for (auto i : xrange(dstStartY, dstEndY)) {
286 dstLines[i] = dst.acquireLine(i);
287 }
288
289 // Do a mosaic scale so every destination pixel has a color.
290 unsigned dstY = dstStartY;
291 for (auto y : xrange(srcNumLines)) {
292 auto* srcLinePtr = srcLinePtrs[y];
293 for (auto x : xrange(srcWidth)) {
294 Pixel col = srcLinePtr[x];
295 for (auto iy : xrange(zoomFactorY)) {
296 for (auto ix : xrange(zoomFactorX)) {
297 dstLines[dstY + iy][x * zoomFactorX + ix] = col;
298 }
299 }
300 }
301 dstY += zoomFactorY;
302 }
303
304 // Render the horizontal edges.
305 const unsigned* horizontalPtr = horizontals.data();
306 dstY = dstStartY;
307 for (auto y : xrange(srcNumLines)) {
308 unsigned x = 0;
309 while (x < srcWidth) {
310 // Fetch information about the edge, if any, at the current pixel.
311 unsigned horzInfo = *horizontalPtr;
312 if ((horzInfo & EDGE_MASK) == EDGE_NONE) {
313 x++;
314 horizontalPtr++;
315 continue;
316 }
317 assert((horzInfo & EDGE_MASK) == EDGE_START);
318
319 // Check which corners are part of a slope.
320 bool slopeTopLeft = (horzInfo & SLOPE_TOP_LEFT ) != 0;
321 bool slopeTopRight = (horzInfo & SLOPE_TOP_RIGHT) != 0;
322 bool slopeBotLeft = (horzInfo & SLOPE_BOT_LEFT ) != 0;
323 bool slopeBotRight = (horzInfo & SLOPE_BOT_RIGHT) != 0;
324 const unsigned startX = x;
325 const unsigned topEndX =
326 startX + ((horzInfo >> SPAN_SHIFT_1) & SPAN_MASK);
327 const unsigned botEndX =
328 startX + ((horzInfo >> SPAN_SHIFT_2) & SPAN_MASK);
329 // Determine edge start and end points.
330 assert(!slopeTopRight || !slopeBotRight || topEndX == botEndX);
331 const unsigned endX = slopeTopRight ? topEndX : (
332 slopeBotRight ? botEndX : std::max(topEndX, botEndX)
333 );
334 x = endX;
335 horizontalPtr += endX - startX;
336
337 // Antialias either the top or the bottom, but not both.
338 // TODO: Figure out what the best way is to handle these situations.
339 if (slopeTopLeft && slopeBotLeft) {
340 slopeTopLeft = slopeBotLeft = false;
341 }
342 if (slopeTopRight && slopeBotRight) {
343 slopeTopRight = slopeBotRight = false;
344 }
345
346 // Render slopes.
347 auto* srcTopLinePtr = srcLinePtrs[y - 1];
348 auto* srcCurLinePtr = srcLinePtrs[y + 0];
349 auto* srcBotLinePtr = srcLinePtrs[y + 1];
350 const unsigned x0 = startX * 2 * zoomFactorX;
351 const unsigned x1 =
352 slopeTopLeft
353 ? (startX + topEndX) * zoomFactorX
354 : (slopeBotLeft
355 ? (startX + botEndX) * zoomFactorX
356 : x0);
357 const unsigned x3 = endX * 2 * zoomFactorX;
358 const unsigned x2 =
359 slopeTopRight
360 ? (startX + topEndX) * zoomFactorX
361 : (slopeBotRight
362 ? (startX + botEndX) * zoomFactorX
363 : x3);
364 for (auto iy : xrange(zoomFactorY)) {
365 auto iyF = narrow<float>(iy);
366 auto dstLinePtr = dstLines[dstY + iy];
367
368 // Figure out which parts of the line should be blended.
369 bool blendTopLeft = false;
370 bool blendTopRight = false;
371 bool blendBotLeft = false;
372 bool blendBotRight = false;
373 if (iy * 2 < zoomFactorY) {
374 blendTopLeft = slopeTopLeft;
375 blendTopRight = slopeTopRight;
376 }
377 if (iy * 2 + 1 >= zoomFactorY) {
378 blendBotLeft = slopeBotLeft;
379 blendBotRight = slopeBotRight;
380 }
381
382 // Render left side.
383 if (blendTopLeft || blendBotLeft) {
384 // TODO: This is implied by !(slopeTopLeft && slopeBotLeft),
385 // which is ensured by a temporary measure.
386 assert(!(blendTopLeft && blendBotLeft));
387 const Pixel* srcMixLinePtr = blendTopLeft
388 ? srcTopLinePtr
389 : srcBotLinePtr;
390 float lineY = blendTopLeft
391 ? ((zoomFactorYF - 1.0f - iyF) / zoomFactorYF)
392 : (iyF / zoomFactorYF);
393 for (unsigned fx = x0 | 1; fx < x1; fx += 2) {
394 float rx = narrow<float>(fx - x0) / narrow<float>(x1 - x0);
395 float ry = 0.5f + rx * 0.5f;
396 float weight = (ry - lineY) * zoomFactorYF;
397 dstLinePtr[fx / 2] = pixelOps.lerp(
398 srcMixLinePtr[fx / (zoomFactorX * 2)],
399 srcCurLinePtr[fx / (zoomFactorX * 2)],
400 std::clamp(int(256 * weight), 0, 256));
401 }
402 }
403
404 // Render right side.
405 if (blendTopRight || blendBotRight) {
406 // TODO: This is implied by !(slopeTopRight && slopeBotRight),
407 // which is ensured by a temporary measure.
408 assert(!(blendTopRight && blendBotRight));
409 const Pixel* srcMixLinePtr = blendTopRight
410 ? srcTopLinePtr
411 : srcBotLinePtr;
412 float lineY = blendTopRight
413 ? ((zoomFactorYF - 1.0f - iyF) / zoomFactorYF)
414 : (iyF / zoomFactorYF);
415 // TODO: The weight is slightly too high for the middle
416 // pixel when zoomFactorX is odd and we are rendering
417 // a U-shape.
418 for (unsigned fx = x2 | 1; fx < x3; fx += 2) {
419 float rx = narrow<float>(fx - x2) / narrow<float>(x3 - x2);
420 float ry = 1.0f - rx * 0.5f;
421 float weight = (ry - lineY) * zoomFactorYF;
422 dstLinePtr[fx / 2] = pixelOps.lerp(
423 srcMixLinePtr[fx / (zoomFactorX * 2)],
424 srcCurLinePtr[fx / (zoomFactorX * 2)],
425 std::clamp(int(256 * weight), 0, 256));
426 }
427 }
428
429 // Draw horizontal edge indicators.
430 if (false) {
431 if (iy == 0) {
432 if (slopeTopLeft) {
433 for (unsigned fx = x0 | 1; fx < x1; fx += 2) {
434 dstLinePtr[fx / 2] =
435 pixelOps.combine256(255, 0, 0);
436 }
437 }
438 if (slopeTopRight) {
439 for (unsigned fx = x2 | 1; fx < x3; fx += 2) {
440 dstLinePtr[fx / 2] =
441 pixelOps.combine256(0, 0, 255);
442 }
443 }
444 } else if (iy == zoomFactorY - 1) {
445 if (slopeBotLeft) {
446 for (unsigned fx = x0 | 1; fx < x1; fx += 2) {
447 dstLinePtr[fx / 2] =
448 pixelOps.combine256(255, 255, 0);
449 }
450 }
451 if (slopeBotRight) {
452 for (unsigned fx = x2 | 1; fx < x3; fx += 2) {
453 dstLinePtr[fx / 2] =
454 pixelOps.combine256(0, 255, 0);
455 }
456 }
457 }
458 }
459 }
460 }
461 assert(x == srcWidth);
462 dstY += zoomFactorY;
463 }
464 assert(unsigned(horizontalPtr - horizontals.data()) == srcNumLines * srcWidth);
465
466 // Render the vertical edges.
467 for (auto x : xrange(srcWidth)) {
468 const unsigned* verticalPtr = &verticals[x];
469 int y = 0;
470 while (y < srcNumLines) {
471 // Fetch information about the edge, if any, at the current pixel.
472 unsigned vertInfo = *verticalPtr;
473 if ((vertInfo & EDGE_MASK) == EDGE_NONE) {
474 y++;
475 verticalPtr += srcWidth;
476 continue;
477 }
478 assert((vertInfo & EDGE_MASK) == EDGE_START);
479
480 // Check which corners are part of a slope.
481 bool slopeTopLeft = (vertInfo & SLOPE_TOP_LEFT ) != 0;
482 bool slopeTopRight = (vertInfo & SLOPE_TOP_RIGHT) != 0;
483 bool slopeBotLeft = (vertInfo & SLOPE_BOT_LEFT ) != 0;
484 bool slopeBotRight = (vertInfo & SLOPE_BOT_RIGHT) != 0;
485 const unsigned startY = y;
486 const unsigned leftEndY =
487 startY + ((vertInfo >> SPAN_SHIFT_1) & SPAN_MASK);
488 const unsigned rightEndY =
489 startY + ((vertInfo >> SPAN_SHIFT_2) & SPAN_MASK);
490 // Determine edge start and end points.
491 assert(!slopeBotLeft || !slopeBotRight || leftEndY == rightEndY);
492 const unsigned endY = slopeBotLeft ? leftEndY : (
493 slopeBotRight ? rightEndY : std::max(leftEndY, rightEndY)
494 );
495 y = endY;
496 verticalPtr += size_t(srcWidth) * (endY - startY);
497
498 // Antialias either the left or the right, but not both.
499 if (slopeTopLeft && slopeTopRight) {
500 slopeTopLeft = slopeTopRight = false;
501 }
502 if (slopeBotLeft && slopeBotRight) {
503 slopeBotLeft = slopeBotRight = false;
504 }
505
506 // Render slopes.
507 const unsigned leftX = x == 0 ? 0 : x - 1;
508 const unsigned curX = x;
509 const unsigned rightX = std::min(x + 1, srcWidth - 1);
510 const unsigned y0 = startY * 2 * zoomFactorY;
511 const unsigned y1 =
512 slopeTopLeft
513 ? (startY + leftEndY) * zoomFactorY
514 : (slopeTopRight
515 ? (startY + rightEndY) * zoomFactorY
516 : y0);
517 const unsigned y3 = endY * 2 * zoomFactorY;
518 const unsigned y2 =
519 slopeBotLeft
520 ? (startY + leftEndY) * zoomFactorY
521 : (slopeBotRight
522 ? (startY + rightEndY) * zoomFactorY
523 : y3);
524 for (auto ix : xrange(zoomFactorX)) {
525 auto ixF = narrow<float>(ix);
526 const unsigned fx = x * zoomFactorX + ix;
527
528 // Figure out which parts of the line should be blended.
529 bool blendTopLeft = false;
530 bool blendTopRight = false;
531 bool blendBotLeft = false;
532 bool blendBotRight = false;
533 if (ix * 2 < zoomFactorX) {
534 blendTopLeft = slopeTopLeft;
535 blendBotLeft = slopeBotLeft;
536 }
537 if (ix * 2 + 1 >= zoomFactorX) {
538 blendTopRight = slopeTopRight;
539 blendBotRight = slopeBotRight;
540 }
541
542 // Render top side.
543 if (blendTopLeft || blendTopRight) {
544 assert(!(blendTopLeft && blendTopRight));
545 unsigned mixX = blendTopLeft ? leftX : rightX;
546 float lineX = blendTopLeft
547 ? ((zoomFactorXF - 1.0f - ixF) / zoomFactorXF)
548 : (ixF / zoomFactorXF);
549 for (unsigned fy = y0 | 1; fy < y1; fy += 2) {
550 auto dstLinePtr = dstLines[dstStartY + fy / 2];
551 float ry = narrow<float>(fy - y0) / narrow<float>(y1 - y0);
552 float rx = 0.5f + ry * 0.5f;
553 float weight = (rx - lineX) * zoomFactorXF;
554 dstLinePtr[fx] = pixelOps.lerp(
555 srcLinePtrs[fy / (zoomFactorY * 2)][mixX],
556 srcLinePtrs[fy / (zoomFactorY * 2)][curX],
557 std::clamp(int(256 * weight), 0, 256));
558 }
559 }
560
561 // Render bottom side.
562 if (blendBotLeft || blendBotRight) {
563 assert(!(blendBotLeft && blendBotRight));
564 unsigned mixX = blendBotLeft ? leftX : rightX;
565 float lineX = blendBotLeft
566 ? ((zoomFactorXF - 1.0f - ixF) / zoomFactorXF)
567 : (ixF / zoomFactorXF);
568 for (unsigned fy = y2 | 1; fy < y3; fy += 2) {
569 auto dstLinePtr = dstLines[dstStartY + fy / 2];
570 float ry = narrow<float>(fy - y2) / narrow<float>(y3 - y2);
571 float rx = 1.0f - ry * 0.5f;
572 float weight = (rx - lineX) * zoomFactorXF;
573 dstLinePtr[fx] = pixelOps.lerp(
574 srcLinePtrs[fy / (zoomFactorY * 2)][mixX],
575 srcLinePtrs[fy / (zoomFactorY * 2)][curX],
576 std::clamp(int(256 * weight), 0, 256));
577 }
578 }
579
580 // Draw vertical edge indicators.
581 if (false) {
582 if (ix == 0) {
583 if (slopeTopLeft) {
584 for (unsigned fy = y0 | 1; fy < y1; fy += 2) {
585 auto dstLinePtr = dstLines[
586 dstStartY + fy / 2];
587 dstLinePtr[fx] =
588 pixelOps.combine256(255, 0, 0);
589 }
590 }
591 if (slopeBotLeft) {
592 for (unsigned fy = y2 | 1; fy < y3; fy += 2) {
593 auto dstLinePtr = dstLines[
594 dstStartY + fy / 2];
595 dstLinePtr[fx] =
596 pixelOps.combine256(255, 255, 0);
597 }
598 }
599 } else if (ix == zoomFactorX - 1) {
600 if (slopeTopRight) {
601 for (unsigned fy = y0 | 1; fy < y1; fy += 2) {
602 auto dstLinePtr = dstLines[
603 dstStartY + fy / 2];
604 dstLinePtr[fx] =
605 pixelOps.combine256(0, 0, 255);
606 }
607 }
608 if (slopeBotRight) {
609 for (unsigned fy = y2 | 1; fy < y3; fy += 2) {
610 auto dstLinePtr = dstLines[
611 dstStartY + fy / 2];
612 dstLinePtr[fx] =
613 pixelOps.combine256(0, 255, 0);
614 }
615 }
616 }
617 }
618 }
619 }
620 assert(y == srcNumLines);
621 }
622
623 // TODO: This is compensation for the fact that we do not support
624 // non-integer zoom factors yet.
625 if (srcWidth * zoomFactorX != dstWidth) {
626 for (auto dy : xrange(dstStartY, dstY)) {
627 unsigned sy = std::min(
628 (dy - dstStartY) / zoomFactorY - srcStartY,
629 unsigned(srcNumLines)
630 );
631 Pixel col = srcLinePtrs[sy][srcWidth - 1];
632 for (auto dx : xrange(srcWidth * zoomFactorX, dstWidth)) {
633 dstLines[dy][dx] = col;
634 }
635 }
636 }
637 if (dstY != dstEndY) {
638 // Typically this will pick the border color, but there is no guarantee.
639 // However, we're inside a workaround anyway, so it's good enough.
640 Pixel col = srcLinePtrs[srcNumLines - 1][srcWidth - 1];
641 for (auto dy : xrange(dstY, dstEndY)) {
642 for (auto dx : xrange(dstWidth)) {
643 dstLines[dy][dx] = col;
644 }
645 }
646 }
647
648 for (auto i : xrange(dstStartY, dstEndY)) {
649 dst.releaseLine(i, dstLines[i]);
650 }
651}
652
653// Force template instantiation.
654#if HAVE_16BPP
655template class MLAAScaler<uint16_t>;
656#endif
657#if HAVE_32BPP
658template class MLAAScaler<uint32_t>;
659#endif
660
661} // namespace openmsx
Interface for getting lines from a video frame.
Definition: FrameSource.hh:20
std::span< const Pixel > getLine(int line, std::span< Pixel > buf) const
Gets a pointer to the pixels of the given line number.
Definition: FrameSource.hh:96
Scaler that uses a variation of the morphological anti-aliasing algorithm.
Definition: MLAAScaler.hh:20
void scaleImage(FrameSource &src, const RawFrame *superImpose, unsigned srcStartY, unsigned srcEndY, unsigned srcWidth, ScalerOutput< Pixel > &dst, unsigned dstStartY, unsigned dstEndY) override
Scales the image in the given area, which must consist of lines which are all equally wide.
Definition: MLAAScaler.cc:25
MLAAScaler(unsigned dstWidth, const PixelOperations< Pixel > &pixelOps)
Definition: MLAAScaler.cc:17
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Definition: RawFrame.hh:15
virtual unsigned getHeight() const =0
virtual void releaseLine(unsigned y, std::span< Pixel > buf)=0
virtual std::span< Pixel > acquireLine(unsigned y)=0
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:340
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:266
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:284
constexpr vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
Definition: gl_vec.hh:293
This file implemented 3 utility functions:
Definition: Autofire.cc:9
uint32_t Pixel
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:12
constexpr auto xrange(T e)
Definition: xrange.hh:133