Commit 8d6a37e2 authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

Fix a triangular brush outline for a 1px brush

This patch also fixes major code duplications in the
KisOutlineGenerator. Now everything is templated.

BUG:334408
parent 1e61bec9
......@@ -30,31 +30,106 @@
#include <kis_iterator_ng.h>
#include <kis_random_accessor_ng.h>
class LinearStorage
{
public:
typedef quint8* StorageType;
public:
LinearStorage(quint8 *buffer, int width, int height, int pixelSize)
: m_buffer(buffer),
m_width(width),
m_height(height),
m_pixelSize(pixelSize)
{
m_marks.reset(new quint8[width * height]);
memset(m_marks.data(), 0, width * height);
}
quint8* pickPixel(int x, int y) {
return m_buffer + (m_width * y + x) * m_pixelSize;
}
quint8* pickMark(int x, int y) {
return m_marks.data() + m_width * y + x;
}
private:
QScopedArrayPointer<quint8> m_marks;
quint8 *m_buffer;
int m_width;
int m_height;
int m_pixelSize;
};
class PaintDeviceStorage
{
public:
typedef const KisPaintDevice* StorageType;
public:
PaintDeviceStorage(const KisPaintDevice *device, int width, int height, int pixelSize)
: m_device(device),
m_width(width),
m_height(height),
m_pixelSize(pixelSize)
{
m_deviceIt = m_device->createRandomConstAccessorNG(0, 0);
const KoColorSpace *alphaCs = KoColorSpaceRegistry::instance()->alpha8();
m_marks = new KisPaintDevice(alphaCs);
m_marksIt = m_marks->createRandomAccessorNG(0, 0);
}
const quint8* pickPixel(int x, int y) {
m_deviceIt->moveTo(x, y);
return m_deviceIt->rawDataConst();
}
quint8* pickMark(int x, int y) {
m_marksIt->moveTo(x, y);
return m_marksIt->rawData();
}
private:
KisPaintDeviceSP m_marks;
const KisPaintDevice *m_device;
KisRandomConstAccessorSP m_deviceIt;
KisRandomAccessorSP m_marksIt;
int m_width;
int m_height;
int m_pixelSize;
};
/******************* class KisOutlineGenerator *******************/
KisOutlineGenerator::KisOutlineGenerator(const KoColorSpace* cs, quint8 defaultOpacity)
: m_cs(cs), m_defaultOpacity(defaultOpacity), m_simple(false)
{
}
QVector<QPolygon> KisOutlineGenerator::outline(quint8* buffer, qint32 xOffset, qint32 yOffset, qint32 width, qint32 height)
template <class StorageStrategy>
QVector<QPolygon> KisOutlineGenerator::outlineImpl(typename StorageStrategy::StorageType buffer,
qint32 xOffset, qint32 yOffset,
qint32 width, qint32 height)
{
QVector<QPolygon> paths;
try {
quint8* marks = new quint8[width*height];
memset(marks, 0, width *height);
StorageStrategy storage(buffer, width, height, m_cs->pixelSize());
int nodes = 0;
for (qint32 y = 0; y < height; y++) {
for (qint32 x = 0; x < width; x++) {
if (m_cs->opacityU8(buffer + (y*width+x)*m_cs->pixelSize()) == m_defaultOpacity)
if (m_cs->opacityU8(storage.pickPixel(x, y)) == m_defaultOpacity)
continue;
EdgeType startEdge = TopEdge;
EdgeType edge = startEdge;
while (edge != NoEdge && (marks[y*width+x] & (1 << edge) || !isOutlineEdge(edge, x, y, buffer, width, height))) {
while (edge != NoEdge &&
(*storage.pickMark(x, y) & (1 << edge) ||
!isOutlineEdge(storage, edge, x, y, width, height))) {
edge = nextEdge(edge);
if (edge == startEdge)
edge = NoEdge;
......@@ -69,24 +144,29 @@ QVector<QPolygon> KisOutlineGenerator::outline(quint8* buffer, qint32 xOffset, q
qint32 row = y, col = x;
EdgeType currentEdge = edge;
EdgeType lastEdge = currentEdge;
do {
forever {
//While following a straight line no points need to be added
if (lastEdge != currentEdge) {
appendCoordinate(&path, col + xOffset, row + yOffset, currentEdge);
nodes++;
lastEdge = currentEdge;
}
marks[row*width+col] |= 1 << currentEdge;
nextOutlineEdge(&currentEdge, &row, &col, buffer, width, height);
} while (row != y || col != x || currentEdge != edge);
*storage.pickMark(col, row) |= 1 << currentEdge;
nextOutlineEdge(storage, &currentEdge, &row, &col, width, height);
if (row == y && col == x && currentEdge == edge) {
// add last point of the polygon
appendCoordinate(&path, col + xOffset, row + yOffset, currentEdge);
break;
}
}
if(!m_simple || !clockwise)
paths.push_back(path);
}
}
}
delete[] marks;
}
catch(std::bad_alloc) {
warnKrita << "KisOutlineGenerator::outline ran out of memory allocating " << width << "*" << height << "marks";
......@@ -95,136 +175,43 @@ QVector<QPolygon> KisOutlineGenerator::outline(quint8* buffer, qint32 xOffset, q
return paths;
}
QVector<QPolygon> KisOutlineGenerator::outline(const KisPaintDevice *buffer, qint32 xOffset, qint32 yOffset, qint32 width, qint32 height)
QVector<QPolygon> KisOutlineGenerator::outline(quint8* buffer, qint32 xOffset, qint32 yOffset, qint32 width, qint32 height)
{
m_xOffset = xOffset;
m_yOffset = yOffset;
m_accessor = buffer->createRandomConstAccessorNG(xOffset, yOffset);
const KoColorSpace *alphaCs = KoColorSpaceRegistry::instance()->alpha8();
KisPaintDeviceSP marks = new KisPaintDevice(alphaCs);
quint8 defaultPixel = 0;
marks->setDefaultPixel(&defaultPixel);
KisRandomAccessorSP marksAccessor = marks->createRandomAccessorNG(0, 0);
QVector<QPolygon> paths;
int nodes = 0;
for (qint32 y = 0; y < height; y++) {
for (qint32 x = 0; x < width; x++) {
m_accessor->moveTo(x + xOffset, y + yOffset);
if (m_cs->opacityU8(m_accessor->oldRawData()) == m_defaultOpacity)
continue;
EdgeType startEdge = TopEdge;
EdgeType edge = startEdge;
marksAccessor->moveTo(x, y);
while (edge != NoEdge && (alphaCs->opacityU8(marksAccessor->rawData()) & (1 << edge) || !isOutlineEdge(edge, x, y, buffer, width, height))) {
edge = nextEdge(edge);
if (edge == startEdge)
edge = NoEdge;
}
if (edge != NoEdge) {
QPolygon path;
path << QPoint(x + xOffset, y + yOffset);
bool clockwise = edge == BottomEdge;
qint32 row = y, col = x;
EdgeType currentEdge = edge;
EdgeType lastEdge = currentEdge;
do {
//While following a straight line no points need to be added
if (lastEdge != currentEdge) {
appendCoordinate(&path, col + xOffset, row + yOffset, currentEdge);
nodes++;
lastEdge = currentEdge;
}
marksAccessor->moveTo(col, row);
quint8 mark = alphaCs->opacityU8(marksAccessor->rawData());
mark |= 1 << currentEdge;
alphaCs->setOpacity(marksAccessor->rawData(), mark, 1);
nextOutlineEdge(&currentEdge, &row, &col, buffer, width, height);
} while (row != y || col != x || currentEdge != edge);
if(!m_simple || !clockwise)
paths.push_back(path);
}
}
}
return paths;
return outlineImpl<LinearStorage>(buffer, xOffset, yOffset, width, height);
}
bool KisOutlineGenerator::isOutlineEdge(EdgeType edge, qint32 x, qint32 y, quint8* buffer, qint32 bufWidth, qint32 bufHeight)
QVector<QPolygon> KisOutlineGenerator::outline(const KisPaintDevice *buffer, qint32 xOffset, qint32 yOffset, qint32 width, qint32 height)
{
if (m_cs->opacityU8(buffer + (y*bufWidth+x)*m_cs->pixelSize()) == m_defaultOpacity)
return false;
switch (edge) {
case LeftEdge:
return x == 0 || m_cs->opacityU8(buffer + (y*bufWidth+(x - 1))*m_cs->pixelSize()) == m_defaultOpacity;
case TopEdge:
return y == 0 || m_cs->opacityU8(buffer + ((y - 1)*bufWidth+x)*m_cs->pixelSize()) == m_defaultOpacity;
case RightEdge:
return x == bufWidth - 1 || m_cs->opacityU8(buffer + (y*bufWidth+(x + 1))*m_cs->pixelSize()) == m_defaultOpacity;
case BottomEdge:
return y == bufHeight - 1 || m_cs->opacityU8(buffer + ((y + 1)*bufWidth+x)*m_cs->pixelSize()) == m_defaultOpacity;
case NoEdge:
return false;
}
return false;
return outlineImpl<PaintDeviceStorage>(buffer, xOffset, yOffset, width, height);
}
bool KisOutlineGenerator::isOutlineEdge(EdgeType edge, qint32 x, qint32 y, const KisPaintDevice * /*buffer*/, qint32 bufWidth, qint32 bufHeight)
template <class StorageStrategy>
bool KisOutlineGenerator::isOutlineEdge(StorageStrategy &storage, EdgeType edge, qint32 x, qint32 y, qint32 bufWidth, qint32 bufHeight)
{
m_accessor->moveTo(x + m_xOffset, y + m_yOffset);
if (m_cs->opacityU8(m_accessor->oldRawData()) == m_defaultOpacity)
if (m_cs->opacityU8(storage.pickPixel(x, y)) == m_defaultOpacity)
return false;
switch (edge) {
case LeftEdge:
{
m_accessor->moveTo(x + m_xOffset - 1, y + m_yOffset);
return x == 0 || m_cs->opacityU8(m_accessor->oldRawData()) == m_defaultOpacity;
}
return x == 0 || m_cs->opacityU8(storage.pickPixel(x - 1, y)) == m_defaultOpacity;
case TopEdge:
{
m_accessor->moveTo(x + m_xOffset, y + m_yOffset - 1);
return y == 0 || m_cs->opacityU8(m_accessor->oldRawData()) == m_defaultOpacity;
}
return y == 0 || m_cs->opacityU8(storage.pickPixel(x, y - 1)) == m_defaultOpacity;
case RightEdge:
{
m_accessor->moveTo(x + m_xOffset + 1, y + m_yOffset);
return x == bufWidth - 1 || m_cs->opacityU8(m_accessor->oldRawData()) == m_defaultOpacity;
}
return x == bufWidth - 1 || m_cs->opacityU8(storage.pickPixel(x + 1, y)) == m_defaultOpacity;
case BottomEdge:
{
m_accessor->moveTo(x + m_xOffset, y + m_yOffset + 1);
return y == bufHeight - 1 || m_cs->opacityU8(m_accessor->oldRawData()) == m_defaultOpacity;
}
return y == bufHeight - 1 || m_cs->opacityU8(storage.pickPixel(x, y + 1)) == m_defaultOpacity;
case NoEdge:
return false;
}
return false;
}
#define TRY_PIXEL(deltaRow, deltaCol, test_edge) \
{ \
int test_row = *row + deltaRow; \
int test_col = *col + deltaCol; \
if ( (0 <= (test_row) && (test_row) < height && 0 <= (test_col) && (test_col) < width) && \
isOutlineEdge (test_edge, test_col, test_row, buffer, width, height)) \
isOutlineEdge (storage, test_edge, test_col, test_row, width, height)) \
{ \
*row = test_row; \
*col = test_col; \
......@@ -233,42 +220,8 @@ bool KisOutlineGenerator::isOutlineEdge(EdgeType edge, qint32 x, qint32 y, const
} \
}
void KisOutlineGenerator::nextOutlineEdge(EdgeType *edge, qint32 *row, qint32 *col, quint8* buffer, qint32 width, qint32 height)
{
int original_row = *row;
int original_col = *col;
switch (*edge) {
case RightEdge:
TRY_PIXEL(-1, 0, RightEdge);
TRY_PIXEL(-1, 1, BottomEdge);
break;
case TopEdge:
TRY_PIXEL(0, -1, TopEdge);
TRY_PIXEL(-1, -1, RightEdge);
break;
case LeftEdge:
TRY_PIXEL(1, 0, LeftEdge);
TRY_PIXEL(1, -1, TopEdge);
break;
case BottomEdge:
TRY_PIXEL(0, 1, BottomEdge);
TRY_PIXEL(1, 1, LeftEdge);
break;
default:
break;
}
if (*row == original_row && *col == original_col)
*edge = nextEdge(*edge);
}
void KisOutlineGenerator::nextOutlineEdge(EdgeType *edge, qint32 *row, qint32 *col, const KisPaintDevice *buffer, qint32 width, qint32 height)
template <class StorageStrategy>
void KisOutlineGenerator::nextOutlineEdge(StorageStrategy &storage, EdgeType *edge, qint32 *row, qint32 *col, qint32 width, qint32 height)
{
int original_row = *row;
int original_col = *col;
......@@ -303,7 +256,6 @@ void KisOutlineGenerator::nextOutlineEdge(EdgeType *edge, qint32 *row, qint32 *c
*edge = nextEdge(*edge);
}
void KisOutlineGenerator::appendCoordinate(QPolygon * path, int x, int y, EdgeType edge)
{
switch (edge) {
......@@ -322,6 +274,7 @@ void KisOutlineGenerator::appendCoordinate(QPolygon * path, int x, int y, EdgeTy
break;
}
*path << QPoint(x, y);
}
......
......@@ -66,24 +66,34 @@ public:
**/
void setSimpleOutline(bool simple);
private:
private:
enum EdgeType {
TopEdge = 1, LeftEdge = 2, BottomEdge = 3, RightEdge = 0, NoEdge = 4
};
bool isOutlineEdge(EdgeType edge, qint32 x, qint32 y, quint8* buffer, qint32 bufWidth, qint32 bufHeight);
bool isOutlineEdge(EdgeType edge, qint32 x, qint32 y, const KisPaintDevice *buffer, qint32 bufWidth, qint32 bufHeight);
template <class StorageStrategy>
QVector<QPolygon> outlineImpl(typename StorageStrategy::StorageType buffer,
qint32 xOffset, qint32 yOffset,
qint32 width, qint32 height);
template <class StorageStrategy>
bool isOutlineEdge(StorageStrategy &storage, EdgeType edge, qint32 x, qint32 y, qint32 bufWidth, qint32 bufHeight);
template <class StorageStrategy>
void nextOutlineEdge(StorageStrategy &storage, EdgeType *edge, qint32 *row, qint32 *col, qint32 width, qint32 height);
EdgeType nextEdge(EdgeType edge) {
return edge == NoEdge ? edge : static_cast<EdgeType>((edge + 1) % 4);
}
void nextOutlineEdge(EdgeType *edge, qint32 *row, qint32 *col, quint8* buffer, qint32 bufWidth, qint32 bufHeight);
void nextOutlineEdge(EdgeType *edge, qint32 *row, qint32 *col, const KisPaintDevice *buffer, qint32 bufWidth, qint32 bufHeight);
void appendCoordinate(QPolygon * path, int x, int y, EdgeType edge);
private:
const KoColorSpace* m_cs;
quint8 m_defaultOpacity;
bool m_simple;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment