| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2017 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the examples of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:BSD$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** BSD License Usage |
| 18 | ** Alternatively, you may use this file under the terms of the BSD license |
| 19 | ** as follows: |
| 20 | ** |
| 21 | ** "Redistribution and use in source and binary forms, with or without |
| 22 | ** modification, are permitted provided that the following conditions are |
| 23 | ** met: |
| 24 | ** * Redistributions of source code must retain the above copyright |
| 25 | ** notice, this list of conditions and the following disclaimer. |
| 26 | ** * Redistributions in binary form must reproduce the above copyright |
| 27 | ** notice, this list of conditions and the following disclaimer in |
| 28 | ** the documentation and/or other materials provided with the |
| 29 | ** distribution. |
| 30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
| 31 | ** contributors may be used to endorse or promote products derived |
| 32 | ** from this software without specific prior written permission. |
| 33 | ** |
| 34 | ** |
| 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
| 46 | ** |
| 47 | ** $QT_END_LICENSE$ |
| 48 | ** |
| 49 | ****************************************************************************/ |
| 50 | |
| 51 | #include "context2d.h" |
| 52 | |
| 53 | #include <QVariant> |
| 54 | #include <qmath.h> |
| 55 | |
| 56 | #define qClamp(val, min, max) qMin(qMax(val, min), max) |
| 57 | static QList<qreal> parseNumbersList(QString::const_iterator &itr) |
| 58 | { |
| 59 | QList<qreal> points; |
| 60 | QString temp; |
| 61 | while ((*itr).isSpace()) |
| 62 | ++itr; |
| 63 | while ((*itr).isNumber() || |
| 64 | (*itr) == '-' || (*itr) == '+' || (*itr) == '.') { |
| 65 | temp = QString(); |
| 66 | |
| 67 | if ((*itr) == '-') |
| 68 | temp += *itr++; |
| 69 | else if ((*itr) == '+') |
| 70 | temp += *itr++; |
| 71 | while ((*itr).isDigit()) |
| 72 | temp += *itr++; |
| 73 | if ((*itr) == '.') |
| 74 | temp += *itr++; |
| 75 | while ((*itr).isDigit()) |
| 76 | temp += *itr++; |
| 77 | while ((*itr).isSpace()) |
| 78 | ++itr; |
| 79 | if ((*itr) == ',') |
| 80 | ++itr; |
| 81 | points.append(t: temp.toDouble()); |
| 82 | //eat spaces |
| 83 | while ((*itr).isSpace()) |
| 84 | ++itr; |
| 85 | } |
| 86 | |
| 87 | return points; |
| 88 | } |
| 89 | |
| 90 | QColor colorFromString(const QString &name) |
| 91 | { |
| 92 | QString::const_iterator itr = name.constBegin(); |
| 93 | QList<qreal> compo; |
| 94 | if (name.startsWith(s: "rgba(" )) { |
| 95 | ++itr; ++itr; ++itr; ++itr; ++itr; |
| 96 | compo = parseNumbersList(itr); |
| 97 | if (compo.size() != 4) { |
| 98 | return QColor(); |
| 99 | } |
| 100 | //alpha seems to be always between 0-1 |
| 101 | compo[3] *= 255; |
| 102 | return QColor((int)compo[0], (int)compo[1], |
| 103 | (int)compo[2], (int)compo[3]); |
| 104 | } else if (name.startsWith(s: "rgb(" )) { |
| 105 | ++itr; ++itr; ++itr; ++itr; |
| 106 | compo = parseNumbersList(itr); |
| 107 | if (compo.size() != 3) { |
| 108 | return QColor(); |
| 109 | } |
| 110 | return QColor((int)qClamp(compo[0], qreal(0), qreal(255)), |
| 111 | (int)qClamp(compo[1], qreal(0), qreal(255)), |
| 112 | (int)qClamp(compo[2], qreal(0), qreal(255))); |
| 113 | } else { |
| 114 | //QRgb color; |
| 115 | //CSSParser::parseColor(name, color); |
| 116 | return QColor(name); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | |
| 121 | static QPainter::CompositionMode compositeOperatorFromString(const QString &compositeOperator) |
| 122 | { |
| 123 | if ( compositeOperator == "source-over" ) { |
| 124 | return QPainter::CompositionMode_SourceOver; |
| 125 | } else if ( compositeOperator == "source-out" ) { |
| 126 | return QPainter::CompositionMode_SourceOut; |
| 127 | } else if ( compositeOperator == "source-in" ) { |
| 128 | return QPainter::CompositionMode_SourceIn; |
| 129 | } else if ( compositeOperator == "source-atop" ) { |
| 130 | return QPainter::CompositionMode_SourceAtop; |
| 131 | } else if ( compositeOperator == "destination-atop" ) { |
| 132 | return QPainter::CompositionMode_DestinationAtop; |
| 133 | } else if ( compositeOperator == "destination-in" ) { |
| 134 | return QPainter::CompositionMode_DestinationIn; |
| 135 | } else if ( compositeOperator == "destination-out" ) { |
| 136 | return QPainter::CompositionMode_DestinationOut; |
| 137 | } else if ( compositeOperator == "destination-over" ) { |
| 138 | return QPainter::CompositionMode_DestinationOver; |
| 139 | } else if ( compositeOperator == "darker" ) { |
| 140 | return QPainter::CompositionMode_SourceOver; |
| 141 | } else if ( compositeOperator == "lighter" ) { |
| 142 | return QPainter::CompositionMode_SourceOver; |
| 143 | } else if ( compositeOperator == "copy" ) { |
| 144 | return QPainter::CompositionMode_Source; |
| 145 | } else if ( compositeOperator == "xor" ) { |
| 146 | return QPainter::CompositionMode_Xor; |
| 147 | } |
| 148 | |
| 149 | return QPainter::CompositionMode_SourceOver; |
| 150 | } |
| 151 | |
| 152 | static QString compositeOperatorToString(QPainter::CompositionMode op) |
| 153 | { |
| 154 | switch (op) { |
| 155 | case QPainter::CompositionMode_SourceOver: |
| 156 | return "source-over" ; |
| 157 | case QPainter::CompositionMode_DestinationOver: |
| 158 | return "destination-over" ; |
| 159 | case QPainter::CompositionMode_Clear: |
| 160 | return "clear" ; |
| 161 | case QPainter::CompositionMode_Source: |
| 162 | return "source" ; |
| 163 | case QPainter::CompositionMode_Destination: |
| 164 | return "destination" ; |
| 165 | case QPainter::CompositionMode_SourceIn: |
| 166 | return "source-in" ; |
| 167 | case QPainter::CompositionMode_DestinationIn: |
| 168 | return "destination-in" ; |
| 169 | case QPainter::CompositionMode_SourceOut: |
| 170 | return "source-out" ; |
| 171 | case QPainter::CompositionMode_DestinationOut: |
| 172 | return "destination-out" ; |
| 173 | case QPainter::CompositionMode_SourceAtop: |
| 174 | return "source-atop" ; |
| 175 | case QPainter::CompositionMode_DestinationAtop: |
| 176 | return "destination-atop" ; |
| 177 | case QPainter::CompositionMode_Xor: |
| 178 | return "xor" ; |
| 179 | case QPainter::CompositionMode_Plus: |
| 180 | return "plus" ; |
| 181 | case QPainter::CompositionMode_Multiply: |
| 182 | return "multiply" ; |
| 183 | case QPainter::CompositionMode_Screen: |
| 184 | return "screen" ; |
| 185 | case QPainter::CompositionMode_Overlay: |
| 186 | return "overlay" ; |
| 187 | case QPainter::CompositionMode_Darken: |
| 188 | return "darken" ; |
| 189 | case QPainter::CompositionMode_Lighten: |
| 190 | return "lighten" ; |
| 191 | case QPainter::CompositionMode_ColorDodge: |
| 192 | return "color-dodge" ; |
| 193 | case QPainter::CompositionMode_ColorBurn: |
| 194 | return "color-burn" ; |
| 195 | case QPainter::CompositionMode_HardLight: |
| 196 | return "hard-light" ; |
| 197 | case QPainter::CompositionMode_SoftLight: |
| 198 | return "soft-light" ; |
| 199 | case QPainter::CompositionMode_Difference: |
| 200 | return "difference" ; |
| 201 | case QPainter::CompositionMode_Exclusion: |
| 202 | return "exclusion" ; |
| 203 | default: |
| 204 | break; |
| 205 | } |
| 206 | return QString(); |
| 207 | } |
| 208 | |
| 209 | void Context2D::save() |
| 210 | { |
| 211 | m_stateStack.push(t: m_state); |
| 212 | } |
| 213 | |
| 214 | |
| 215 | void Context2D::restore() |
| 216 | { |
| 217 | if (!m_stateStack.isEmpty()) { |
| 218 | m_state = m_stateStack.pop(); |
| 219 | m_state.flags = AllIsFullOfDirt; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | |
| 224 | void Context2D::scale(qreal x, qreal y) |
| 225 | { |
| 226 | m_state.matrix.scale(sx: x, sy: y); |
| 227 | m_state.flags |= DirtyTransformationMatrix; |
| 228 | } |
| 229 | |
| 230 | |
| 231 | void Context2D::rotate(qreal angle) |
| 232 | { |
| 233 | m_state.matrix.rotate(a: qRadiansToDegrees(radians: angle)); |
| 234 | m_state.flags |= DirtyTransformationMatrix; |
| 235 | } |
| 236 | |
| 237 | |
| 238 | void Context2D::translate(qreal x, qreal y) |
| 239 | { |
| 240 | m_state.matrix.translate(dx: x, dy: y); |
| 241 | m_state.flags |= DirtyTransformationMatrix; |
| 242 | } |
| 243 | |
| 244 | |
| 245 | void Context2D::transform(qreal m11, qreal m12, qreal m21, qreal m22, |
| 246 | qreal dx, qreal dy) |
| 247 | { |
| 248 | QTransform mat(m11, m12, |
| 249 | m21, m22, |
| 250 | dx, dy); |
| 251 | m_state.matrix *= mat; |
| 252 | m_state.flags |= DirtyTransformationMatrix; |
| 253 | } |
| 254 | |
| 255 | |
| 256 | void Context2D::setTransform(qreal m11, qreal m12, qreal m21, qreal m22, |
| 257 | qreal dx, qreal dy) |
| 258 | { |
| 259 | QTransform mat(m11, m12, |
| 260 | m21, m22, |
| 261 | dx, dy); |
| 262 | m_state.matrix = mat; |
| 263 | m_state.flags |= DirtyTransformationMatrix; |
| 264 | } |
| 265 | |
| 266 | |
| 267 | QString Context2D::globalCompositeOperation() const |
| 268 | { |
| 269 | return compositeOperatorToString(op: m_state.globalCompositeOperation); |
| 270 | } |
| 271 | |
| 272 | void Context2D::setGlobalCompositeOperation(const QString &op) |
| 273 | { |
| 274 | QPainter::CompositionMode mode = |
| 275 | compositeOperatorFromString(compositeOperator: op); |
| 276 | m_state.globalCompositeOperation = mode; |
| 277 | m_state.flags |= DirtyGlobalCompositeOperation; |
| 278 | } |
| 279 | |
| 280 | QVariant Context2D::strokeStyle() const |
| 281 | { |
| 282 | return m_state.strokeStyle; |
| 283 | } |
| 284 | |
| 285 | void Context2D::setStrokeStyle(const QVariant &style) |
| 286 | { |
| 287 | if (style.canConvert<CanvasGradient>()) { |
| 288 | CanvasGradient cg = qvariant_cast<CanvasGradient>(v: style); |
| 289 | m_state.strokeStyle = cg.value; |
| 290 | } else { |
| 291 | QColor color = colorFromString(name: style.toString()); |
| 292 | m_state.strokeStyle = color; |
| 293 | } |
| 294 | m_state.flags |= DirtyStrokeStyle; |
| 295 | } |
| 296 | |
| 297 | QVariant Context2D::fillStyle() const |
| 298 | { |
| 299 | return m_state.fillStyle; |
| 300 | } |
| 301 | |
| 302 | //! [3] |
| 303 | void Context2D::setFillStyle(const QVariant &style) |
| 304 | { |
| 305 | if (style.canConvert<CanvasGradient>()) { |
| 306 | CanvasGradient cg = qvariant_cast<CanvasGradient>(v: style); |
| 307 | m_state.fillStyle = cg.value; |
| 308 | } else { |
| 309 | QColor color = colorFromString(name: style.toString()); |
| 310 | m_state.fillStyle = color; |
| 311 | } |
| 312 | m_state.flags |= DirtyFillStyle; |
| 313 | } |
| 314 | //! [3] |
| 315 | |
| 316 | qreal Context2D::globalAlpha() const |
| 317 | { |
| 318 | return m_state.globalAlpha; |
| 319 | } |
| 320 | |
| 321 | void Context2D::setGlobalAlpha(qreal alpha) |
| 322 | { |
| 323 | m_state.globalAlpha = alpha; |
| 324 | m_state.flags |= DirtyGlobalAlpha; |
| 325 | } |
| 326 | |
| 327 | |
| 328 | CanvasGradient Context2D::createLinearGradient(qreal x0, qreal y0, |
| 329 | qreal x1, qreal y1) |
| 330 | { |
| 331 | QLinearGradient g(x0, y0, x1, y1); |
| 332 | return CanvasGradient(g); |
| 333 | } |
| 334 | |
| 335 | |
| 336 | CanvasGradient Context2D::createRadialGradient(qreal x0, qreal y0, |
| 337 | qreal r0, qreal x1, |
| 338 | qreal y1, qreal r1) |
| 339 | { |
| 340 | QRadialGradient g(QPointF(x1, y1), r0+r1, QPointF(x0, y0)); |
| 341 | return CanvasGradient(g); |
| 342 | } |
| 343 | |
| 344 | qreal Context2D::lineWidth() const |
| 345 | { |
| 346 | return m_state.lineWidth; |
| 347 | } |
| 348 | |
| 349 | void Context2D::setLineWidth(qreal w) |
| 350 | { |
| 351 | m_state.lineWidth = w; |
| 352 | m_state.flags |= DirtyLineWidth; |
| 353 | } |
| 354 | |
| 355 | //! [0] |
| 356 | QString Context2D::lineCap() const |
| 357 | { |
| 358 | switch (m_state.lineCap) { |
| 359 | case Qt::FlatCap: |
| 360 | return "butt" ; |
| 361 | case Qt::SquareCap: |
| 362 | return "square" ; |
| 363 | case Qt::RoundCap: |
| 364 | return "round" ; |
| 365 | default: ; |
| 366 | } |
| 367 | return QString(); |
| 368 | } |
| 369 | |
| 370 | void Context2D::setLineCap(const QString &capString) |
| 371 | { |
| 372 | Qt::PenCapStyle style; |
| 373 | if (capString == "round" ) |
| 374 | style = Qt::RoundCap; |
| 375 | else if (capString == "square" ) |
| 376 | style = Qt::SquareCap; |
| 377 | else //if (capString == "butt") |
| 378 | style = Qt::FlatCap; |
| 379 | m_state.lineCap = style; |
| 380 | m_state.flags |= DirtyLineCap; |
| 381 | } |
| 382 | //! [0] |
| 383 | |
| 384 | QString Context2D::lineJoin() const |
| 385 | { |
| 386 | switch (m_state.lineJoin) { |
| 387 | case Qt::RoundJoin: |
| 388 | return "round" ; |
| 389 | case Qt::BevelJoin: |
| 390 | return "bevel" ; |
| 391 | case Qt::MiterJoin: |
| 392 | return "miter" ; |
| 393 | default: ; |
| 394 | } |
| 395 | return QString(); |
| 396 | } |
| 397 | |
| 398 | void Context2D::setLineJoin(const QString &joinString) |
| 399 | { |
| 400 | Qt::PenJoinStyle style; |
| 401 | if (joinString == "round" ) |
| 402 | style = Qt::RoundJoin; |
| 403 | else if (joinString == "bevel" ) |
| 404 | style = Qt::BevelJoin; |
| 405 | else //if (joinString == "miter") |
| 406 | style = Qt::MiterJoin; |
| 407 | m_state.lineJoin = style; |
| 408 | m_state.flags |= DirtyLineJoin; |
| 409 | } |
| 410 | |
| 411 | qreal Context2D::miterLimit() const |
| 412 | { |
| 413 | return m_state.miterLimit; |
| 414 | } |
| 415 | |
| 416 | void Context2D::setMiterLimit(qreal m) |
| 417 | { |
| 418 | m_state.miterLimit = m; |
| 419 | m_state.flags |= DirtyMiterLimit; |
| 420 | } |
| 421 | |
| 422 | void Context2D::setShadowOffsetX(qreal x) |
| 423 | { |
| 424 | m_state.shadowOffsetX = x; |
| 425 | m_state.flags |= DirtyShadowOffsetX; |
| 426 | } |
| 427 | |
| 428 | void Context2D::setShadowOffsetY(qreal y) |
| 429 | { |
| 430 | m_state.shadowOffsetY = y; |
| 431 | m_state.flags |= DirtyShadowOffsetY; |
| 432 | } |
| 433 | |
| 434 | void Context2D::setShadowBlur(qreal b) |
| 435 | { |
| 436 | m_state.shadowBlur = b; |
| 437 | m_state.flags |= DirtyShadowBlur; |
| 438 | } |
| 439 | |
| 440 | void Context2D::setShadowColor(const QString &str) |
| 441 | { |
| 442 | m_state.shadowColor = colorFromString(name: str); |
| 443 | m_state.flags |= DirtyShadowColor; |
| 444 | } |
| 445 | |
| 446 | qreal Context2D::shadowOffsetX() const |
| 447 | { |
| 448 | return m_state.shadowOffsetX; |
| 449 | } |
| 450 | |
| 451 | qreal Context2D::shadowOffsetY() const |
| 452 | { |
| 453 | return m_state.shadowOffsetY; |
| 454 | } |
| 455 | |
| 456 | |
| 457 | qreal Context2D::shadowBlur() const |
| 458 | { |
| 459 | return m_state.shadowBlur; |
| 460 | } |
| 461 | |
| 462 | |
| 463 | QString Context2D::shadowColor() const |
| 464 | { |
| 465 | return m_state.shadowColor.name(); |
| 466 | } |
| 467 | |
| 468 | |
| 469 | void Context2D::clearRect(qreal x, qreal y, qreal w, qreal h) |
| 470 | { |
| 471 | beginPainting(); |
| 472 | m_painter.save(); |
| 473 | m_painter.setTransform(transform: QTransform(m_state.matrix), combine: false); |
| 474 | m_painter.setCompositionMode(QPainter::CompositionMode_Source); |
| 475 | m_painter.fillRect(QRectF(x, y, w, h), color: QColor(0, 0, 0, 0)); |
| 476 | m_painter.restore(); |
| 477 | scheduleChange(); |
| 478 | } |
| 479 | |
| 480 | |
| 481 | //! [1] |
| 482 | void Context2D::fillRect(qreal x, qreal y, qreal w, qreal h) |
| 483 | { |
| 484 | beginPainting(); |
| 485 | m_painter.save(); |
| 486 | m_painter.setTransform(transform: QTransform(m_state.matrix), combine: false); |
| 487 | m_painter.fillRect(QRectF(x, y, w, h), m_painter.brush()); |
| 488 | m_painter.restore(); |
| 489 | scheduleChange(); |
| 490 | } |
| 491 | //! [1] |
| 492 | |
| 493 | |
| 494 | void Context2D::strokeRect(qreal x, qreal y, qreal w, qreal h) |
| 495 | { |
| 496 | QPainterPath path; |
| 497 | path.addRect(x, y, w, h); |
| 498 | beginPainting(); |
| 499 | m_painter.save(); |
| 500 | m_painter.setTransform(transform: QTransform(m_state.matrix), combine: false); |
| 501 | m_painter.strokePath(path, pen: m_painter.pen()); |
| 502 | m_painter.restore(); |
| 503 | scheduleChange(); |
| 504 | } |
| 505 | |
| 506 | |
| 507 | void Context2D::beginPath() |
| 508 | { |
| 509 | m_path = QPainterPath(); |
| 510 | } |
| 511 | |
| 512 | |
| 513 | void Context2D::closePath() |
| 514 | { |
| 515 | m_path.closeSubpath(); |
| 516 | } |
| 517 | |
| 518 | |
| 519 | void Context2D::moveTo(qreal x, qreal y) |
| 520 | { |
| 521 | QPointF pt = m_state.matrix.map(p: QPointF(x, y)); |
| 522 | m_path.moveTo(p: pt); |
| 523 | } |
| 524 | |
| 525 | |
| 526 | void Context2D::lineTo(qreal x, qreal y) |
| 527 | { |
| 528 | QPointF pt = m_state.matrix.map(p: QPointF(x, y)); |
| 529 | m_path.lineTo(p: pt); |
| 530 | } |
| 531 | |
| 532 | |
| 533 | void Context2D::quadraticCurveTo(qreal cpx, qreal cpy, qreal x, qreal y) |
| 534 | { |
| 535 | QPointF cp = m_state.matrix.map(p: QPointF(cpx, cpy)); |
| 536 | QPointF xy = m_state.matrix.map(p: QPointF(x, y)); |
| 537 | m_path.quadTo(ctrlPt: cp, endPt: xy); |
| 538 | } |
| 539 | |
| 540 | |
| 541 | void Context2D::bezierCurveTo(qreal cp1x, qreal cp1y, |
| 542 | qreal cp2x, qreal cp2y, qreal x, qreal y) |
| 543 | { |
| 544 | QPointF cp1 = m_state.matrix.map(p: QPointF(cp1x, cp1y)); |
| 545 | QPointF cp2 = m_state.matrix.map(p: QPointF(cp2x, cp2y)); |
| 546 | QPointF end = m_state.matrix.map(p: QPointF(x, y)); |
| 547 | m_path.cubicTo(ctrlPt1: cp1, ctrlPt2: cp2, endPt: end); |
| 548 | } |
| 549 | |
| 550 | |
| 551 | void Context2D::arcTo(qreal x1, qreal y1, qreal x2, qreal y2, qreal radius) |
| 552 | { |
| 553 | //FIXME: this is surely busted |
| 554 | QPointF st = m_state.matrix.map(p: QPointF(x1, y1)); |
| 555 | QPointF end = m_state.matrix.map(p: QPointF(x2, y2)); |
| 556 | m_path.arcTo(x: st.x(), y: st.y(), |
| 557 | w: end.x()-st.x(), h: end.y()-st.y(), |
| 558 | startAngle: radius, arcLength: 90); |
| 559 | } |
| 560 | |
| 561 | |
| 562 | void Context2D::rect(qreal x, qreal y, qreal w, qreal h) |
| 563 | { |
| 564 | QPainterPath path; path.addRect(x, y, w, h); |
| 565 | path = m_state.matrix.map(p: path); |
| 566 | m_path.addPath(path); |
| 567 | } |
| 568 | |
| 569 | void Context2D::arc(qreal xc, qreal yc, qreal radius, |
| 570 | qreal sar, qreal ear, |
| 571 | bool anticlockwise) |
| 572 | { |
| 573 | //### HACK |
| 574 | // In Qt we don't switch the coordinate system for degrees |
| 575 | // and still use the 0,0 as bottom left for degrees so we need |
| 576 | // to switch |
| 577 | sar = -sar; |
| 578 | ear = -ear; |
| 579 | anticlockwise = !anticlockwise; |
| 580 | //end hack |
| 581 | |
| 582 | float sa = qRadiansToDegrees(radians: sar); |
| 583 | float ea = qRadiansToDegrees(radians: ear); |
| 584 | |
| 585 | double span = 0; |
| 586 | |
| 587 | double xs = xc - radius; |
| 588 | double ys = yc - radius; |
| 589 | double width = radius*2; |
| 590 | double height = radius*2; |
| 591 | |
| 592 | if (!anticlockwise && (ea < sa)) { |
| 593 | span += 360; |
| 594 | } else if (anticlockwise && (sa < ea)) { |
| 595 | span -= 360; |
| 596 | } |
| 597 | |
| 598 | //### this is also due to switched coordinate system |
| 599 | // we would end up with a 0 span instead of 360 |
| 600 | if (!(qFuzzyCompare(p1: span + (ea - sa) + 1, p2: 1) && |
| 601 | qFuzzyCompare(p1: qAbs(t: span), p2: 360))) { |
| 602 | span += ea - sa; |
| 603 | } |
| 604 | |
| 605 | QPainterPath path; |
| 606 | path.moveTo(p: QPointF(xc + radius * cos(x: sar), |
| 607 | yc - radius * sin(x: sar))); |
| 608 | |
| 609 | path.arcTo(x: xs, y: ys, w: width, h: height, startAngle: sa, arcLength: span); |
| 610 | path = m_state.matrix.map(p: path); |
| 611 | m_path.addPath(path); |
| 612 | } |
| 613 | |
| 614 | |
| 615 | void Context2D::fill() |
| 616 | { |
| 617 | beginPainting(); |
| 618 | m_painter.fillPath(path: m_path, brush: m_painter.brush()); |
| 619 | scheduleChange(); |
| 620 | } |
| 621 | |
| 622 | |
| 623 | void Context2D::stroke() |
| 624 | { |
| 625 | beginPainting(); |
| 626 | m_painter.save(); |
| 627 | m_painter.setTransform(transform: QTransform(m_state.matrix), combine: false); |
| 628 | QPainterPath tmp = m_state.matrix.inverted().map(p: m_path); |
| 629 | m_painter.strokePath(path: tmp, pen: m_painter.pen()); |
| 630 | m_painter.restore(); |
| 631 | scheduleChange(); |
| 632 | } |
| 633 | |
| 634 | |
| 635 | void Context2D::clip() |
| 636 | { |
| 637 | m_state.clipPath = m_path; |
| 638 | m_state.flags |= DirtyClippingRegion; |
| 639 | } |
| 640 | |
| 641 | |
| 642 | bool Context2D::isPointInPath(qreal x, qreal y) const |
| 643 | { |
| 644 | return m_path.contains(pt: QPointF(x, y)); |
| 645 | } |
| 646 | |
| 647 | |
| 648 | ImageData Context2D::getImageData(qreal sx, qreal sy, qreal sw, qreal sh) |
| 649 | { |
| 650 | Q_UNUSED(sx); |
| 651 | Q_UNUSED(sy); |
| 652 | Q_UNUSED(sw); |
| 653 | Q_UNUSED(sh); |
| 654 | return ImageData(); |
| 655 | } |
| 656 | |
| 657 | |
| 658 | void Context2D::putImageData(ImageData image, qreal dx, qreal dy) |
| 659 | { |
| 660 | Q_UNUSED(image); |
| 661 | Q_UNUSED(dx); |
| 662 | Q_UNUSED(dy); |
| 663 | } |
| 664 | |
| 665 | Context2D::Context2D(QObject *parent) |
| 666 | : QObject(parent), m_changeTimerId(-1) |
| 667 | { |
| 668 | reset(); |
| 669 | } |
| 670 | |
| 671 | const QImage &Context2D::endPainting() |
| 672 | { |
| 673 | if (m_painter.isActive()) |
| 674 | m_painter.end(); |
| 675 | return m_image; |
| 676 | } |
| 677 | |
| 678 | void Context2D::beginPainting() |
| 679 | { |
| 680 | if (!m_painter.isActive()) { |
| 681 | m_painter.begin(&m_image); |
| 682 | m_painter.setRenderHint(hint: QPainter::Antialiasing); |
| 683 | if (!m_state.clipPath.isEmpty()) |
| 684 | m_painter.setClipPath(path: m_state.clipPath); |
| 685 | m_painter.setBrush(m_state.fillStyle); |
| 686 | m_painter.setOpacity(m_state.globalAlpha); |
| 687 | QPen pen; |
| 688 | pen.setBrush(m_state.strokeStyle); |
| 689 | if (pen.style() == Qt::NoPen) |
| 690 | pen.setStyle(Qt::SolidLine); |
| 691 | pen.setCapStyle(m_state.lineCap); |
| 692 | pen.setJoinStyle(m_state.lineJoin); |
| 693 | pen.setWidthF(m_state.lineWidth); |
| 694 | pen.setMiterLimit(m_state.miterLimit); |
| 695 | m_painter.setPen(pen); |
| 696 | } else { |
| 697 | if ((m_state.flags & DirtyClippingRegion) && !m_state.clipPath.isEmpty()) |
| 698 | m_painter.setClipPath(path: m_state.clipPath); |
| 699 | if (m_state.flags & DirtyFillStyle) |
| 700 | m_painter.setBrush(m_state.fillStyle); |
| 701 | if (m_state.flags & DirtyGlobalAlpha) |
| 702 | m_painter.setOpacity(m_state.globalAlpha); |
| 703 | if (m_state.flags & DirtyGlobalCompositeOperation) |
| 704 | m_painter.setCompositionMode(m_state.globalCompositeOperation); |
| 705 | if (m_state.flags & MDirtyPen) { |
| 706 | QPen pen = m_painter.pen(); |
| 707 | if (m_state.flags & DirtyStrokeStyle) |
| 708 | pen.setBrush(m_state.strokeStyle); |
| 709 | if (m_state.flags & DirtyLineWidth) |
| 710 | pen.setWidthF(m_state.lineWidth); |
| 711 | if (m_state.flags & DirtyLineCap) |
| 712 | pen.setCapStyle(m_state.lineCap); |
| 713 | if (m_state.flags & DirtyLineJoin) |
| 714 | pen.setJoinStyle(m_state.lineJoin); |
| 715 | if (m_state.flags & DirtyMiterLimit) |
| 716 | pen.setMiterLimit(m_state.miterLimit); |
| 717 | m_painter.setPen(pen); |
| 718 | } |
| 719 | m_state.flags = 0; |
| 720 | } |
| 721 | } |
| 722 | |
| 723 | void Context2D::clear() |
| 724 | { |
| 725 | endPainting(); |
| 726 | m_image.fill(pixel: qRgba(r: 0,g: 0,b: 0,a: 0)); |
| 727 | scheduleChange(); |
| 728 | } |
| 729 | |
| 730 | void Context2D::reset() |
| 731 | { |
| 732 | m_stateStack.clear(); |
| 733 | m_state.matrix = QTransform(); |
| 734 | m_state.clipPath = QPainterPath(); |
| 735 | m_state.globalAlpha = 1.0; |
| 736 | m_state.globalCompositeOperation = QPainter::CompositionMode_SourceOver; |
| 737 | m_state.strokeStyle = Qt::black; |
| 738 | m_state.fillStyle = Qt::black; |
| 739 | m_state.lineWidth = 1; |
| 740 | m_state.lineCap = Qt::FlatCap; |
| 741 | m_state.lineJoin = Qt::MiterJoin; |
| 742 | m_state.miterLimit = 10; |
| 743 | m_state.shadowOffsetX = 0; |
| 744 | m_state.shadowOffsetY = 0; |
| 745 | m_state.shadowBlur = 0; |
| 746 | m_state.shadowColor = qRgba(r: 0, g: 0, b: 0, a: 0); |
| 747 | m_state.flags = AllIsFullOfDirt; |
| 748 | clear(); |
| 749 | } |
| 750 | |
| 751 | void Context2D::setSize(int width, int height) |
| 752 | { |
| 753 | endPainting(); |
| 754 | QImage newi(width, height, QImage::Format_ARGB32_Premultiplied); |
| 755 | newi.fill(pixel: qRgba(r: 0,g: 0,b: 0,a: 0)); |
| 756 | QPainter p(&newi); |
| 757 | p.drawImage(x: 0, y: 0, image: m_image); |
| 758 | p.end(); |
| 759 | m_image = newi; |
| 760 | scheduleChange(); |
| 761 | } |
| 762 | |
| 763 | void Context2D::setSize(const QSize &size) |
| 764 | { |
| 765 | setSize(width: size.width(), height: size.height()); |
| 766 | } |
| 767 | |
| 768 | QSize Context2D::size() const |
| 769 | { |
| 770 | return m_image.size(); |
| 771 | } |
| 772 | |
| 773 | void Context2D::drawImage(DomImage *image, qreal dx, qreal dy) |
| 774 | { |
| 775 | if (!image) |
| 776 | return; |
| 777 | if (dx < 0) { |
| 778 | qreal sx = qAbs(t: dx); |
| 779 | qreal sy = qAbs(t: dy); |
| 780 | qreal sw = image->width() - sx; |
| 781 | qreal sh = image->height() - sy; |
| 782 | |
| 783 | drawImage(image, sx, sy, sw, sh, dx: 0, dy: 0, dw: sw, dh: sh); |
| 784 | } else { |
| 785 | beginPainting(); |
| 786 | m_painter.drawImage(p: QPointF(dx, dy), image: image->image()); |
| 787 | scheduleChange(); |
| 788 | } |
| 789 | } |
| 790 | |
| 791 | void Context2D::drawImage(DomImage *image, qreal dx, qreal dy, |
| 792 | qreal dw, qreal dh) |
| 793 | { |
| 794 | if (!image) |
| 795 | return; |
| 796 | beginPainting(); |
| 797 | m_painter.drawImage(r: QRectF(dx, dy, dw, dh).toRect(), image: image->image()); |
| 798 | scheduleChange(); |
| 799 | } |
| 800 | |
| 801 | void Context2D::drawImage(DomImage *image, qreal sx, qreal sy, |
| 802 | qreal sw, qreal sh, qreal dx, qreal dy, |
| 803 | qreal dw, qreal dh) |
| 804 | { |
| 805 | if (!image) |
| 806 | return; |
| 807 | beginPainting(); |
| 808 | m_painter.drawImage(targetRect: QRectF(dx, dy, dw, dh), image: image->image(), |
| 809 | sourceRect: QRectF(sx, sy, sw, sh)); |
| 810 | scheduleChange(); |
| 811 | } |
| 812 | |
| 813 | //! [2] |
| 814 | void Context2D::scheduleChange() |
| 815 | { |
| 816 | if (m_changeTimerId == -1) |
| 817 | m_changeTimerId = startTimer(interval: 0); |
| 818 | } |
| 819 | |
| 820 | void Context2D::timerEvent(QTimerEvent *e) |
| 821 | { |
| 822 | if (e->timerId() == m_changeTimerId) { |
| 823 | killTimer(id: m_changeTimerId); |
| 824 | m_changeTimerId = -1; |
| 825 | emit changed(image: endPainting()); |
| 826 | } else { |
| 827 | QObject::timerEvent(event: e); |
| 828 | } |
| 829 | } |
| 830 | //! [2] |
| 831 | |