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