402 lines
12 KiB
C++
402 lines
12 KiB
C++
|
|
|
|
#include "trop.h"
|
|
|
|
/*
|
|
See Alexander Reshetov's "Morphological Antialiasing" paper on Intel Labs site.
|
|
|
|
Basically, this antialiasing algorithm is based on the following ideas:
|
|
|
|
- Suppose that our image is just made up of flat colors. Then, a simple antialiasing
|
|
approach is that of assuming that the 'actual' line separating two distinct colors
|
|
is the polyline that passes through the midpoint of each edge of its original jaggy
|
|
counterpart.
|
|
As pixels around the border are cut through by the polyline, the area of the pixel
|
|
that is filled of a certain color is its weight in the output filtered pixel.
|
|
|
|
- The above method can be applied on each single uniform piece of a scanline, considering
|
|
the lines originated by the vertical extensions of its left and right edges.
|
|
|
|
- Of these lines, only those which lie completely on pixels adjacent to the edge are
|
|
considered - so that the antialiasing effect is kept only around the contours.
|
|
|
|
This algorithm would yield a good result at what may be considered 50% softness. Implementing
|
|
a generalized softness simply requires that the line slopes used above are modified
|
|
accordingly (divide by 2 * softFactor).
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------------------
|
|
|
|
namespace
|
|
{
|
|
template <typename PIX>
|
|
class PixelSelector
|
|
{
|
|
public:
|
|
typedef PIX pixel_type;
|
|
|
|
private:
|
|
int m_thresh;
|
|
|
|
public:
|
|
PixelSelector(int thresh) : m_thresh(thresh) {}
|
|
|
|
bool areEqual(const PIX &a, const PIX &b) const
|
|
{
|
|
return tmax(abs((int)a.r - b.r),
|
|
abs((int)a.g - b.g),
|
|
abs((int)a.b - b.b),
|
|
abs((int)a.m - b.m)) < m_thresh;
|
|
}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------------------
|
|
|
|
template <>
|
|
class PixelSelector<TPixelCM32>
|
|
{
|
|
public:
|
|
typedef TPixelCM32 pixel_type;
|
|
|
|
private:
|
|
int m_thresh;
|
|
|
|
public:
|
|
PixelSelector(int thresh) : m_thresh(thresh) {}
|
|
|
|
bool areEqual(const TPixelCM32 &a, const TPixelCM32 &b) const
|
|
{
|
|
return (a.getInk() == b.getInk()) && (abs(a.getTone() - b.getTone()) < m_thresh);
|
|
}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------------------
|
|
|
|
template <typename PIX>
|
|
inline void weightPix(PIX *out, const PIX *a, const PIX *b, double weightA, double weightB)
|
|
{
|
|
out->r = a->r * weightA + b->r * weightB;
|
|
out->g = a->g * weightA + b->g * weightB;
|
|
out->b = a->b * weightA + b->b * weightB;
|
|
out->m = a->m * weightA + b->m * weightB;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------
|
|
|
|
template <>
|
|
inline void weightPix<TPixelCM32>(TPixelCM32 *out, const TPixelCM32 *a, const TPixelCM32 *b,
|
|
double weightA, double weightB)
|
|
{
|
|
*out = TPixelCM32(
|
|
out->isPurePaint() ? b->getInk() : a->getInk(), a->getPaint(),
|
|
a->getTone() * weightA + b->getTone() * weightB);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------
|
|
|
|
//Returns 0 if pixels to connect are on the 00-11 diagonal, 1 on the 01-10 one.
|
|
template <typename PIX, typename SELECTOR>
|
|
inline bool checkNeighbourHood(int x, int y, PIX *pix, int lx, int ly, int dx, int dy,
|
|
const SELECTOR &sel)
|
|
{
|
|
int count1 = 0, count2 = 0;
|
|
int dx2 = 2 * dx, dy2 = 2 * dy;
|
|
if (y > 1) {
|
|
//Lower edge
|
|
count1 +=
|
|
(int)sel.areEqual(*(pix - dx), *(pix - dy2)) +
|
|
(int)sel.areEqual(*(pix - dx), *(pix - dy2 - dx));
|
|
count2 +=
|
|
(int)sel.areEqual(*pix, *(pix - dy2)) +
|
|
(int)sel.areEqual(*pix, *(pix - dy2 - dx));
|
|
}
|
|
if (y < ly - 1) {
|
|
//Upper edge
|
|
count1 +=
|
|
(int)sel.areEqual(*(pix - dx), *(pix + dy)) +
|
|
(int)sel.areEqual(*(pix - dx), *(pix + dy - dx));
|
|
count2 +=
|
|
(int)sel.areEqual(*pix, *(pix + dy)) +
|
|
(int)sel.areEqual(*pix, *(pix + dy - dx));
|
|
}
|
|
if (x > 1) {
|
|
//Left edge
|
|
count1 +=
|
|
(int)sel.areEqual(*(pix - dx), *(pix - dx2)) +
|
|
(int)sel.areEqual(*(pix - dx), *(pix - dx2 - dy));
|
|
count2 +=
|
|
(int)sel.areEqual(*pix, *(pix - dx2)) +
|
|
(int)sel.areEqual(*pix, *(pix - dx2 - dy));
|
|
}
|
|
if (x < lx - 1) {
|
|
//Left edge
|
|
count1 +=
|
|
(int)sel.areEqual(*(pix - dx), *(pix + dx)) +
|
|
(int)sel.areEqual(*(pix - dx), *(pix + dx - dy));
|
|
count2 +=
|
|
(int)sel.areEqual(*pix, *(pix + dx)) +
|
|
(int)sel.areEqual(*pix, *(pix + dx - dy));
|
|
}
|
|
|
|
//Connect by minority: if there are more pixels like those on the 00-11 diagonal, connect the other,
|
|
//and viceversa.
|
|
return count1 > count2;
|
|
}
|
|
}
|
|
|
|
//========================================================================================
|
|
|
|
template <typename PIX>
|
|
inline void filterLine(PIX *inLPix, PIX *inUPix, PIX *outLPix, PIX *outUPix,
|
|
int ll, int inDl, int outLDl, int outUDl,
|
|
double hStart, double slope, bool filterLower)
|
|
{
|
|
assert(hStart >= 0.0 && slope > 0.0);
|
|
|
|
double h0 = hStart, h1, area;
|
|
double base = hStart / slope;
|
|
|
|
int i, end = tmin(tfloor(base), ll);
|
|
if (filterLower) {
|
|
//Filter lower line
|
|
for (i = 0; i < end; ++i, h0 = h1,
|
|
inLPix += inDl, inUPix += inDl, outLPix += outLDl) {
|
|
h1 = h0 - slope;
|
|
area = 0.5 * (h0 + h1);
|
|
weightPix(outLPix, outLPix, inUPix, 1.0 - area, area);
|
|
}
|
|
|
|
if (i < ll) {
|
|
double remnant = base - end;
|
|
area = 0.5 * remnant * h0;
|
|
weightPix(outLPix, outLPix, inUPix, 1.0 - area, area);
|
|
}
|
|
} else {
|
|
//Filter upper line
|
|
for (i = 0; i < end; ++i, h0 = h1,
|
|
inLPix += inDl, inUPix += inDl, outUPix += outUDl) {
|
|
h1 = h0 - slope;
|
|
area = 0.5 * (h0 + h1);
|
|
weightPix(outUPix, outUPix, inLPix, 1.0 - area, area);
|
|
}
|
|
|
|
if (i < ll) {
|
|
double remnant = base - end;
|
|
area = 0.5 * remnant * h0;
|
|
weightPix(outUPix, outUPix, inLPix, 1.0 - area, area);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
|
|
template <typename PIX, typename SELECTOR>
|
|
inline bool checkLength(int lLine, int y, int ly, int dy,
|
|
PIX *pixL1, PIX *pixU1, PIX *pixL2, PIX *pixU2,
|
|
bool uniteU, bool do1Line,
|
|
const SELECTOR &sel)
|
|
{
|
|
//1-length edges must be processed (as primary edges) only if explicitly required,
|
|
//and only when its associated secondary edge is of the same length.
|
|
|
|
return (lLine > 1) ||
|
|
(do1Line &&
|
|
((uniteU && (y > 1 && !(sel.areEqual(*pixL1, *(pixL1 - dy)) && sel.areEqual(*pixL2, *(pixL2 - dy))))) ||
|
|
(y < ly - 1 && !(sel.areEqual(*pixU1, *(pixU1 + dy)) && sel.areEqual(*pixU2, *(pixU2 + dy))))));
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
|
|
template <typename PIX, typename SELECTOR>
|
|
void processLine(int r, int lx, int ly,
|
|
PIX *inLRow, PIX *inURow, PIX *outLRow, PIX *outURow,
|
|
int inDx, int inDy, int outLDx, int outUDx,
|
|
bool do1Line, double hStart, double slope,
|
|
const SELECTOR &sel)
|
|
{
|
|
//Using a 'horizontal' notation here - but the same applies in vertical too
|
|
++r;
|
|
|
|
//As long as we don't reach row end, process uninterrupted separation lines
|
|
//between colors
|
|
|
|
PIX *inLL = inLRow, *inLR, *inUL = inURow, *inUR;
|
|
PIX *inLL_1, *inUL_1, *inLR_1, *inUR_1;
|
|
PIX *inLEnd = inLRow + lx * inDx;
|
|
int x, lLine;
|
|
bool uniteLL, uniteUL, uniteLR, uniteUR;
|
|
|
|
//Special case: a line at row start has different weights
|
|
if (!sel.areEqual(*inLL, *inUL)) {
|
|
//Look for line ends
|
|
for (inLR = inLL + inDx, inUR = inUL + inDx;
|
|
inLR != inLEnd && sel.areEqual(*inLL, *inLR) && sel.areEqual(*inUL, *inUR);
|
|
inLR += inDx, inUR += inDx)
|
|
;
|
|
|
|
if (inLR != inLEnd) {
|
|
//Found a line to process
|
|
lLine = (inLR - inLL) / inDx;
|
|
|
|
inLR_1 = inLR - inDx, inUR_1 = inUR - inDx;
|
|
x = (inLR_1 - inLRow) / inDx;
|
|
|
|
uniteUR = sel.areEqual(*inUR_1, *inLR);
|
|
uniteLR = sel.areEqual(*inLR_1, *inUR);
|
|
if (uniteUR || uniteLR) {
|
|
if (uniteUR && uniteLR)
|
|
//Ambiguous case. Check neighborhood to find out which one must be actually united.
|
|
uniteUR = !checkNeighbourHood(x + 1, r, inUR, lx, ly, inDx, inDy, sel);
|
|
|
|
if (checkLength(lLine, r, ly, inDy, inLR_1, inUR_1, inLR, inUR, uniteUR, do1Line, sel))
|
|
filterLine(inLR_1, inUR_1, outLRow + x * outLDx, outURow + x * outUDx,
|
|
lLine, -inDx, -outLDx, -outUDx, hStart, slope / (lLine << 1), uniteUR);
|
|
}
|
|
}
|
|
|
|
//Update lefts
|
|
inLL = inLR, inUL = inUR;
|
|
}
|
|
|
|
//Search for a line start
|
|
for (; inLL != inLEnd && sel.areEqual(*inLL, *inUL); inLL += inDx, inUL += inDx)
|
|
;
|
|
|
|
while (inLL != inLEnd) {
|
|
//Look for line ends
|
|
for (inLR = inLL + inDx, inUR = inUL + inDx;
|
|
inLR != inLEnd && sel.areEqual(*inLL, *inLR) && sel.areEqual(*inUL, *inUR);
|
|
inLR += inDx, inUR += inDx)
|
|
;
|
|
|
|
if (inLR == inLEnd)
|
|
break; //Dealt with later
|
|
|
|
//Found a line to process
|
|
lLine = (inLR - inLL) / inDx;
|
|
|
|
//First, filter left to right
|
|
inLL_1 = inLL - inDx, inUL_1 = inUL - inDx;
|
|
x = (inLL - inLRow) / inDx;
|
|
|
|
uniteUL = sel.areEqual(*inUL, *inLL_1);
|
|
uniteLL = sel.areEqual(*inLL, *inUL_1);
|
|
if (uniteUL || uniteLL) {
|
|
if (uniteUL && uniteLL)
|
|
uniteUL = checkNeighbourHood(x, r, inUL, lx, ly, inDx, inDy, sel);
|
|
|
|
if (checkLength(lLine, r, ly, inDy, inLL_1, inUL_1, inLL, inUL, uniteUL, do1Line, sel))
|
|
filterLine(inLL, inUL, outLRow + x * outLDx, outURow + x * outUDx,
|
|
lLine, inDx, outLDx, outUDx, hStart, slope / lLine, uniteUL);
|
|
}
|
|
|
|
//Then, filter right to left
|
|
inLR_1 = inLR - inDx, inUR_1 = inUR - inDx;
|
|
x = (inLR_1 - inLRow) / inDx;
|
|
|
|
uniteUR = sel.areEqual(*inUR_1, *inLR);
|
|
uniteLR = sel.areEqual(*inLR_1, *inUR);
|
|
if (uniteUR || uniteLR) {
|
|
if (uniteUR && uniteLR)
|
|
uniteUR = !checkNeighbourHood(x + 1, r, inUR, lx, ly, inDx, inDy, sel);
|
|
|
|
if (checkLength(lLine, r, ly, inDy, inLR_1, inUR_1, inLR, inUR, uniteUR, do1Line, sel))
|
|
filterLine(inLR_1, inUR_1, outLRow + x * outLDx, outURow + x * outUDx,
|
|
lLine, -inDx, -outLDx, -outUDx, hStart, slope / lLine, uniteUR);
|
|
}
|
|
|
|
//Update lefts - search for a new line start
|
|
inLL = inLR, inUL = inUR;
|
|
for (; inLL != inLEnd && sel.areEqual(*inLL, *inUL); inLL += inDx, inUL += inDx)
|
|
;
|
|
}
|
|
|
|
//Special case: filter the last line in the row
|
|
if (inLL != inLEnd) {
|
|
//Found a line to process
|
|
lLine = (inLR - inLL) / inDx;
|
|
|
|
inLL_1 = inLL - inDx, inUL_1 = inUL - inDx;
|
|
x = (inLL - inLRow) / inDx;
|
|
|
|
uniteUL = sel.areEqual(*inUL, *inLL_1);
|
|
uniteLL = sel.areEqual(*inLL, *inUL_1);
|
|
if (uniteUL || uniteLL) {
|
|
if (uniteUL && uniteLL)
|
|
uniteUL = checkNeighbourHood(x, r, inUL, lx, ly, inDx, inDy, sel);
|
|
|
|
if (checkLength(lLine, r, ly, inDy, inLL_1, inUL_1, inLL, inUL, uniteUL, do1Line, sel))
|
|
filterLine(inLL, inUL, outLRow + x * outLDx, outURow + x * outUDx,
|
|
lLine, inDx, outLDx, outUDx, hStart, slope / (lLine << 1), uniteUL);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
|
|
template <typename PIX>
|
|
void makeAntialias(const TRasterPT<PIX> &src, TRasterPT<PIX> &dst, int threshold, int softness)
|
|
{
|
|
dst->copy(src);
|
|
if (softness == 0)
|
|
return;
|
|
|
|
double slope = (50.0 / softness);
|
|
double hStart = 0.5; //fixed for now
|
|
|
|
src->lock();
|
|
dst->lock();
|
|
|
|
PixelSelector<PIX> sel(threshold);
|
|
|
|
//First, filter by rows
|
|
int x, y, lx = src->getLx(), ly = src->getLy(), lx_1 = lx - 1, ly_1 = ly - 1;
|
|
for (y = 0; y < ly_1; ++y) {
|
|
processLine(y, lx, ly,
|
|
src->pixels(y), src->pixels(y + 1),
|
|
dst->pixels(y), dst->pixels(y + 1),
|
|
1, src->getWrap(), 1, 1,
|
|
true, hStart, slope,
|
|
sel);
|
|
}
|
|
|
|
//Then, go by columns
|
|
for (x = 0; x < lx_1; ++x) {
|
|
processLine(x, ly, lx,
|
|
src->pixels(0) + x, src->pixels(0) + x + 1,
|
|
dst->pixels(0) + x, dst->pixels(0) + x + 1,
|
|
src->getWrap(), 1, dst->getWrap(), dst->getWrap(),
|
|
false, hStart, slope,
|
|
sel);
|
|
}
|
|
|
|
dst->unlock();
|
|
src->unlock();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
|
|
void TRop::antialias(const TRasterP &src, const TRasterP &dst, int threshold, int softness)
|
|
{
|
|
assert(src->getSize() == dst->getSize());
|
|
|
|
TRaster32P src32(src), dst32(dst);
|
|
if (src32 && dst32) {
|
|
makeAntialias<TPixel32>(src32, dst32, threshold, softness);
|
|
return;
|
|
}
|
|
|
|
TRaster64P src64(src), dst64(dst);
|
|
if (src64 && dst64) {
|
|
makeAntialias<TPixel64>(src64, dst64, threshold << 8, softness);
|
|
return;
|
|
}
|
|
|
|
TRasterCM32P srcCM(src), dstCM(dst);
|
|
if (srcCM && dstCM) {
|
|
makeAntialias<TPixelCM32>(srcCM, dstCM, threshold, softness);
|
|
return;
|
|
}
|
|
|
|
assert(!"Source and destination rasters must be of the same type!");
|
|
}
|