EdgeTable: Fix issue where edges of paths could be anti-aliased incorrectly at edges of clip regions
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 912 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
|
@ -884,7 +884,13 @@ public:
|
||||||
{
|
{
|
||||||
beginTest ("Render image subsection");
|
beginTest ("Render image subsection");
|
||||||
{
|
{
|
||||||
renderImageSubsection (NativeImageType{}, NativeImageType{});
|
const SoftwareImageType softwareImageType;
|
||||||
|
const NativeImageType nativeImageType;
|
||||||
|
const ImageType* types[] { &softwareImageType, &nativeImageType };
|
||||||
|
|
||||||
|
for (auto* sourceType : types)
|
||||||
|
for (auto* targetType : types)
|
||||||
|
renderImageSubsection (*sourceType, *targetType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -904,6 +910,8 @@ private:
|
||||||
{
|
{
|
||||||
// Render the subsection image so that it fills 'target'
|
// Render the subsection image so that it fills 'target'
|
||||||
Graphics g { target };
|
Graphics g { target };
|
||||||
|
// Use low resampling quality, because we want to avoid our pixel getting blurry when it's scaled up
|
||||||
|
g.setImageResamplingQuality (Graphics::lowResamplingQuality);
|
||||||
g.drawImage (subsection,
|
g.drawImage (subsection,
|
||||||
0, 0, target.getWidth(), target.getHeight(),
|
0, 0, target.getWidth(), target.getHeight(),
|
||||||
0, 0, 1, 1);
|
0, 0, 1, 1);
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ EdgeTable::EdgeTable (Rectangle<int> area, const Path& path, const AffineTransfo
|
||||||
{
|
{
|
||||||
auto step = jmin (stepSize, y2 - y1, 256 - (y1 & 255));
|
auto step = jmin (stepSize, y2 - y1, 256 - (y1 & 255));
|
||||||
auto x = static_cast<int64_t> (startX + multiplier * static_cast<double> ((y1 + (step >> 1)) - startY));
|
auto x = static_cast<int64_t> (startX + multiplier * static_cast<double> ((y1 + (step >> 1)) - startY));
|
||||||
auto clampedX = static_cast<int> (jlimit (leftLimit, rightLimit - 1, x));
|
auto clampedX = static_cast<int> (jlimit (leftLimit, rightLimit, x));
|
||||||
|
|
||||||
addEdgePoint (clampedX, static_cast<int> (y1 / scale), static_cast<int> (direction * step));
|
addEdgePoint (clampedX, static_cast<int> (y1 / scale), static_cast<int> (direction * step));
|
||||||
y1 += step;
|
y1 += step;
|
||||||
|
|
@ -885,14 +885,44 @@ public:
|
||||||
}();
|
}();
|
||||||
|
|
||||||
expect (! contains (edgeTableContainingAPath, { 6, 2 }),
|
expect (! contains (edgeTableContainingAPath, { 6, 2 }),
|
||||||
"The path doesn't enclose the point (6, 2) so it's EdgeTable shouldn't contain it");
|
"The path doesn't enclose the point (6, 2) so its EdgeTable shouldn't contain it");
|
||||||
|
|
||||||
expect (contains (edgeTableFromRectangle, { 6, 2 }),
|
expect (contains (edgeTableFromRectangle, { 6, 2 }),
|
||||||
"The Rectangle covers the point (6, 2) so it's EdgeTable should contain it");
|
"The Rectangle covers the point (6, 2) so its EdgeTable should contain it");
|
||||||
|
|
||||||
expect (! contains (intersection, { 6, 2 }),
|
expect (! contains (intersection, { 6, 2 }),
|
||||||
"The intersecting EdgeTable shouldn't contain (6, 2) because one of its constituents doesn't contain it either");
|
"The intersecting EdgeTable shouldn't contain (6, 2) because one of its constituents doesn't contain it either");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginTest ("An EdgeTable constructed from a pixel-aligned Rectangle should not anti-alias");
|
||||||
|
{
|
||||||
|
Rectangle<int> area { 5, 5 };
|
||||||
|
Path p;
|
||||||
|
p.addRectangle (area.reduced (1));
|
||||||
|
EdgeTable bordered { area, p, {} };
|
||||||
|
|
||||||
|
// Pixels at edges should be clear
|
||||||
|
expect (getLevel (bordered, { 0, 0 }) == 0);
|
||||||
|
expect (getLevel (bordered, { 0, 4 }) == 0);
|
||||||
|
expect (getLevel (bordered, { 4, 0 }) == 0);
|
||||||
|
expect (getLevel (bordered, { 4, 4 }) == 0);
|
||||||
|
|
||||||
|
// Corners of filled area should have max level
|
||||||
|
expect (getLevel (bordered, { 1, 1 }) == 255);
|
||||||
|
expect (getLevel (bordered, { 1, 3 }) == 255);
|
||||||
|
expect (getLevel (bordered, { 3, 1 }) == 255);
|
||||||
|
expect (getLevel (bordered, { 3, 3 }) == 255);
|
||||||
|
|
||||||
|
Path q;
|
||||||
|
q.addRectangle (area);
|
||||||
|
EdgeTable filled { area, q, {} };
|
||||||
|
|
||||||
|
// Pixels at edges should have max level
|
||||||
|
expect (getLevel (filled, { 0, 0 }) == 255);
|
||||||
|
expect (getLevel (filled, { 0, 4 }) == 255);
|
||||||
|
expect (getLevel (filled, { 4, 0 }) == 255);
|
||||||
|
expect (getLevel (filled, { 4, 4 }) == 255);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -910,48 +940,48 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEdgeTablePixelFull (int x)
|
void handleEdgeTablePixelFull (int x)
|
||||||
|
{
|
||||||
|
handleEdgeTablePixel (x, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEdgeTablePixel (int x, uint8_t level)
|
||||||
{
|
{
|
||||||
if (! (y < height && x < width))
|
if (! (y < height && x < width))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto* ptr = data.data() + width * y + x;
|
auto* ptr = data.data() + width * y + x;
|
||||||
*ptr = 1;
|
*ptr = level;
|
||||||
}
|
|
||||||
|
|
||||||
void handleEdgeTablePixel (int x, int)
|
|
||||||
{
|
|
||||||
handleEdgeTablePixelFull (x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEdgeTableLineFull (int x, int w)
|
void handleEdgeTableLineFull (int x, int w)
|
||||||
|
{
|
||||||
|
handleEdgeTableLine (x, w, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEdgeTableLine (int x, int w, uint8_t level)
|
||||||
{
|
{
|
||||||
if (! (y < height && x < width))
|
if (! (y < height && x < width))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto* ptr = data.data() + width * y + x;
|
auto* ptr = data.data() + width * y + x;
|
||||||
std::fill (ptr, ptr + std::min (w, width - x), 1);
|
std::fill (ptr, ptr + std::min (w, width - x), level);
|
||||||
}
|
|
||||||
|
|
||||||
void handleEdgeTableLine (int x, int w, int)
|
|
||||||
{
|
|
||||||
handleEdgeTableLineFull (x, w);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEdgeTableRectangleFull (int x, int yIn, int w, int h) noexcept
|
void handleEdgeTableRectangleFull (int x, int yIn, int w, int h) noexcept
|
||||||
|
{
|
||||||
|
handleEdgeTableRectangle (x, yIn, w, h, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEdgeTableRectangle (int x, int yIn, int w, int h, uint8_t level) noexcept
|
||||||
{
|
{
|
||||||
for (int j = yIn; j < std::min (yIn + h, height); ++j)
|
for (int j = yIn; j < std::min (yIn + h, height); ++j)
|
||||||
{
|
{
|
||||||
auto* ptr = data.data() + width * j + x;
|
auto* ptr = data.data() + width * j + x;
|
||||||
std::fill (ptr, ptr + std::min (w, width - x), 1);
|
std::fill (ptr, ptr + std::min (w, width - x), level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEdgeTableRectangle (int x, int yIn, int w, int h, int) noexcept
|
uint8_t get (int x, int yIn) const
|
||||||
{
|
|
||||||
handleEdgeTableRectangleFull (x, yIn, w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
int get (int x, int yIn) const
|
|
||||||
{
|
{
|
||||||
const auto index = (size_t) (width * yIn + x);
|
const auto index = (size_t) (width * yIn + x);
|
||||||
|
|
||||||
|
|
@ -963,17 +993,22 @@ private:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const int width, height = 0;
|
const int width, height = 0;
|
||||||
std::vector<int> data;
|
std::vector<uint8_t> data;
|
||||||
int y = 0;
|
int y = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool contains (const EdgeTable& et, Point<int> p)
|
static uint8_t getLevel (const EdgeTable& et, Point<int> p)
|
||||||
{
|
{
|
||||||
EdgeTableFiller filler { p.getX() + 2, p.getY() + 2 };
|
EdgeTableFiller filler { p.getX() + 2, p.getY() + 2 };
|
||||||
et.iterate (filler);
|
et.iterate (filler);
|
||||||
|
return filler.get (p.getX(), p.getY());
|
||||||
return filler.get (p.getX(), p.getY()) == 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool contains (const EdgeTable& et, Point<int> p)
|
||||||
|
{
|
||||||
|
return getLevel (et, p) == 255;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static EdgeTableTests edgeTableTests;
|
static EdgeTableTests edgeTableTests;
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ public:
|
||||||
if (levelAccumulator >= 255)
|
if (levelAccumulator >= 255)
|
||||||
iterationCallback.handleEdgeTablePixelFull (x);
|
iterationCallback.handleEdgeTablePixelFull (x);
|
||||||
else
|
else
|
||||||
iterationCallback.handleEdgeTablePixel (x, levelAccumulator);
|
iterationCallback.handleEdgeTablePixel (x, static_cast<uint8_t> (levelAccumulator));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's a run of similar pixels, do it all in one go..
|
// if there's a run of similar pixels, do it all in one go..
|
||||||
|
|
@ -164,7 +164,7 @@ public:
|
||||||
const int numPix = endOfRun - ++x;
|
const int numPix = endOfRun - ++x;
|
||||||
|
|
||||||
if (numPix > 0)
|
if (numPix > 0)
|
||||||
iterationCallback.handleEdgeTableLine (x, numPix, level);
|
iterationCallback.handleEdgeTableLine (x, numPix, static_cast<uint8_t> (level));
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the bit at the end to be drawn next time round the loop.
|
// save the bit at the end to be drawn next time round the loop.
|
||||||
|
|
@ -184,7 +184,7 @@ public:
|
||||||
if (levelAccumulator >= 255)
|
if (levelAccumulator >= 255)
|
||||||
iterationCallback.handleEdgeTablePixelFull (x);
|
iterationCallback.handleEdgeTablePixelFull (x);
|
||||||
else
|
else
|
||||||
iterationCallback.handleEdgeTablePixel (x, levelAccumulator);
|
iterationCallback.handleEdgeTablePixel (x, static_cast<uint8_t> (levelAccumulator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||