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