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 //---------------------------------------------------------------------