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 "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, SSE_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 = blendTopLeft
385  ? srcTopLinePtr
386  : srcBotLinePtr;
387  float lineY = blendTopLeft
388  ? ((zoomFactorY - 1 - iy) / float(zoomFactorY))
389  : (iy / float(zoomFactorY));
390  for (unsigned fx = x0 | 1; fx < x1; fx += 2) {
391  float rx = (fx - x0) / float(x1 - x0);
392  float ry = 0.5f + rx * 0.5f;
393  float weight = (ry - lineY) * zoomFactorY;
394  dstLinePtr[fx / 2] = pixelOps.lerp(
395  srcMixLinePtr[fx / (zoomFactorX * 2)],
396  srcCurLinePtr[fx / (zoomFactorX * 2)],
397  std::clamp(int(256 * weight), 0, 256));
398  }
399  }
400 
401  // Render right side.
402  if (blendTopRight || blendBotRight) {
403  // TODO: This is implied by !(slopeTopRight && slopeBotRight),
404  // which is ensured by a temporary measure.
405  assert(!(blendTopRight && blendBotRight));
406  const Pixel* srcMixLinePtr = blendTopRight
407  ? srcTopLinePtr
408  : srcBotLinePtr;
409  float lineY = blendTopRight
410  ? ((zoomFactorY - 1 - iy) / float(zoomFactorY))
411  : (iy / float(zoomFactorY));
412  // TODO: The weight is slightly too high for the middle
413  // pixel when zoomFactorX is odd and we are rendering
414  // a U-shape.
415  for (unsigned fx = x2 | 1; fx < x3; fx += 2) {
416  float rx = (fx - x2) / float(x3 - x2);
417  float ry = 1.0f - rx * 0.5f;
418  float weight = (ry - lineY) * zoomFactorY;
419  dstLinePtr[fx / 2] = pixelOps.lerp(
420  srcMixLinePtr[fx / (zoomFactorX * 2)],
421  srcCurLinePtr[fx / (zoomFactorX * 2)],
422  std::clamp(int(256 * weight), 0, 256));
423  }
424  }
425 
426  // Draw horizontal edge indicators.
427  if (false) {
428  if (iy == 0) {
429  if (slopeTopLeft) {
430  for (unsigned fx = x0 | 1; fx < x1; fx += 2) {
431  dstLinePtr[fx / 2] =
432  pixelOps.combine256(255, 0, 0);
433  }
434  }
435  if (slopeTopRight) {
436  for (unsigned fx = x2 | 1; fx < x3; fx += 2) {
437  dstLinePtr[fx / 2] =
438  pixelOps.combine256(0, 0, 255);
439  }
440  }
441  } else if (iy == zoomFactorY - 1) {
442  if (slopeBotLeft) {
443  for (unsigned fx = x0 | 1; fx < x1; fx += 2) {
444  dstLinePtr[fx / 2] =
445  pixelOps.combine256(255, 255, 0);
446  }
447  }
448  if (slopeBotRight) {
449  for (unsigned fx = x2 | 1; fx < x3; fx += 2) {
450  dstLinePtr[fx / 2] =
451  pixelOps.combine256(0, 255, 0);
452  }
453  }
454  }
455  }
456  }
457  }
458  assert(x == srcWidth);
459  dstY += zoomFactorY;
460  }
461  assert(unsigned(horizontalPtr - horizontals.data()) == srcNumLines * srcWidth);
462 
463  // Render the vertical edges.
464  for (unsigned x = 0; x < srcWidth; x++) {
465  const unsigned* verticalPtr = &verticals[x];
466  int y = 0;
467  while (y < srcNumLines) {
468  // Fetch information about the edge, if any, at the current pixel.
469  unsigned vertInfo = *verticalPtr;
470  if ((vertInfo & EDGE_MASK) == EDGE_NONE) {
471  y++;
472  verticalPtr += srcWidth;
473  continue;
474  }
475  assert((vertInfo & EDGE_MASK) == EDGE_START);
476 
477  // Check which corners are part of a slope.
478  bool slopeTopLeft = (vertInfo & SLOPE_TOP_LEFT ) != 0;
479  bool slopeTopRight = (vertInfo & SLOPE_TOP_RIGHT) != 0;
480  bool slopeBotLeft = (vertInfo & SLOPE_BOT_LEFT ) != 0;
481  bool slopeBotRight = (vertInfo & SLOPE_BOT_RIGHT) != 0;
482  const unsigned startY = y;
483  const unsigned leftEndY =
484  startY + ((vertInfo >> SPAN_SHIFT_1) & SPAN_MASK);
485  const unsigned rightEndY =
486  startY + ((vertInfo >> SPAN_SHIFT_2) & SPAN_MASK);
487  // Determine edge start and end points.
488  assert(!slopeBotLeft || !slopeBotRight || leftEndY == rightEndY);
489  const unsigned endY = slopeBotLeft ? leftEndY : (
490  slopeBotRight ? rightEndY : std::max(leftEndY, rightEndY)
491  );
492  y = endY;
493  verticalPtr += srcWidth * (endY - startY);
494 
495  // Antialias either the left or the right, but not both.
496  if (slopeTopLeft && slopeTopRight) {
497  slopeTopLeft = slopeTopRight = false;
498  }
499  if (slopeBotLeft && slopeBotRight) {
500  slopeBotLeft = slopeBotRight = false;
501  }
502 
503  // Render slopes.
504  const unsigned leftX = x == 0 ? 0 : x - 1;
505  const unsigned curX = x;
506  const unsigned rightX = std::min(x + 1, srcWidth - 1);
507  const unsigned y0 = startY * 2 * zoomFactorY;
508  const unsigned y1 =
509  slopeTopLeft
510  ? (startY + leftEndY) * zoomFactorY
511  : ( slopeTopRight
512  ? (startY + rightEndY) * zoomFactorY
513  : y0
514  );
515  const unsigned y3 = endY * 2 * zoomFactorY;
516  const unsigned y2 =
517  slopeBotLeft
518  ? (startY + leftEndY) * zoomFactorY
519  : ( slopeBotRight
520  ? (startY + rightEndY) * zoomFactorY
521  : y3
522  );
523  for (unsigned ix = 0; ix < zoomFactorX; ix++) {
524  const unsigned fx = x * zoomFactorX + ix;
525 
526  // Figure out which parts of the line should be blended.
527  bool blendTopLeft = false;
528  bool blendTopRight = false;
529  bool blendBotLeft = false;
530  bool blendBotRight = false;
531  if (ix * 2 < zoomFactorX) {
532  blendTopLeft = slopeTopLeft;
533  blendBotLeft = slopeBotLeft;
534  }
535  if (ix * 2 + 1 >= zoomFactorX) {
536  blendTopRight = slopeTopRight;
537  blendBotRight = slopeBotRight;
538  }
539 
540  // Render top side.
541  if (blendTopLeft || blendTopRight) {
542  assert(!(blendTopLeft && blendTopRight));
543  unsigned mixX = blendTopLeft ? leftX : rightX;
544  float lineX = blendTopLeft
545  ? ((zoomFactorX - 1 - ix) / float(zoomFactorX))
546  : (ix / float(zoomFactorX));
547  for (unsigned fy = y0 | 1; fy < y1; fy += 2) {
548  auto* dstLinePtr = dstLines[dstStartY + fy / 2];
549  float ry = (fy - y0) / float(y1 - y0);
550  float rx = 0.5f + ry * 0.5f;
551  float weight = (rx - lineX) * zoomFactorX;
552  dstLinePtr[fx] = pixelOps.lerp(
553  srcLinePtrs[fy / (zoomFactorY * 2)][mixX],
554  srcLinePtrs[fy / (zoomFactorY * 2)][curX],
555  std::clamp(int(256 * weight), 0, 256));
556  }
557  }
558 
559  // Render bottom side.
560  if (blendBotLeft || blendBotRight) {
561  assert(!(blendBotLeft && blendBotRight));
562  unsigned mixX = blendBotLeft ? leftX : rightX;
563  float lineX = blendBotLeft
564  ? ((zoomFactorX - 1 - ix) / float(zoomFactorX))
565  : (ix / float(zoomFactorX));
566  for (unsigned fy = y2 | 1; fy < y3; fy += 2) {
567  auto* dstLinePtr = dstLines[dstStartY + fy / 2];
568  float ry = (fy - y2) / float(y3 - y2);
569  float rx = 1.0f - ry * 0.5f;
570  float weight = (rx - lineX) * zoomFactorX;
571  dstLinePtr[fx] = pixelOps.lerp(
572  srcLinePtrs[fy / (zoomFactorY * 2)][mixX],
573  srcLinePtrs[fy / (zoomFactorY * 2)][curX],
574  std::clamp(int(256 * weight), 0, 256));
575  }
576  }
577 
578  // Draw vertical edge indicators.
579  if (false) {
580  if (ix == 0) {
581  if (slopeTopLeft) {
582  for (unsigned fy = y0 | 1; fy < y1; fy += 2) {
583  auto* dstLinePtr = dstLines[
584  dstStartY + fy / 2];
585  dstLinePtr[fx] =
586  pixelOps.combine256(255, 0, 0);
587  }
588  }
589  if (slopeBotLeft) {
590  for (unsigned fy = y2 | 1; fy < y3; fy += 2) {
591  auto* dstLinePtr = dstLines[
592  dstStartY + fy / 2];
593  dstLinePtr[fx] =
594  pixelOps.combine256(255, 255, 0);
595  }
596  }
597  } else if (ix == zoomFactorX - 1) {
598  if (slopeTopRight) {
599  for (unsigned fy = y0 | 1; fy < y1; fy += 2) {
600  auto* dstLinePtr = dstLines[
601  dstStartY + fy / 2];
602  dstLinePtr[fx] =
603  pixelOps.combine256(0, 0, 255);
604  }
605  }
606  if (slopeBotRight) {
607  for (unsigned fy = y2 | 1; fy < y3; fy += 2) {
608  auto* dstLinePtr = dstLines[
609  dstStartY + fy / 2];
610  dstLinePtr[fx] =
611  pixelOps.combine256(0, 255, 0);
612  }
613  }
614  }
615  }
616  }
617  }
618  assert(y == srcNumLines);
619  }
620 
621  // TODO: This is compensation for the fact that we do not support
622  // non-integer zoom factors yet.
623  if (srcWidth * zoomFactorX != dstWidth) {
624  for (unsigned dy = dstStartY; dy < dstY; dy++) {
625  unsigned sy = std::min(
626  (dy - dstStartY) / zoomFactorY - srcStartY,
627  unsigned(srcNumLines)
628  );
629  Pixel col = srcLinePtrs[sy][srcWidth - 1];
630  for (unsigned dx = srcWidth * zoomFactorX; dx < dstWidth; dx++) {
631  dstLines[dy][dx] = col;
632  }
633  }
634  }
635  if (dstY != dstEndY) {
636  // Typically this will pick the border color, but there is no guarantee.
637  // However, we're inside a workaround anyway, so it's good enough.
638  Pixel col = srcLinePtrs[srcNumLines - 1][srcWidth - 1];
639  for (unsigned dy = dstY; dy < dstEndY; dy++) {
640  for (unsigned dx = 0; dx < dstWidth; dx++) {
641  dstLines[dy][dx] = col;
642  }
643  }
644  }
645 
646  for (unsigned i = dstStartY; i < dstEndY; ++i) {
647  dst.releaseLine(i, dstLines[i]);
648  }
649 }
650 
651 // Force template instantiation.
652 #if HAVE_16BPP
653 template class MLAAScaler<uint16_t>;
654 #endif
655 #if HAVE_32BPP
656 template class MLAAScaler<uint32_t>;
657 #endif
658 
659 } // namespace openmsx
MLAAScaler.hh
gl::clamp
vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
Definition: gl_vec.hh:301
gl::min
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:274
openmsx::PixelOperations
Definition: PixelOperations.hh:143
aligned.hh
openmsx::MLAAScaler::scaleImage
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
FrameSource.hh
openmsx::MLAAScaler
Scaler that uses a variation of the morphological anti-aliasing algorithm.
Definition: MLAAScaler.hh:20
openmsx::ScalerOutput::releaseLine
virtual void releaseLine(unsigned y, Pixel *buf)=0
openmsx::FrameSource::getLinePtr
const Pixel * getLinePtr(int line, unsigned width, Pixel *buf) const
Gets a pointer to the pixels of the given line number.
Definition: FrameSource.hh:90
gl::length
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:348
openmsx::ScalerOutput::getHeight
virtual unsigned getHeight() const =0
openmsx::ScalerOutput
Definition: ScalerOutput.hh:7
vla.hh
openmsx::Pixel
uint32_t Pixel
Definition: GLHQLiteScaler.cc:98
ScalerOutput.hh
openmsx::MemBuffer< uint8_t >
build-info.hh
openmsx::ScalerOutput::acquireLine
virtual Pixel * acquireLine(unsigned y)=0
openmsx::RawFrame
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Definition: RawFrame.hh:26
MemBuffer.hh
openmsx::x
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:1416
openmsx::FrameSource
Interface for getting lines from a video frame.
Definition: FrameSource.hh:14
openmsx::MemBuffer::data
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
gl::max
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:292
openmsx::MLAAScaler::MLAAScaler
MLAAScaler(unsigned dstWidth, const PixelOperations< Pixel > &pixelOps)
Definition: MLAAScaler.cc:16
VLA
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5