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 974 of file kis_selection_filters.cpp.

978{
979 *spanEndDistance = 0;
980 *spanExtremeValid = true;
981 for (qint32 i = 0; i < numSteps; ++i) {
982 *spanEndDistance += offsets[i];
983 const qint32 row1 = currentScanlineIndex + (direction * *spanEndDistance * rowMultiplier);
984 const qint32 col1 = x + horizontalBorderSize + (direction * *spanEndDistance * colMultiplier);
985 const qint32 row2 = row1 + pixelOffset * colMultiplier;
986 const qint32 col2 = col1 + pixelOffset * rowMultiplier;
987 const quint8 *pixel1 = scanlines[row1] + col1;
988 const quint8 *pixel2 = scanlines[row2] + col2;
989 // Get how different are these edge pixels from the current pixels and
990 // stop searching if they are too different
991 *pixelDiff = ((*pixel1 + *pixel2) >> 1) - pixelAvg;
992 if (qAbs(*pixelDiff) > scaledGradient) {
993 // If this is the end of the span then check if the corner belongs
994 // to a jagged border or to a right angled part of the shape
995 qint32 pixelDiff2;
996 if ((currentPixelDiff < 0 && *pixelDiff < 0) || (currentPixelDiff > 0 && *pixelDiff > 0)) {
997 const qint32 row3 = row2 + pixelOffset * colMultiplier;
998 const qint32 col3 = col2 + pixelOffset * rowMultiplier;
999 const quint8 *pixel3 = scanlines[row3] + col3;
1000 pixelDiff2 = ((*pixel2 + *pixel3) >> 1) - pixelAvg;
1001 } else {
1002 const qint32 row3 = row1 - pixelOffset * colMultiplier;
1003 const qint32 col3 = col1 - pixelOffset * rowMultiplier;
1004 const quint8 *pixel3 = scanlines[row3] + col3;
1005 pixelDiff2 = ((*pixel1 + *pixel3) >> 1) - pixelAvg;
1006 }
1007 *spanExtremeValid = !(qAbs(pixelDiff2) > scaledGradient);
1008 break;
1009 }
1010 }
1011}
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 1013 of file kis_selection_filters.cpp.

1019{
1020 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, -1, pixelAvg, scaledGradient,
1021 currentPixelDiff, negativeSpanEndDistance, negativePixelDiff, negativeSpanExtremeValid);
1022 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, 1, pixelAvg, scaledGradient,
1023 currentPixelDiff, positiveSpanEndDistance, positivePixelDiff, positiveSpanExtremeValid);
1024}
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 933 of file kis_selection_filters.cpp.

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

References distance().

◆ name()

KUndo2MagicString KisAntiAliasSelectionFilter::name ( )
overridevirtual

Reimplemented from KisSelectionFilter.

Definition at line 928 of file kis_selection_filters.cpp.

929{
930 return kundo2_i18n("Anti-Alias Selection");
931}
KUndo2MagicString kundo2_i18n(const char *text)

References kundo2_i18n().

◆ process()

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

Implements KisSelectionFilter.

Definition at line 1026 of file kis_selection_filters.cpp.

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