File indexing completed on 2024-05-05 04:21:07
0001 0002 /* 0003 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org> 0004 All rights reserved. 0005 0006 Redistribution and use in source and binary forms, with or without 0007 modification, are permitted provided that the following conditions 0008 are met: 0009 0010 1. Redistributions of source code must retain the above copyright 0011 notice, this list of conditions and the following disclaimer. 0012 2. Redistributions in binary form must reproduce the above copyright 0013 notice, this list of conditions and the following disclaimer in the 0014 documentation and/or other materials provided with the distribution. 0015 0016 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 0017 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 0018 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 0019 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 0020 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 0021 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 0022 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 0023 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 0024 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 0025 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 0026 */ 0027 0028 0029 #define DEBUG_KP_PAINTER 0 0030 0031 0032 #include "kpPainter.h" 0033 0034 #include "pixmapfx/kpPixmapFX.h" 0035 #include "tools/kpTool.h" 0036 #include "tools/flow/kpToolFlowBase.h" 0037 0038 #include <cstdio> 0039 0040 #include <QPainter> 0041 #include <QRandomGenerator> 0042 0043 #include "kpLogCategories.h" 0044 0045 //--------------------------------------------------------------------- 0046 0047 // public static 0048 bool kpPainter::pointsAreCardinallyAdjacent (const QPoint &p, const QPoint &q) 0049 { 0050 int dx = qAbs (p.x () - q.x ()); 0051 int dy = qAbs (p.y () - q.y ()); 0052 0053 return (dx + dy == 1); 0054 } 0055 0056 //--------------------------------------------------------------------- 0057 0058 // public static 0059 QList <QPoint> kpPainter::interpolatePoints (const QPoint &startPoint, 0060 const QPoint &endPoint, 0061 bool cardinalAdjacency, 0062 double probability) 0063 { 0064 #if DEBUG_KP_PAINTER 0065 qCDebug(kpLogImagelib) << "CALL(startPoint=" << startPoint 0066 << ",endPoint=" << endPoint << ")"; 0067 #endif 0068 0069 QList <QPoint> ret; 0070 0071 Q_ASSERT (probability >= 0.0 && probability <= 1.0); 0072 const int probabilityTimes1000 = qRound (probability * 1000); 0073 #define SHOULD_DRAW() ( (probabilityTimes1000 == 1000) /*avoid QRandomGenerator call*/ || \ 0074 (QRandomGenerator::global()->bounded(1000) < probabilityTimes1000) ) 0075 0076 0077 // Derived from the zSprite2 Graphics Engine. 0078 // "MODIFIED" comment shows deviation from zSprite2 and Bresenham's line 0079 // algorithm. 0080 0081 const int x1 = startPoint.x (), 0082 y1 = startPoint.y (), 0083 x2 = endPoint.x (), 0084 y2 = endPoint.y (); 0085 0086 // Difference of x and y values 0087 const int dx = x2 - x1; 0088 const int dy = y2 - y1; 0089 0090 // Absolute values of differences 0091 const int ix = qAbs (dx); 0092 const int iy = qAbs (dy); 0093 0094 // Larger of the x and y differences 0095 const int inc = ix > iy ? ix : iy; 0096 0097 // Plot location 0098 int plotx = x1; 0099 int ploty = y1; 0100 0101 int x = 0; 0102 int y = 0; 0103 0104 if (SHOULD_DRAW ()) { 0105 ret.append (QPoint (plotx, ploty)); 0106 } 0107 0108 0109 for (int i = 0; i <= inc; i++) 0110 { 0111 // oldplotx is equally as valid but would look different 0112 // (but nobody will notice which one it is) 0113 const int oldploty = ploty; 0114 int plot = 0; 0115 0116 x += ix; 0117 y += iy; 0118 0119 if (x > inc) 0120 { 0121 plot++; 0122 x -= inc; 0123 0124 if (dx < 0) { 0125 plotx--; 0126 } 0127 else { 0128 plotx++; 0129 } 0130 } 0131 0132 if (y > inc) 0133 { 0134 plot++; 0135 y -= inc; 0136 0137 if (dy < 0) { 0138 ploty--; 0139 } 0140 else { 0141 ploty++; 0142 } 0143 } 0144 0145 if (plot) 0146 { 0147 if (cardinalAdjacency && plot == 2) 0148 { 0149 // MODIFIED: Every point is 0150 // horizontally or vertically adjacent to another point (if there 0151 // is more than 1 point, of course). This is in contrast to the 0152 // ordinary line algorithm which can create diagonal adjacencies. 0153 0154 if (SHOULD_DRAW ()) { 0155 ret.append (QPoint (plotx, oldploty)); 0156 } 0157 } 0158 0159 if (SHOULD_DRAW ()) { 0160 ret.append (QPoint (plotx, ploty)); 0161 } 0162 } 0163 } 0164 0165 #undef SHOULD_DRAW 0166 0167 return ret; 0168 } 0169 0170 //--------------------------------------------------------------------- 0171 0172 // public static 0173 void kpPainter::fillRect (kpImage *image, 0174 int x, int y, int width, int height, 0175 const kpColor &color) 0176 { 0177 kpPixmapFX::fillRect (image, x, y, width, height, color); 0178 } 0179 0180 //--------------------------------------------------------------------- 0181 0182 0183 // <rgbPainter> are operating on the original image 0184 // (the original image is not passed to this function). 0185 // 0186 // <image> = subset of the original image containing all the pixels in 0187 // <imageRect> 0188 // <drawRect> = the rectangle, relative to the painters, whose pixels we 0189 // want to change 0190 static bool ReadableImageWashRect (QPainter *rgbPainter, 0191 const QImage &image, 0192 const kpColor &colorToReplace, 0193 const QRect &imageRect, const QRect &drawRect, 0194 int processedColorSimilarity) 0195 { 0196 bool didSomething = false; 0197 0198 #if DEBUG_KP_PAINTER && 0 0199 qCDebug(kpLogImagelib) << "kppixmapfx.cpp:WashRect(imageRect=" << imageRect 0200 << ",drawRect=" << drawRect 0201 << ")" << endl; 0202 #endif 0203 0204 // If you're going to pass painter pointers, those painters had better be 0205 // active (i.e. QPainter::begin() has been called). 0206 Q_ASSERT (!rgbPainter || rgbPainter->isActive ()); 0207 0208 // make use of scanline coherence 0209 #define FLUSH_LINE() \ 0210 { \ 0211 if (rgbPainter) { \ 0212 if (startDrawX == x - 1) \ 0213 rgbPainter->drawPoint (startDrawX + imageRect.x (), \ 0214 y + imageRect.y ()); \ 0215 else \ 0216 rgbPainter->drawLine (startDrawX + imageRect.x (), \ 0217 y + imageRect.y (), \ 0218 x - 1 + imageRect.x (), \ 0219 y + imageRect.y ()); \ 0220 } \ 0221 didSomething = true; \ 0222 startDrawX = -1; \ 0223 } 0224 0225 const int maxY = drawRect.bottom () - imageRect.top (); 0226 0227 const int minX = drawRect.left () - imageRect.left (); 0228 const int maxX = drawRect.right () - imageRect.left (); 0229 0230 for (int y = drawRect.top () - imageRect.top (); 0231 y <= maxY; 0232 y++) 0233 { 0234 int startDrawX = -1; 0235 0236 int x; // for FLUSH_LINE() 0237 for (x = minX; x <= maxX; x++) 0238 { 0239 #if DEBUG_KP_PAINTER && 0 0240 fprintf (stderr, "y=%i x=%i colorAtPixel=%08X colorToReplace=%08X ... ", 0241 y, x, 0242 kpPixmapFX::getColorAtPixel (image, QPoint (x, y)).toQRgb (), 0243 colorToReplace.toQRgb ()); 0244 #endif 0245 if (kpPixmapFX::getColorAtPixel (image, QPoint (x, y)).isSimilarTo (colorToReplace, processedColorSimilarity)) 0246 { 0247 #if DEBUG_KP_PAINTER && 0 0248 fprintf (stderr, "similar\n"); 0249 #endif 0250 if (startDrawX < 0) { 0251 startDrawX = x; 0252 } 0253 } 0254 else 0255 { 0256 #if DEBUG_KP_PAINTER && 0 0257 fprintf (stderr, "different\n"); 0258 #endif 0259 if (startDrawX >= 0) { 0260 FLUSH_LINE (); 0261 } 0262 } 0263 } 0264 0265 if (startDrawX >= 0) { 0266 FLUSH_LINE (); 0267 } 0268 } 0269 0270 #undef FLUSH_LINE 0271 0272 return didSomething; 0273 } 0274 0275 //--------------------------------------------------------------------- 0276 0277 struct WashPack 0278 { 0279 QPoint startPoint, endPoint; 0280 kpColor color; 0281 int penWidth{}, penHeight{}; 0282 kpColor colorToReplace; 0283 int processedColorSimilarity{}; 0284 0285 QRect readableImageRect; 0286 QImage readableImage; 0287 }; 0288 0289 //--------------------------------------------------------------------- 0290 0291 static QRect Wash (kpImage *image, 0292 const QPoint &startPoint, const QPoint &endPoint, 0293 const kpColor &color, int penWidth, int penHeight, 0294 const kpColor &colorToReplace, 0295 int processedColorSimilarity, 0296 QRect (*drawFunc) (QPainter * /*rgbPainter*/, void * /*data*/)) 0297 { 0298 WashPack pack; 0299 pack.startPoint = startPoint; pack.endPoint = endPoint; 0300 pack.color = color; 0301 pack.penWidth = penWidth; pack.penHeight = penHeight; 0302 pack.colorToReplace = colorToReplace; 0303 pack.processedColorSimilarity = processedColorSimilarity; 0304 0305 0306 // Get the rectangle that bounds the changes and the pixmap for that 0307 // rectangle. 0308 const QRect normalizedRect = kpPainter::normalizedRect(pack.startPoint, pack.endPoint); 0309 pack.readableImageRect = kpTool::neededRect (normalizedRect, 0310 qMax (pack.penWidth, pack.penHeight)); 0311 #if DEBUG_KP_PAINTER 0312 qCDebug(kpLogImagelib) << "kppainter.cpp:Wash() startPoint=" << startPoint 0313 << " endPoint=" << endPoint 0314 << " --> normalizedRect=" << normalizedRect 0315 << " readableImageRect=" << pack.readableImageRect 0316 << endl; 0317 #endif 0318 pack.readableImage = kpPixmapFX::getPixmapAt (*image, pack.readableImageRect); 0319 0320 QPainter painter(image); 0321 return (*drawFunc)(&painter, &pack); 0322 } 0323 0324 //--------------------------------------------------------------------- 0325 0326 void WashHelperSetup (QPainter *rgbPainter, const WashPack *pack) 0327 { 0328 // Set the drawing colors for the painters. 0329 0330 if (rgbPainter) { 0331 rgbPainter->setPen (pack->color.toQColor()); 0332 } 0333 } 0334 0335 //--------------------------------------------------------------------- 0336 0337 static QRect WashLineHelper (QPainter *rgbPainter, void *data) 0338 { 0339 #if DEBUG_KP_PAINTER && 0 0340 qCDebug(kpLogImagelib) << "Washing pixmap (w=" << rect.width () 0341 << ",h=" << rect.height () << ")" << endl; 0342 QTime timer; 0343 int convAndWashTime; 0344 #endif 0345 0346 auto *pack = static_cast <WashPack *> (data); 0347 0348 // Setup painters. 0349 ::WashHelperSetup (rgbPainter, pack); 0350 0351 0352 bool didSomething = false; 0353 0354 const QList <QPoint> points = kpPainter::interpolatePoints (pack->startPoint, pack->endPoint); 0355 for (const QPoint &p : points) 0356 { 0357 // OPT: This may be reading and possibly writing pixels that were 0358 // visited on a previous iteration, since the pen is usually 0359 // bigger than 1 pixel. Maybe we could use QRegion to determine 0360 // all the non-intersecting regions and only wash each region once. 0361 // 0362 // Profiling needs to be done as QRegion is known to be a CPU hog. 0363 if (::ReadableImageWashRect (rgbPainter, 0364 pack->readableImage, 0365 pack->colorToReplace, 0366 pack->readableImageRect, 0367 kpToolFlowBase::hotRectForMousePointAndBrushWidthHeight ( 0368 p, pack->penWidth, pack->penHeight), 0369 pack->processedColorSimilarity)) 0370 { 0371 didSomething = true; 0372 } 0373 } 0374 0375 0376 #if DEBUG_KP_PAINTER && 0 0377 int ms = timer.restart (); 0378 qCDebug(kpLogImagelib) << "\ttried to wash: " << ms << "ms" 0379 << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) 0380 << " pixels/ms)" 0381 << endl; 0382 convAndWashTime += ms; 0383 #endif 0384 0385 0386 // TODO: Rectangle may be too big. Use QRect::united() incrementally? 0387 // Efficiency? 0388 return didSomething ? pack->readableImageRect : QRect (); 0389 } 0390 0391 //--------------------------------------------------------------------- 0392 0393 // public static 0394 QRect kpPainter::washLine (kpImage *image, 0395 int x1, int y1, int x2, int y2, 0396 const kpColor &color, int penWidth, int penHeight, 0397 const kpColor &colorToReplace, 0398 int processedColorSimilarity) 0399 { 0400 return ::Wash (image, 0401 QPoint (x1, y1), QPoint (x2, y2), 0402 color, penWidth, penHeight, 0403 colorToReplace, 0404 processedColorSimilarity, 0405 &::WashLineHelper); 0406 } 0407 0408 //--------------------------------------------------------------------- 0409 0410 static QRect WashRectHelper (QPainter *rgbPainter, void *data) 0411 { 0412 auto *pack = static_cast <WashPack *> (data); 0413 #if DEBUG_KP_PAINTER && 0 0414 qCDebug(kpLogImagelib) << "Washing pixmap (w=" << rect.width () 0415 << ",h=" << rect.height () << ")" << endl; 0416 QTime timer; 0417 int convAndWashTime; 0418 #endif 0419 0420 // Setup painters. 0421 ::WashHelperSetup (rgbPainter, pack); 0422 0423 0424 const QRect drawRect (pack->startPoint, pack->endPoint); 0425 0426 bool didSomething = false; 0427 0428 if (::ReadableImageWashRect (rgbPainter, 0429 pack->readableImage, 0430 pack->colorToReplace, 0431 pack->readableImageRect, 0432 drawRect, 0433 pack->processedColorSimilarity)) 0434 { 0435 didSomething = true; 0436 } 0437 0438 0439 #if DEBUG_KP_PAINTER && 0 0440 int ms = timer.restart (); 0441 qCDebug(kpLogImagelib) << "\ttried to wash: " << ms << "ms" 0442 << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) 0443 << " pixels/ms)" 0444 << endl; 0445 convAndWashTime += ms; 0446 #endif 0447 0448 0449 return didSomething ? drawRect : QRect (); 0450 } 0451 0452 //--------------------------------------------------------------------- 0453 0454 // public static 0455 QRect kpPainter::washRect (kpImage *image, 0456 int x, int y, int width, int height, 0457 const kpColor &color, 0458 const kpColor &colorToReplace, 0459 int processedColorSimilarity) 0460 { 0461 return ::Wash (image, 0462 QPoint (x, y), QPoint (x + width - 1, y + height - 1), 0463 color, 1/*pen width*/, 1/*pen height*/, 0464 colorToReplace, 0465 processedColorSimilarity, 0466 &::WashRectHelper); 0467 } 0468 0469 //--------------------------------------------------------------------- 0470 0471 // public static 0472 void kpPainter::sprayPoints (kpImage *image, 0473 const QList <QPoint> &points, 0474 const kpColor &color, 0475 int spraycanSize) 0476 { 0477 #if DEBUG_KP_PAINTER 0478 qCDebug(kpLogImagelib) << "kpPainter::sprayPoints()"; 0479 #endif 0480 0481 Q_ASSERT (spraycanSize > 0); 0482 0483 QPainter painter(image); 0484 const int radius = spraycanSize / 2; 0485 0486 // Set the drawing colors for the painters. 0487 0488 painter.setPen(color.toQColor()); 0489 0490 for (const auto &p : points) 0491 { 0492 for (int i = 0; i < 10; i++) 0493 { 0494 const int dx = (QRandomGenerator::global()->generate() % spraycanSize) - radius; 0495 const int dy = (QRandomGenerator::global()->generate() % spraycanSize) - radius; 0496 0497 // Make it look circular. 0498 // TODO: Can be done better by doing a random vector angle & length 0499 // but would sin and cos be too slow? 0500 if ((dx * dx) + (dy * dy) > (radius * radius)) { 0501 continue; 0502 } 0503 0504 const QPoint p2 (p.x () + dx, p.y () + dy); 0505 0506 painter.drawPoint(p2); 0507 } 0508 } 0509 } 0510 0511 //---------------------------------------------------------------------