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 }