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, ¤tEdge, &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 }