File indexing completed on 2024-12-22 04:16:10

0001 /*
0002  *  SPDX-FileCopyrightText: 2008, 2009, 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0003  *  SPDX-FileCopyrightText: 2010 José Luis Vergara <pentalis@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "hatching_brush.h"
0009 
0010 #include <KoColor.h>
0011 #include <KoColorTransformation.h>
0012 
0013 #include <QVariant>
0014 #include <QHash>
0015 
0016 #include "kis_random_accessor_ng.h"
0017 #include <cmath>
0018 #include <time.h>
0019 
0020 
0021 void inline myround(double *x)
0022 {
0023     *x = ((*x - floor(*x)) >= 0.5) ? ceil(*x) : floor(*x);
0024 }
0025 
0026 HatchingBrush::HatchingBrush(KisHatchingPaintOpSettingsSP settings)
0027   : m_settings(settings)
0028   , separation(m_settings->separation)
0029   , origin_x(m_settings->origin_x)
0030   , origin_y(m_settings->origin_y)
0031 {
0032 }
0033 
0034 
0035 HatchingBrush::~HatchingBrush()
0036 {
0037 }
0038 
0039 void HatchingBrush::init()
0040 {
0041 }
0042 
0043 void HatchingBrush::hatch(KisPaintDeviceSP dev, qreal x, qreal y, double width, double height, double givenAngle, const KoColor &color, qreal additionalScale)
0044 {
0045     m_painter.begin(dev);
0046     m_painter.setFillStyle(KisPainter::FillStyleForegroundColor);
0047     m_painter.setPaintColor(color);
0048     m_painter.setBackgroundColor(color);
0049 
0050     angle = givenAngle;
0051     double tempthickness = m_settings->thickness * m_settings->thicknesssensorvalue;
0052     thickness = qMax(1, qRound(additionalScale * tempthickness));
0053     separation = additionalScale *
0054         (m_settings->enabledcurveseparation ?
0055          separationAsFunctionOfParameter(m_settings->separationsensorvalue, m_settings->separation, m_settings->separationintervals) :
0056          m_settings->separation);
0057 
0058     height_ = height;
0059     width_ = width;
0060 
0061     m_painter.setMaskImageSize(width_, height_);
0062 
0063     /*  dx and dy are the separation between lines in the x and y axis
0064     dx = separation / sin(angle*M_PI/180);     csc = 1/sin(angle)  */
0065     dy = fabs(separation / cos(angle * M_PI / 180)); // sec = 1/cos(angle)
0066     // I took the absolute value to avoid confusions with negative numbers
0067 
0068     if (!m_settings->subpixelprecision)
0069         modf(dy, &dy);
0070 
0071     // Exception for vertical lines, for which a tangent does not exist
0072     if ((angle == 90) || (angle == -90)) {
0073         verticalHotX = fmod((origin_x - x), separation);
0074 
0075         iterateVerticalLines(true, 1, false);    // Forward
0076         iterateVerticalLines(true, 0, true);     // In Between both
0077         iterateVerticalLines(false, 1, false);   // Backward
0078     }
0079     else {
0080         // Turn Angle + Point into Slope + Intercept
0081         slope = tan(angle * M_PI / 180);                // Angle into slope
0082         baseLineIntercept = origin_y - slope * origin_x; // Slope and Point of the Base Line into Intercept
0083         cursorLineIntercept = y - slope * x;
0084         hotIntercept = fmod((baseLineIntercept - cursorLineIntercept), dy);  // This hotIntercept belongs to a line that intersects with the hatching area
0085 
0086         iterateLines(true, 1, false);    // Forward
0087         iterateLines(true, 0, true);     // In Between both
0088         iterateLines(false, 1, false);   // Backward
0089         // I tried to make this cleaner but there's too many possibilities to be
0090         // worth the micromanagement to optimize
0091     }
0092 }
0093 
0094 void HatchingBrush::iterateLines(bool forward, int lineindex, bool oneline)
0095 {
0096     //---Preparations before the loop---
0097 
0098     double xdraw[2] = {0, 0};
0099     double ydraw[2] = {0, 0};
0100     //points A and B of the segments to trace
0101     QPointF A, B;
0102     int append_index = 0;
0103     bool remaininginnerlines = true;
0104 
0105     while (remaininginnerlines) {
0106 
0107         //---------START INTERSECTION POINT VERIFICATION--------
0108 
0109         append_index = 0;
0110         remaininginnerlines = false; // We assume there's no more lines unless proven contrary
0111         if (forward)
0112             scanIntercept = hotIntercept + dy * lineindex; // scanIntercept will represent the Intercept of the current line
0113         else
0114             scanIntercept = hotIntercept - dy * lineindex;  // scanIntercept will represent the Intercept of the current line
0115 
0116         lineindex++; // We are descending vertically out of convenience, see blog entry at pentalis.org/kritablog
0117 
0118         /*
0119         Explanation: only 2 out of the 4 segments can include limit values
0120         to verify intersections, otherwise we could encounter a situation where
0121         our new lines intersect with all 4 segments and is still considered an
0122         inner line (for example, a line that goes from corner to corner), thus
0123         triggering an error. The idea is of the algorithm is that only 2 intersections
0124         at most are considered at a time. Graphically this is indistinguishable, it's
0125         just there to avoid making unnecessary control structures (like additional "ifs").
0126         */
0127 
0128         if ((scanIntercept >= 0) && (scanIntercept <= height_)) {
0129             xdraw[append_index] = 0;
0130             ydraw[append_index] = scanIntercept;       //intersection at left
0131             remaininginnerlines = true;
0132             append_index++;
0133         }
0134 
0135         if ((slope * width_ + scanIntercept <= height_) && (slope * width_ + scanIntercept >= 0)) {
0136             xdraw[append_index] = width_;
0137             ydraw[append_index] = scanIntercept + slope * width_; //intersection at right
0138             remaininginnerlines = true;
0139             append_index++;
0140         }
0141 
0142         if ((-scanIntercept / slope > 0) && (-scanIntercept / slope < width_)) {
0143             xdraw[append_index] = -scanIntercept / slope;
0144             ydraw[append_index] = 0;       //intersection at top
0145             remaininginnerlines = true;
0146             append_index++;
0147         }
0148 
0149         if (((height_ - scanIntercept) / slope > 0) && ((height_ - scanIntercept) / slope < width_)) {
0150             xdraw[append_index] = (height_ - scanIntercept) / slope;
0151             ydraw[append_index] = height_;       //intersection at bottom
0152             remaininginnerlines = true;
0153             append_index++;
0154         }
0155         //--------END INTERSECTION POINT VERIFICATION---------
0156 
0157         if (!remaininginnerlines)
0158             break;
0159 
0160         if (!m_settings->subpixelprecision) {
0161             myround(&xdraw[0]);
0162             myround(&xdraw[1]);
0163             myround(&ydraw[0]);
0164             myround(&ydraw[1]);
0165         }
0166 
0167         A.setX(xdraw[0]);
0168         A.setY(ydraw[0]);
0169 
0170         // If 2 lines intersect with the dab square
0171         if (append_index == 2) {
0172             B.setX(xdraw[1]);
0173             B.setY(ydraw[1]);
0174 
0175             if (m_settings->antialias)
0176                 m_painter.drawThickLine(A, B, thickness, thickness);
0177             else
0178                 m_painter.drawLine(A, B, thickness, false);    //testing no subpixel;
0179 
0180             if (oneline)
0181                 break;
0182         }
0183         else {
0184             continue;
0185             /*Drawing points at the vertices causes inconsistent results due to
0186             floating point calculations not being quite in sync with algebra,
0187             therefore if I have only 1 intersection (= corner = this case),
0188             don't draw*/
0189         }
0190     }
0191 }
0192 
0193 void HatchingBrush::iterateVerticalLines(bool forward, int lineindex, bool oneline)
0194 {
0195     //---Preparations before the loop---
0196 
0197     double xdraw = 0;
0198     double ydraw[2] = {0, height_};
0199     //points A and B of the segments to trace
0200     QPointF A, B;
0201     bool remaininginnerlines = true;
0202 
0203     while (remaininginnerlines) {
0204 
0205         //---------START INTERSECTION POINT VERIFICATION--------
0206         remaininginnerlines = false;     // We assume there's no more lines unless proven contrary
0207         if (forward)
0208             verticalScanX = verticalHotX + separation * lineindex;
0209         else
0210             verticalScanX = verticalHotX - separation * lineindex;
0211 
0212         lineindex++;
0213 
0214         /*Read the explanation in HatchingBrush::iterateLines for more information*/
0215 
0216         if ((verticalScanX >= 0) && (verticalScanX <= width_)) {
0217             xdraw = verticalScanX;
0218             remaininginnerlines = true;
0219         }
0220         //--------END INTERSECTION POINT VERIFICATION---------
0221 
0222         if (!remaininginnerlines)
0223             break;
0224 
0225         if (!m_settings->subpixelprecision) {
0226             myround(&xdraw);
0227             myround(&ydraw[1]);
0228         }
0229 
0230         A.setX(xdraw);
0231         A.setY(ydraw[0]);
0232         B.setX(xdraw);
0233         B.setY(ydraw[1]);
0234 
0235         if (m_settings->antialias)
0236             m_painter.drawThickLine(A, B, thickness, thickness);
0237         else
0238             m_painter.drawLine(A, B, thickness, false);    //testing no subpixel;
0239 
0240         if (oneline)
0241             break;
0242         else
0243             continue;
0244     }
0245 }
0246 
0247 double HatchingBrush::separationAsFunctionOfParameter(double parameter, double separation, int numintervals)
0248 {
0249     if ((numintervals < 2) || (numintervals > 7)) {
0250         dbgKrita << "Fix your function" << numintervals << "<> 2-7" ;
0251         return separation;
0252     }
0253 
0254     double sizeinterval = 1 / double(numintervals);
0255     double lowerlimit = 0;
0256     double upperlimit = 0;
0257     double factor = 0;
0258 
0259     int basefactor = numintervals / 2;
0260     // Make the base separation factor tend to greater instead of lesser numbers when numintervals is even
0261     if ((numintervals % 2) == 0)
0262         basefactor--;
0263 
0264     for (quint8 currentinterval = 0; currentinterval < numintervals; currentinterval++) {
0265         lowerlimit = upperlimit;
0266         upperlimit += sizeinterval;
0267         if (currentinterval == (numintervals - 1))
0268             upperlimit = 1;
0269         if ((parameter >= lowerlimit) && (parameter <= upperlimit)) {
0270             factor = pow(2.0, (basefactor - currentinterval));
0271             //dbgKrita << factor;
0272             return (separation * factor);
0273         }
0274     }
0275 
0276     dbgKrita << "Fix your function" << parameter << ">" << upperlimit ;
0277     return separation;
0278 }