Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAntiAliasSelectionFilter Class Reference

AntiAlias filter for selections inspired by FXAA. More...

#include <kis_selection_filters.h>

+ Inheritance diagram for KisAntiAliasSelectionFilter:

Public Member Functions

KUndo2MagicString name () override
 
void process (KisPixelSelectionSP pixelSelection, const QRect &rect) override
 
- Public Member Functions inherited from KisSelectionFilter
virtual QRect changeRect (const QRect &rect, KisDefaultBoundsBaseSP defaultBounds)
 
virtual ~KisSelectionFilter ()
 

Private Member Functions

void findSpanExtreme (quint8 **scanlines, qint32 x, qint32 pixelOffset, qint32 rowMultiplier, qint32 colMultiplier, qint32 direction, qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, qint32 *spanEndDistance, qint32 *pixelDiff, bool *spanExtremeValidType) const
 Get the extreme point of the span for the current pixel in the given direction.
 
void findSpanExtremes (quint8 **scanlines, qint32 x, qint32 pixelOffset, qint32 rowMultiplier, qint32 colMultiplier, qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, qint32 *negativeSpanEndDistance, qint32 *positiveSpanEndDistance, qint32 *negativePixelDiff, qint32 *positivePixelDiff, bool *negativeSpanExtremeValid, bool *positiveSpanExtremeValid) const
 Get the extreme points of the span for the current pixel.
 
bool getInterpolationValue (qint32 negativeSpanEndDistance, qint32 positiveSpanEndDistance, qint32 negativePixelDiff, qint32 positivePixelDiff, qint32 currentPixelDiff, bool negativeSpanExtremeValid, bool positiveSpanExtremeValid, qint32 *interpolationValue) const
 Get a interpolation value to linearly interpolate the current pixel with its edge neighbor.
 

Static Private Attributes

static constexpr qint32 currentScanlineIndex {verticalBorderSize}
 Offset of the current scanline in the buffer (The middle scanline).
 
static constexpr qint32 edgeThreshold {4}
 Edges with gradient less than this value will not be antiAliased.
 
static constexpr qint32 horizontalBorderSize {2}
 The size of the border added internally to the left and right of the scanline buffer so that we can read outside the selection rect without problems. It must be equal to the largest value in offsets.
 
static constexpr qint32 numberOfScanlines {2 * verticalBorderSize + 1}
 Number of scanlines in the internal buffer.
 
static constexpr qint32 numSteps {30}
 Number of steps to jump when searching for one of the ends of the antiAliased span.
 
static constexpr qint32 offsets [numSteps]
 This array of numSteps size holds the number of pixels to jump in each step.
 
static constexpr qint32 verticalBorderSize {40}
 The size of the border added internally to the top and bottom of the scanline buffer so that we can read outside the selection rect without problems. It must be equal to the sum of all values in offsets.
 

Additional Inherited Members

- Protected Member Functions inherited from KisSelectionFilter
void computeBorder (qint32 *circ, qint32 xradius, qint32 yradius)
 
void computeTransition (quint8 *transition, quint8 **buf, qint32 width)
 
void rotatePointers (quint8 **p, quint32 n)
 

Detailed Description

AntiAlias filter for selections inspired by FXAA.

Definition at line 147 of file kis_selection_filters.h.

Member Function Documentation

◆ findSpanExtreme()

void KisAntiAliasSelectionFilter::findSpanExtreme ( quint8 ** scanlines,
qint32 x,
qint32 pixelOffset,
qint32 rowMultiplier,
qint32 colMultiplier,
qint32 direction,
qint32 pixelAvg,
qint32 scaledGradient,
qint32 currentPixelDiff,
qint32 * spanEndDistance,
qint32 * pixelDiff,
bool * spanExtremeValidType ) const
private

Get the extreme point of the span for the current pixel in the given direction.

Definition at line 979 of file kis_selection_filters.cpp.

983{
984 *spanEndDistance = 0;
985 *spanExtremeValid = true;
986 for (qint32 i = 0; i < numSteps; ++i) {
987 *spanEndDistance += offsets[i];
988 const qint32 row1 = currentScanlineIndex + (direction * *spanEndDistance * rowMultiplier);
989 const qint32 col1 = x + horizontalBorderSize + (direction * *spanEndDistance * colMultiplier);
990 const qint32 row2 = row1 + pixelOffset * colMultiplier;
991 const qint32 col2 = col1 + pixelOffset * rowMultiplier;
992 const quint8 *pixel1 = scanlines[row1] + col1;
993 const quint8 *pixel2 = scanlines[row2] + col2;
994 // Get how different are these edge pixels from the current pixels and
995 // stop searching if they are too different
996 *pixelDiff = ((*pixel1 + *pixel2) >> 1) - pixelAvg;
997 if (qAbs(*pixelDiff) > scaledGradient) {
998 // If this is the end of the span then check if the corner belongs
999 // to a jagged border or to a right angled part of the shape
1000 qint32 pixelDiff2;
1001 if ((currentPixelDiff < 0 && *pixelDiff < 0) || (currentPixelDiff > 0 && *pixelDiff > 0)) {
1002 const qint32 row3 = row2 + pixelOffset * colMultiplier;
1003 const qint32 col3 = col2 + pixelOffset * rowMultiplier;
1004 const quint8 *pixel3 = scanlines[row3] + col3;
1005 pixelDiff2 = ((*pixel2 + *pixel3) >> 1) - pixelAvg;
1006 } else {
1007 const qint32 row3 = row1 - pixelOffset * colMultiplier;
1008 const qint32 col3 = col1 - pixelOffset * rowMultiplier;
1009 const quint8 *pixel3 = scanlines[row3] + col3;
1010 pixelDiff2 = ((*pixel1 + *pixel3) >> 1) - pixelAvg;
1011 }
1012 *spanExtremeValid = !(qAbs(pixelDiff2) > scaledGradient);
1013 break;
1014 }
1015 }
1016}
static constexpr qint32 numSteps
Number of steps to jump when searching for one of the ends of the antiAliased span.
static constexpr qint32 currentScanlineIndex
Offset of the current scanline in the buffer (The middle scanline).
static constexpr qint32 horizontalBorderSize
The size of the border added internally to the left and right of the scanline buffer so that we can r...
static constexpr qint32 offsets[numSteps]
This array of numSteps size holds the number of pixels to jump in each step.

References currentScanlineIndex, horizontalBorderSize, numSteps, and offsets.

◆ findSpanExtremes()

void KisAntiAliasSelectionFilter::findSpanExtremes ( quint8 ** scanlines,
qint32 x,
qint32 pixelOffset,
qint32 rowMultiplier,
qint32 colMultiplier,
qint32 pixelAvg,
qint32 scaledGradient,
qint32 currentPixelDiff,
qint32 * negativeSpanEndDistance,
qint32 * positiveSpanEndDistance,
qint32 * negativePixelDiff,
qint32 * positivePixelDiff,
bool * negativeSpanExtremeValid,
bool * positiveSpanExtremeValid ) const
private

Get the extreme points of the span for the current pixel.

Definition at line 1018 of file kis_selection_filters.cpp.

1024{
1025 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, -1, pixelAvg, scaledGradient,
1026 currentPixelDiff, negativeSpanEndDistance, negativePixelDiff, negativeSpanExtremeValid);
1027 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, 1, pixelAvg, scaledGradient,
1028 currentPixelDiff, positiveSpanEndDistance, positivePixelDiff, positiveSpanExtremeValid);
1029}
void findSpanExtreme(quint8 **scanlines, qint32 x, qint32 pixelOffset, qint32 rowMultiplier, qint32 colMultiplier, qint32 direction, qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, qint32 *spanEndDistance, qint32 *pixelDiff, bool *spanExtremeValidType) const
Get the extreme point of the span for the current pixel in the given direction.

References findSpanExtreme().

◆ getInterpolationValue()

bool KisAntiAliasSelectionFilter::getInterpolationValue ( qint32 negativeSpanEndDistance,
qint32 positiveSpanEndDistance,
qint32 negativePixelDiff,
qint32 positivePixelDiff,
qint32 currentPixelDiff,
bool negativeSpanExtremeValid,
bool positiveSpanExtremeValid,
qint32 * interpolationValue ) const
private

Get a interpolation value to linearly interpolate the current pixel with its edge neighbor.

Returns
true if we must apply the interpolation. false otherwise.

Definition at line 938 of file kis_selection_filters.cpp.

946{
947 // Since we search a limited number of steps in each direction of the
948 // current pixel, the end pixel of the span may still belong to the edge.
949 // So we check for that, and if that's the case we must not smooth the
950 // current pixel
951 const bool pixelDiffLessThanZero = currentPixelDiff < 0;
952 quint32 distance;
953 if (negativeSpanEndDistance < positiveSpanEndDistance) {
954 if (!negativeSpanExtremeValid) {
955 return false;
956 }
957 // The pixel is closer to the negative end
958 const bool spanEndPixelDiffLessThanZero = negativePixelDiff < 0;
959 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) {
960 return false;
961 }
962 distance = negativeSpanEndDistance;
963 } else {
964 if (!positiveSpanExtremeValid) {
965 return false;
966 }
967 // The pixel is closer to the positive end
968 const bool spanEndPixelDiffLessThanZero = positivePixelDiff < 0;
969 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) {
970 return false;
971 }
972 distance = positiveSpanEndDistance;
973 }
974 const qint32 spanLength = positiveSpanEndDistance + negativeSpanEndDistance;
975 *interpolationValue = ((distance << 8) / spanLength) + 128;
976 return *interpolationValue >= 0;
977}
qreal distance(const QPointF &p1, const QPointF &p2)

References distance().

◆ name()

KUndo2MagicString KisAntiAliasSelectionFilter::name ( )
overridevirtual

Reimplemented from KisSelectionFilter.

Definition at line 933 of file kis_selection_filters.cpp.

934{
935 return kundo2_i18n("Anti-Alias Selection");
936}
KUndo2MagicString kundo2_i18n(const char *text)

References kundo2_i18n().

◆ process()

void KisAntiAliasSelectionFilter::process ( KisPixelSelectionSP pixelSelection,
const QRect & rect )
overridevirtual

Implements KisSelectionFilter.

Definition at line 1031 of file kis_selection_filters.cpp.

1032{
1033 const quint8 defaultPixel = *pixelSelection->defaultPixel().data();
1034 // Size of a scanline
1035 const quint32 bytesPerScanline = rect.width() + 2 * horizontalBorderSize;
1036 // Size of a scanline padded to a multiple of 8
1037 const quint32 bytesPerPaddedScanline = ((bytesPerScanline + 7) / 8) * 8;
1038
1039 // This buffer contains the number of consecutive scanlines needed to
1040 // process the current scanline
1041 QVector<quint8> buffer(bytesPerPaddedScanline * numberOfScanlines);
1042
1043 // These pointers point to the individual scanlines in the buffer
1044 quint8 *scanlines[numberOfScanlines];
1045 for (quint32 i = 0; i < numberOfScanlines; ++i) {
1046 scanlines[i] = buffer.data() + i * bytesPerPaddedScanline;
1047 }
1048
1049 // Initialize the scanlines
1050 // Set the border scanlines on the top
1051 for (qint32 i = 0; i < verticalBorderSize; ++i) {
1052 memset(scanlines[i], defaultPixel, bytesPerScanline);
1053 }
1054 // Copy the first scanlines of the image
1055 const quint32 numberOfFirstRows = qMin(rect.height(), numberOfScanlines - verticalBorderSize);
1056 for (quint32 i = verticalBorderSize; i < verticalBorderSize + numberOfFirstRows; ++i) {
1057 // Set the border pixels on the left
1058 memset(scanlines[i], defaultPixel, horizontalBorderSize);
1059 // Copy the pixel data
1060 pixelSelection->readBytes(scanlines[i] + horizontalBorderSize, rect.x(), rect.y() + i - verticalBorderSize, rect.width(), 1);
1061 // Set the border pixels on the right
1062 memset(scanlines[i] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize);
1063 }
1064 // Set the border scanlines on the bottom
1065 if (verticalBorderSize + numberOfFirstRows < numberOfScanlines) {
1066 for (quint32 i = verticalBorderSize + numberOfFirstRows; i < numberOfScanlines; ++i) {
1067 memset(scanlines[i], defaultPixel, bytesPerScanline);
1068 }
1069 }
1070 // Buffer that contains the current output scanline
1071 QVector<quint8> antialiasedScanline(rect.width());
1072 // Main loop
1073 for (int y = 0; y < rect.height(); ++y)
1074 {
1075 // Move to the next scanline
1076 if (y > 0) {
1077 // Update scanline pointers
1078 std::rotate(std::begin(scanlines), std::begin(scanlines) + 1, std::end(scanlines));
1079 // Copy the next scanline
1080 if (y < rect.height() - verticalBorderSize) {
1081 // Set the border pixels on the left
1082 memset(scanlines[numberOfScanlines - 1], defaultPixel, horizontalBorderSize);
1083 // Copy the pixel data
1084 pixelSelection->readBytes(scanlines[numberOfScanlines - 1] + horizontalBorderSize, rect.x(), rect.y() + y + verticalBorderSize, rect.width(), 1);
1085 // Set the border pixels on the right
1086 memset(scanlines[numberOfScanlines - 1] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize);
1087 } else {
1088 memset(scanlines[numberOfScanlines - 1], defaultPixel, bytesPerScanline);
1089 }
1090 }
1091 // Process the pixels in the current scanline
1092 for (int x = 0; x < rect.width(); ++x)
1093 {
1094 // Get the current pixel and neighbors
1095 quint8 *pixelPtrM = scanlines[currentScanlineIndex ] + x + horizontalBorderSize;
1096 quint8 *pixelPtrN = scanlines[currentScanlineIndex - 1] + x + horizontalBorderSize;
1097 quint8 *pixelPtrS = scanlines[currentScanlineIndex + 1] + x + horizontalBorderSize;
1098 const qint32 pixelNW = *(pixelPtrN - 1);
1099 const qint32 pixelN = *(pixelPtrN );
1100 const qint32 pixelNE = *(pixelPtrN + 1);
1101 const qint32 pixelW = *(pixelPtrM - 1);
1102 const qint32 pixelM = *(pixelPtrM );
1103 const qint32 pixelE = *(pixelPtrM + 1);
1104 const qint32 pixelSW = *(pixelPtrS - 1);
1105 const qint32 pixelS = *(pixelPtrS );
1106 const qint32 pixelSE = *(pixelPtrS + 1);
1107 // Get the gradients
1108 const qint32 rowNSum = (pixelNW >> 2) + (pixelN >> 1) + (pixelNE >> 2);
1109 const qint32 rowMSum = (pixelW >> 2) + (pixelM >> 1) + (pixelE >> 2);
1110 const qint32 rowSSum = (pixelSW >> 2) + (pixelS >> 1) + (pixelSE >> 2);
1111 const qint32 colWSum = (pixelNW >> 2) + (pixelW >> 1) + (pixelSW >> 2);
1112 const qint32 colMSum = (pixelN >> 2) + (pixelM >> 1) + (pixelS >> 2);
1113 const qint32 colESum = (pixelNE >> 2) + (pixelE >> 1) + (pixelSE >> 2);
1114 const qint32 gradientN = qAbs(rowMSum - rowNSum);
1115 const qint32 gradientS = qAbs(rowSSum - rowMSum);
1116 const qint32 gradientW = qAbs(colMSum - colWSum);
1117 const qint32 gradientE = qAbs(colESum - colMSum);
1118 // Get the maximum gradient
1119 const qint32 maxGradientNS = qMax(gradientN, gradientS);
1120 const qint32 maxGradientWE = qMax(gradientW, gradientE);
1121 const qint32 maxGradient = qMax(maxGradientNS, maxGradientWE);
1122 // Return early if the gradient is bellow some threshold (given by
1123 // the value bellow which the jagged edge is not noticeable)
1124 if (maxGradient < edgeThreshold) {
1125 antialiasedScanline[x] = pixelM;
1126 continue;
1127 }
1128 // Collect some info about the pixel and neighborhood
1129 qint32 neighborPixel, gradient;
1130 qint32 pixelOffset, rowMultiplier, colMultiplier;
1131 if (maxGradientNS > maxGradientWE) {
1132 // Horizontal span
1133 if (gradientN > gradientS) {
1134 // The edge is formed with the top pixel
1135 neighborPixel = pixelN;
1136 gradient = gradientN;
1137 pixelOffset = -1;
1138 } else {
1139 // The edge is formed with the bottom pixel
1140 neighborPixel = pixelS;
1141 gradient = gradientS;
1142 pixelOffset = 1;
1143 }
1144 rowMultiplier = 0;
1145 colMultiplier = 1;
1146 } else {
1147 // Vertical span
1148 if (gradientW > gradientE) {
1149 // The edge is formed with the left pixel
1150 neighborPixel = pixelW;
1151 gradient = gradientW;
1152 pixelOffset = -1;
1153 } else {
1154 // The edge is formed with the right pixel
1155 neighborPixel = pixelE;
1156 gradient = gradientE;
1157 pixelOffset = 1;
1158 }
1159 rowMultiplier = 1;
1160 colMultiplier = 0;
1161 }
1162 // Find the span extremes
1163 const qint32 pixelAvg = (neighborPixel + pixelM) >> 1;
1164 const qint32 currentPixelDiff = pixelM - pixelAvg;
1165 qint32 negativePixelDiff, positivePixelDiff;
1166 qint32 negativeSpanEndDistance, positiveSpanEndDistance;
1167 bool negativeSpanExtremeValid, positiveSpanExtremeValid;
1168 findSpanExtremes(scanlines, x, pixelOffset,
1169 rowMultiplier, colMultiplier,
1170 pixelAvg, gradient >> 2, currentPixelDiff,
1171 &negativeSpanEndDistance, &positiveSpanEndDistance,
1172 &negativePixelDiff, &positivePixelDiff,
1173 &negativeSpanExtremeValid, &positiveSpanExtremeValid);
1174 // Get the interpolation value for this pixel given the span extent
1175 // and perform linear interpolation between the current pixel and
1176 // the edge neighbor
1177 qint32 interpolationValue;
1178 if (!getInterpolationValue(negativeSpanEndDistance, positiveSpanEndDistance,
1179 negativePixelDiff, positivePixelDiff, currentPixelDiff,
1180 negativeSpanExtremeValid, positiveSpanExtremeValid, &interpolationValue)) {
1181 antialiasedScanline[x] = pixelM;
1182 } else {
1183 antialiasedScanline[x] = neighborPixel + ((pixelM - neighborPixel) * interpolationValue >> 8);
1184 }
1185 }
1186 // Copy the scanline data to the mask
1187 pixelSelection->writeBytes(antialiasedScanline.data(), rect.x(), rect.y() + y, rect.width(), 1);
1188 }
1189}
static constexpr qint32 numberOfScanlines
Number of scanlines in the internal buffer.
void findSpanExtremes(quint8 **scanlines, qint32 x, qint32 pixelOffset, qint32 rowMultiplier, qint32 colMultiplier, qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, qint32 *negativeSpanEndDistance, qint32 *positiveSpanEndDistance, qint32 *negativePixelDiff, qint32 *positivePixelDiff, bool *negativeSpanExtremeValid, bool *positiveSpanExtremeValid) const
Get the extreme points of the span for the current pixel.
static constexpr qint32 edgeThreshold
Edges with gradient less than this value will not be antiAliased.
bool getInterpolationValue(qint32 negativeSpanEndDistance, qint32 positiveSpanEndDistance, qint32 negativePixelDiff, qint32 positivePixelDiff, qint32 currentPixelDiff, bool negativeSpanExtremeValid, bool positiveSpanExtremeValid, qint32 *interpolationValue) const
Get a interpolation value to linearly interpolate the current pixel with its edge neighbor.
static constexpr qint32 verticalBorderSize
The size of the border added internally to the top and bottom of the scanline buffer so that we can r...
KoColor defaultPixel() const
void readBytes(quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) const
void writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h)
quint8 * data()
Definition KoColor.h:144

References currentScanlineIndex, KoColor::data(), KisPaintDevice::defaultPixel(), edgeThreshold, findSpanExtremes(), getInterpolationValue(), horizontalBorderSize, numberOfScanlines, KisPaintDevice::readBytes(), verticalBorderSize, and KisPaintDevice::writeBytes().

Member Data Documentation

◆ currentScanlineIndex

constexpr qint32 KisAntiAliasSelectionFilter::currentScanlineIndex {verticalBorderSize}
staticconstexprprivate

Offset of the current scanline in the buffer (The middle scanline).

Definition at line 191 of file kis_selection_filters.h.

◆ edgeThreshold

constexpr qint32 KisAntiAliasSelectionFilter::edgeThreshold {4}
staticconstexprprivate

Edges with gradient less than this value will not be antiAliased.

Definition at line 157 of file kis_selection_filters.h.

157{4};

◆ horizontalBorderSize

constexpr qint32 KisAntiAliasSelectionFilter::horizontalBorderSize {2}
staticconstexprprivate

The size of the border added internally to the left and right of the scanline buffer so that we can read outside the selection rect without problems. It must be equal to the largest value in offsets.

Definition at line 177 of file kis_selection_filters.h.

177{2};

◆ numberOfScanlines

constexpr qint32 KisAntiAliasSelectionFilter::numberOfScanlines {2 * verticalBorderSize + 1}
staticconstexprprivate

Number of scanlines in the internal buffer.

Definition at line 187 of file kis_selection_filters.h.

187{2 * verticalBorderSize + 1};

◆ numSteps

constexpr qint32 KisAntiAliasSelectionFilter::numSteps {30}
staticconstexprprivate

Number of steps to jump when searching for one of the ends of the antiAliased span.

Definition at line 162 of file kis_selection_filters.h.

162{30};

◆ offsets

constexpr qint32 KisAntiAliasSelectionFilter::offsets
staticconstexprprivate
Initial value:
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2
}

This array of numSteps size holds the number of pixels to jump in each step.

Definition at line 167 of file kis_selection_filters.h.

167 {
168 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
169 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
170 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
171 };

◆ verticalBorderSize

constexpr qint32 KisAntiAliasSelectionFilter::verticalBorderSize {40}
staticconstexprprivate

The size of the border added internally to the top and bottom of the scanline buffer so that we can read outside the selection rect without problems. It must be equal to the sum of all values in offsets.

Definition at line 183 of file kis_selection_filters.h.

183{40};

The documentation for this class was generated from the following files: