File indexing completed on 2024-05-12 15:58:30

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2007, 2010 Sven Langkamp <sven.langkamp@gmail.com>
0004  *
0005  *  Outline algorithm based of the limn of fontutils
0006  *  SPDX-FileCopyrightText: 1992 Karl Berry <karl@cs.umb.edu>
0007  *  SPDX-FileCopyrightText: 1992 Kathryn Hargreaves <letters@cs.umb.edu>
0008  *
0009  *  SPDX-License-Identifier: GPL-2.0-or-later
0010  */
0011 
0012 #include "kis_outline_generator.h"
0013 #include <KoColorSpace.h>
0014 #include <KoColorSpaceRegistry.h>
0015 
0016 #include "kis_paint_device.h"
0017 #include <kis_iterator_ng.h>
0018 #include <kis_random_accessor_ng.h>
0019 
0020 class LinearStorage
0021 {
0022 public:
0023     typedef quint8* StorageType;
0024 public:
0025     LinearStorage(quint8 *buffer, int width, int height, int pixelSize)
0026         : m_buffer(buffer),
0027           m_width(width),
0028           m_pixelSize(pixelSize)
0029     {
0030         m_marks.reset(new quint8[width * height]);
0031         memset(m_marks.data(), 0, width * height);
0032     }
0033 
0034     quint8* pickPixel(int x, int y) {
0035         return m_buffer + (m_width * y + x) * m_pixelSize;
0036     }
0037 
0038     quint8* pickMark(int x, int y) {
0039         return m_marks.data() + m_width * y + x;
0040     }
0041 
0042 private:
0043     QScopedArrayPointer<quint8> m_marks;
0044     quint8 *m_buffer;
0045     int m_width;
0046     int m_pixelSize;
0047 };
0048 
0049 class PaintDeviceStorage
0050 {
0051 public:
0052     typedef const KisPaintDevice* StorageType;
0053 public:
0054     PaintDeviceStorage(const KisPaintDevice *device, int /*width*/, int /*height*/, int /*pixelSize*/)
0055         : m_device(device)
0056     {
0057         m_deviceIt = m_device->createRandomConstAccessorNG();
0058 
0059         const KoColorSpace *alphaCs = KoColorSpaceRegistry::instance()->alpha8();
0060         m_marks = new KisPaintDevice(alphaCs);
0061         m_marksIt = m_marks->createRandomAccessorNG();
0062     }
0063 
0064     const quint8* pickPixel(int x, int y) {
0065         m_deviceIt->moveTo(x, y);
0066         return m_deviceIt->rawDataConst();
0067     }
0068 
0069     quint8* pickMark(int x, int y) {
0070         m_marksIt->moveTo(x, y);
0071         return m_marksIt->rawData();
0072     }
0073 
0074 private:
0075     KisPaintDeviceSP m_marks;
0076     const KisPaintDevice *m_device;
0077     KisRandomConstAccessorSP m_deviceIt;
0078     KisRandomAccessorSP m_marksIt;
0079 };
0080 
0081 
0082 /******************* class KisOutlineGenerator *******************/
0083 
0084 KisOutlineGenerator::KisOutlineGenerator(const KoColorSpace* cs, quint8 defaultOpacity)
0085     : m_cs(cs), m_defaultOpacity(defaultOpacity), m_simple(false)
0086 {
0087 }
0088 
0089 template <class StorageStrategy>
0090 QVector<QPolygon> KisOutlineGenerator::outlineImpl(typename StorageStrategy::StorageType buffer,
0091                                                    qint32 xOffset, qint32 yOffset,
0092                                                    qint32 width, qint32 height)
0093 {
0094     QVector<QPolygon> paths;
0095 
0096     try {
0097         StorageStrategy storage(buffer, width, height, m_cs->pixelSize());
0098 
0099         for (qint32 y = 0; y < height; y++) {
0100             for (qint32 x = 0; x < width; x++) {
0101 
0102                 if (m_cs->opacityU8(storage.pickPixel(x, y)) == m_defaultOpacity)
0103                     continue;
0104 
0105                 const EdgeType initialEdge = TopEdge;
0106 
0107                 EdgeType startEdge = initialEdge;
0108                 while (startEdge != NoEdge &&
0109                        (*storage.pickMark(x, y) & (1 << startEdge) ||
0110                         !isOutlineEdge(storage, startEdge, x, y, width, height))) {
0111 
0112                     startEdge = nextEdge(startEdge);
0113                     if (startEdge == initialEdge)
0114                         startEdge = NoEdge;
0115                 }
0116 
0117                 if (startEdge != NoEdge) {
0118                     QPolygon path;
0119                     const bool clockwise = startEdge == BottomEdge;
0120 
0121                     qint32 row = y, col = x;
0122                     EdgeType currentEdge = startEdge;
0123                     EdgeType lastEdge = NoEdge;
0124 
0125                     if (currentEdge == BottomEdge) {
0126                         appendCoordinate(&path, col + xOffset, row + yOffset, currentEdge, lastEdge);
0127                         lastEdge = BottomEdge;
0128                     }
0129 
0130                     forever {
0131                         //qDebug() << "visit" << xOffset + col << yOffset + row << ppVar(currentEdge) << ppVar(lastEdge);
0132 
0133                         *storage.pickMark(col, row) |= 1 << currentEdge;
0134                         nextOutlineEdge(storage, &currentEdge, &row, &col, width, height);
0135 
0136                         //While following a straight line no points need to be added
0137                         if (lastEdge != currentEdge) {
0138                             appendCoordinate(&path, col + xOffset, row + yOffset, currentEdge, lastEdge);
0139                             lastEdge = currentEdge;
0140                         }
0141 
0142                         if (row == y && col == x && currentEdge == startEdge) {
0143                             if (startEdge != BottomEdge) {
0144                                 // add last point of the polygon
0145                                 appendCoordinate(&path, col + xOffset, row + yOffset, NoEdge, NoEdge);
0146                             }
0147                             break;
0148                         }
0149                     }
0150 
0151                     if(!m_simple || !clockwise) {
0152                         paths.push_back(path);
0153                     }
0154                 }
0155             }
0156         }
0157     }
0158     catch(const std::bad_alloc&) {
0159         warnKrita << "KisOutlineGenerator::outline ran out of memory allocating " <<  width << "*" << height << "marks";
0160     }
0161 
0162     return paths;
0163 }
0164 
0165 QVector<QPolygon> KisOutlineGenerator::outline(quint8* buffer, qint32 xOffset, qint32 yOffset, qint32 width, qint32 height)
0166 {
0167     return outlineImpl<LinearStorage>(buffer, xOffset, yOffset, width, height);
0168 }
0169 
0170 QVector<QPolygon> KisOutlineGenerator::outline(const KisPaintDevice *buffer, qint32 xOffset, qint32 yOffset, qint32 width, qint32 height)
0171 {
0172     return outlineImpl<PaintDeviceStorage>(buffer, xOffset, yOffset, width, height);
0173 }
0174 
0175 template <class StorageStrategy>
0176 bool KisOutlineGenerator::isOutlineEdge(StorageStrategy &storage, EdgeType edge, qint32 x, qint32 y, qint32 bufWidth, qint32 bufHeight)
0177 {
0178     if (m_cs->opacityU8(storage.pickPixel(x, y)) == m_defaultOpacity)
0179         return false;
0180 
0181     switch (edge) {
0182     case LeftEdge:
0183         return x == 0 || m_cs->opacityU8(storage.pickPixel(x - 1, y)) == m_defaultOpacity;
0184     case TopEdge:
0185         return y == 0 || m_cs->opacityU8(storage.pickPixel(x, y - 1)) == m_defaultOpacity;
0186     case RightEdge:
0187         return x == bufWidth - 1 || m_cs->opacityU8(storage.pickPixel(x + 1, y)) == m_defaultOpacity;
0188     case BottomEdge:
0189         return y == bufHeight - 1 || m_cs->opacityU8(storage.pickPixel(x, y + 1)) == m_defaultOpacity;
0190     case NoEdge:
0191         return false;
0192     }
0193     return false;
0194 }
0195 
0196 #define TRY_PIXEL(deltaRow, deltaCol, test_edge)                                                \
0197     {                                                                                               \
0198         int test_row = *row + deltaRow;                                                             \
0199         int test_col = *col + deltaCol;                                                             \
0200         if ( (0 <= (test_row) && (test_row) < height && 0 <= (test_col) && (test_col) < width) &&   \
0201              isOutlineEdge (storage, test_edge, test_col, test_row, width, height)) \
0202         {                                                                                           \
0203             *row = test_row;                                                                        \
0204             *col = test_col;                                                                        \
0205             *edge = test_edge;                                                                      \
0206             break;                                                                                  \
0207         }                                                                                       \
0208     }
0209 
0210 template <class StorageStrategy>
0211 void KisOutlineGenerator::nextOutlineEdge(StorageStrategy &storage, EdgeType *edge, qint32 *row, qint32 *col, qint32 width, qint32 height)
0212 {
0213     int original_row = *row;
0214     int original_col = *col;
0215 
0216     switch (*edge) {
0217     case RightEdge:
0218         TRY_PIXEL(-1, 0, RightEdge);
0219         TRY_PIXEL(-1, 1, BottomEdge);
0220         break;
0221 
0222     case TopEdge:
0223         TRY_PIXEL(0, -1, TopEdge);
0224         TRY_PIXEL(-1, -1, RightEdge);
0225         break;
0226 
0227     case LeftEdge:
0228         TRY_PIXEL(1, 0, LeftEdge);
0229         TRY_PIXEL(1, -1, TopEdge);
0230         break;
0231 
0232     case BottomEdge:
0233         TRY_PIXEL(0, 1, BottomEdge);
0234         TRY_PIXEL(1, 1, LeftEdge);
0235         break;
0236 
0237     default:
0238         break;
0239 
0240     }
0241 
0242     if (*row == original_row && *col == original_col)
0243         *edge = nextEdge(*edge);
0244 }
0245 
0246 void KisOutlineGenerator::appendCoordinate(QPolygon * path, int x, int y, EdgeType edge, EdgeType prevEdge)
0247 {
0248     Q_UNUSED(prevEdge);
0249 
0250     //const QPoint origPt(x, y);
0251 
0252     if (edge == TopEdge) {
0253         x++;
0254     } else if (edge == BottomEdge) {
0255         y++;
0256     } else if (edge == RightEdge) {
0257         x++;
0258         y++;
0259     }
0260 
0261     //qDebug() <<"add" << ppVar(origPt) << ppVar(edge) << ppVar(prevEdge) << "-->" << QPoint(x, y);
0262 
0263     *path << QPoint(x, y);
0264 }
0265 
0266 void KisOutlineGenerator::setSimpleOutline(bool simple)
0267 {
0268     m_simple = simple;
0269 }