Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qquickcontext2d_p.h"
#include "qquickcontext2dcommandbuffer_p.h"
#include "qquickcanvasitem_p.h"
#include <private/qtquickglobal_p.h>
#include <private/qquickcontext2dtexture_p.h>
#include <private/qquickitem_p.h>
#if QT_CONFIG(quick_shadereffect)
#include <QtQuick/private/qquickshadereffectsource_p.h>
#endif
#include <qsgrendererinterface.h>
#include <QtQuick/private/qsgcontext_p.h>
#include <QtGui/private/qguisvg_p.h>
#if QT_CONFIG(quick_path)
#include <private/qquickpath_p.h>
#endif
#include <private/qquickimage_p_p.h>
#include <qqmlinfo.h>
#include <qqmlengine.h>
#include <private/qv4domerrors_p.h>
#include <private/qv4engine_p.h>
#include <private/qv4object_p.h>
#include <private/qv4qobjectwrapper_p.h>
#include <private/qquickwindow_p.h>
#include <private/qv4value_p.h>
#include <private/qv4functionobject_p.h>
#include <private/qv4objectproto_p.h>
#include <private/qv4scopedvalue_p.h>
#include <private/qlocale_tools_p.h>
#include <QtCore/qmath.h>
#include <QtCore/qvector.h>
#include <QtCore/private/qnumeric_p.h>
#include <QtCore/QRunnable>
#include <QtGui/qguiapplication.h>
#include <private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
#include <private/qsgdefaultrendercontext_p.h>
#include <QtCore/qpointer.h>
#include <cmath>
#if defined(Q_OS_QNX) || defined(Q_OS_ANDROID)
#include <ctype.h>
#endif
QT_BEGIN_NAMESPACE
/*!
\qmltype Context2D
\nativetype QQuickContext2D
\inqmlmodule QtQuick
\ingroup qtquick-canvas
\since 5.0
\brief Provides 2D context for shapes on a Canvas item.
The Context2D object can be created by \c Canvas item's \c getContext()
method:
\code
Canvas {
id:canvas
onPaint:{
var ctx = canvas.getContext('2d');
//...
}
}
\endcode
The Context2D API implements the same \l
some enhanced features.
The Context2D API provides the rendering \b{context} which defines the
methods and attributes needed to draw on the \c Canvas item. The following
assigns the canvas rendering context to a \c{context} variable:
\code
var context = mycanvas.getContext("2d")
\endcode
The Context2D API renders the canvas as a coordinate system whose origin
(0,0) is at the top left corner, as shown in the figure below. Coordinates
increase along the \c{x} axis from left to right and along the \c{y} axis
from top to bottom of the canvas.
\image qml-item-canvas-context.gif {Canvas coordinate system with
origin at top-left, x-axis right, y-axis down, 300x150 default size}
*/
#define CHECK_CONTEXT(r) if (!r || !r->d()->context() || !r->d()->context()->bufferValid()) \
THROW_GENERIC_ERROR("Not a Context2D object");
#define CHECK_CONTEXT_SETTER(r) if (!r || !r->d()->context() || !r->d()->context()->bufferValid()) \
THROW_GENERIC_ERROR("Not a Context2D object");
#define qClamp(val, min, max) qMin(qMax(val, min), max)
#define CHECK_RGBA(c) (c == '-' || c == '.' || (c >=0 && c <= 9))
Q_QUICK_EXPORT QColor qt_color_from_string(const QV4::Value &name)
{
QByteArray str = name.toQString().toUtf8();
char *p = str.data();
int len = str.size();
//rgb/hsl color string has at least 7 characters
if (!p || len > 255 || len <= 7)
return QColor::fromString(p);
else {
bool isRgb(false), isHsl(false), hasAlpha(false);
Q_UNUSED(isHsl);
while (isspace(*p)) p++;
if (strncmp(p, "rgb", 3) == 0)
isRgb = true;
else if (strncmp(p, "hsl", 3) == 0)
isHsl = true;
else
return QColor::fromString(p);
p+=3; //skip "rgb" or "hsl"
hasAlpha = (*p == 'a') ? true : false;
++p; //skip "("
if (hasAlpha) ++p; //skip "a"
int rh, gs, bl, alpha = 255;
//red
while (isspace(*p)) p++;
rh = strtol(p, &p, 10);
if (*p == '%') {
rh = qRound(rh/100.0 * 255);
++p;
}
if (*p++ != ',') return QColor();
//green
while (isspace(*p)) p++;
gs = strtol(p, &p, 10);
if (*p == '%') {
gs = qRound(gs/100.0 * 255);
++p;
}
if (*p++ != ',') return QColor();
//blue
while (isspace(*p)) p++;
bl = strtol(p, &p, 10);
if (*p == '%') {
bl = qRound(bl/100.0 * 255);
++p;
}
if (hasAlpha) {
if (*p++!= ',') return QColor();
while (isspace(*p)) p++;
bool ok = false;
alpha = qRound(qstrtod(p, const_cast<const char **>(&p), &ok) * 255);
}
if (*p != ')') return QColor();
if (isRgb)
return QColor::fromRgba(qRgba(qClamp(rh, 0, 255), qClamp(gs, 0, 255), qClamp(bl, 0, 255), qClamp(alpha, 0, 255)));
else if (isHsl)
return QColor::fromHsl(qClamp(rh, 0, 359), qClamp(gs, 0, 255), qClamp(bl, 0, 255), qClamp(alpha, 0, 255));
}
return QColor();
}
static int qParseFontSizeFromToken(QStringView fontSizeToken, bool &ok)
{
ok = false;
float size = fontSizeToken.trimmed().toFloat(&ok);
if (ok) {
return int(size);
}
qWarning().nospace() << "Context2D: A font size of " << fontSizeToken << " is invalid.";
return 0;
}
/*
Attempts to set the font size of \a font to \a fontSizeToken, returning
\c true if successful. If the font size is invalid, \c false is returned
and a warning is printed.
*/
static bool qSetFontSizeFromToken(QFont &font, QStringView fontSizeToken)
{
const QStringView trimmedToken = fontSizeToken.trimmed();
const QStringView unitStr = trimmedToken.right(2);
const QStringView value = trimmedToken.left(trimmedToken.size() - 2);
bool ok = false;
int size = 0;
if (unitStr == QLatin1String("px")) {
size = qParseFontSizeFromToken(value, ok);
if (ok) {
font.setPixelSize(size);
return true;
}
} else if (unitStr == QLatin1String("pt")) {
size = qParseFontSizeFromToken(value, ok);
if (ok) {
font.setPointSize(size);
return true;
}
} else {
qWarning().nospace() << "Context2D: Invalid font size unit in font string.";
}
return false;
}
/*
Returns a list of all of the families in \a fontFamiliesString, where
each family is separated by spaces. Families with spaces in their name
must be quoted.
*/
static QStringList qExtractFontFamiliesFromString(QStringView fontFamiliesString)
{
QStringList extractedFamilies;
int quoteIndex = -1;
QString currentFamily;
for (int index = 0; index < fontFamiliesString.size(); ++index) {
const QChar ch = fontFamiliesString.at(index);
if (ch == u'"' || ch == u'\'') {
if (quoteIndex == -1) {
quoteIndex = index;
} else {
if (ch == fontFamiliesString.at(quoteIndex)) {
// Found the matching quote. +1/-1 because we don't want the quote as part of the name.
const QString family = fontFamiliesString.mid(quoteIndex + 1, index - quoteIndex - 1).toString();
extractedFamilies.push_back(family);
currentFamily.clear();
quoteIndex = -1;
} else {
qWarning().nospace() << "Context2D: Mismatched quote in font string.";
return QStringList();
}
}
} else if (ch == u' ' && quoteIndex == -1) {
// This is a space that's not within quotes...
if (!currentFamily.isEmpty()) {
// and there is a current family; consider it the end of the current family.
extractedFamilies.push_back(currentFamily);
currentFamily.clear();
} // else: ignore the space
} else {
currentFamily.push_back(ch);
}
}
if (!currentFamily.isEmpty()) {
if (quoteIndex == -1) {
// This is the end of the string, so add this family to our list.
extractedFamilies.push_back(currentFamily);
} else {
qWarning().nospace() << "Context2D: Unclosed quote in font string.";
return QStringList();
}
}
if (extractedFamilies.isEmpty()) {
qWarning().nospace() << "Context2D: Missing or misplaced font family in font string"
<< " (it must come after the font size).";
}
return extractedFamilies;
}
/*
Tries to set a family on \a font using the families provided in \a fontFamilyTokens.
The list is ordered by preference, with the first family having the highest preference.
If the first family is invalid, the next family in the list is evaluated.
This process is repeated until a valid font is found (at which point the function
will return \c true and the family set on \a font) or there are no more
families left, at which point a warning is printed and \c false is returned.
*/
static bool qSetFontFamilyFromTokens(QFont &font, const QStringList &fontFamilyTokens)
{
for (const QString &fontFamilyToken : fontFamilyTokens) {
if (QFontDatabase::hasFamily(fontFamilyToken)) {
font.setFamily(fontFamilyToken);
return true;
} else {
// Can't find a family matching this name; if it's a generic family,
// try searching for the default family for it by using style hints.
int styleHint = -1;
if (fontFamilyToken.compare(QLatin1String("serif")) == 0) {
styleHint = QFont::Serif;
} else if (fontFamilyToken.compare(QLatin1String("sans-serif")) == 0) {
styleHint = QFont::SansSerif;
} else if (fontFamilyToken.compare(QLatin1String("cursive")) == 0) {
styleHint = QFont::Cursive;
} else if (fontFamilyToken.compare(QLatin1String("monospace")) == 0) {
styleHint = QFont::Monospace;
} else if (fontFamilyToken.compare(QLatin1String("fantasy")) == 0) {
styleHint = QFont::Fantasy;
}
if (styleHint != -1) {
QFont tmp;
tmp.setStyleHint(static_cast<QFont::StyleHint>(styleHint));
font.setFamily(tmp.defaultFamily());
return true;
}
}
}
qWarning("Context2D: The font families specified are invalid: %s", qPrintable(fontFamilyTokens.join(QString()).trimmed()));
return false;
}
enum FontToken
{
NoTokens = 0x00,
FontStyle = 0x01,
FontVariant = 0x02,
FontWeight = 0x04
};
#define Q_TRY_SET_TOKEN(token, value, setStatement) \
if (!(usedTokens & token)) { \
usedTokens |= token; \
setStatement; \
} else { \
qWarning().nospace() << "Context2D: Duplicate token " << QLatin1String(value) << " found in font string."; \
return currentFont; \
}
/*
Parses a font string based on the CSS shorthand font property.
*/
static QFont qt_font_from_string(const QString& fontString, const QFont ¤tFont) {
if (fontString.isEmpty()) {
qWarning().nospace() << "Context2D: Font string is empty.";
return currentFont;
}
// We know that font-size must be specified and it must be before font-family
// (which could potentially have "px" or "pt" in its name), so extract it now.
int fontSizeEnd = fontString.indexOf(QLatin1String("px"));
if (fontSizeEnd == -1)
fontSizeEnd = fontString.indexOf(QLatin1String("pt"));
if (fontSizeEnd == -1) {
qWarning().nospace() << "Context2D: Invalid font size unit in font string.";
return currentFont;
}
int fontSizeStart = fontString.lastIndexOf(u' ', fontSizeEnd);
if (fontSizeStart == -1) {
// The font size might be the first token in the font string, which is OK.
// Regardless, we'll find out if the font is invalid with qSetFontSizeFromToken().
fontSizeStart = 0;
} else {
// Don't want to take the leading space.
++fontSizeStart;
}
// + 2 for the unit, +1 for the space that we require.
fontSizeEnd += 3;
QFont newFont;
if (!qSetFontSizeFromToken(newFont, QStringView{fontString}.mid(fontSizeStart, fontSizeEnd - fontSizeStart)))
return currentFont;
// We don't want to parse the size twice, so remove it now.
QString remainingFontString = fontString;
remainingFontString.remove(fontSizeStart, fontSizeEnd - fontSizeStart);
QStringView remainingFontStringRef(remainingFontString);
// Next, we have to take any font families out, as QString::split() will ruin quoted family names.
const QStringView fontFamiliesString = remainingFontStringRef.mid(fontSizeStart);
remainingFontStringRef.truncate(fontSizeStart);
QStringList fontFamilies = qExtractFontFamiliesFromString(fontFamiliesString);
if (fontFamilies.isEmpty()) {
return currentFont;
}
if (!qSetFontFamilyFromTokens(newFont, fontFamilies))
return currentFont;
// Now that we've removed the messy parts, we can split the font string on spaces.
const QStringView trimmedTokensStr = remainingFontStringRef.trimmed();
if (trimmedTokensStr.isEmpty()) {
// No optional properties.
return newFont;
}
const auto tokens = trimmedTokensStr.split(QLatin1Char(' '));
int usedTokens = NoTokens;
// Optional properties can be in any order, but font-size and font-family must be last.
for (const QStringView &token : tokens) {
if (token.compare(QLatin1String("normal")) == 0) {
if (!(usedTokens & FontStyle) || !(usedTokens & FontVariant) || !(usedTokens & FontWeight)) {
// Could be font-style, font-variant or font-weight.
if (!(usedTokens & FontStyle)) {
// QFont::StyleNormal is the default for QFont::style.
usedTokens = usedTokens | FontStyle;
} else if (!(usedTokens & FontVariant)) {
// QFont::MixedCase is the default for QFont::capitalization.
usedTokens |= FontVariant;
} else if (!(usedTokens & FontWeight)) {
// QFont::Normal is the default for QFont::weight.
usedTokens |= FontWeight;
}
} else {
qWarning().nospace() << "Context2D: Duplicate token \"normal\" found in font string.";
return currentFont;
}
} else if (token.compare(QLatin1String("bold")) == 0) {
Q_TRY_SET_TOKEN(FontWeight, "bold", newFont.setBold(true))
} else if (token.compare(QLatin1String("italic")) == 0) {
Q_TRY_SET_TOKEN(FontStyle, "italic", newFont.setStyle(QFont::StyleItalic))
} else if (token.compare(QLatin1String("oblique")) == 0) {
Q_TRY_SET_TOKEN(FontStyle, "oblique", newFont.setStyle(QFont::StyleOblique))
} else if (token.compare(QLatin1String("small-caps")) == 0) {
Q_TRY_SET_TOKEN(FontVariant, "small-caps", newFont.setCapitalization(QFont::SmallCaps))
} else {
bool conversionOk = false;
int weight = token.toInt(&conversionOk);
if (conversionOk) {
Q_TRY_SET_TOKEN(FontWeight, "<font-weight>",
newFont.setWeight(QFont::Weight(weight)))
} else {
// The token is invalid or in the wrong place/order in the font string.
qWarning().nospace() << "Context2D: Invalid or misplaced token " << token
<< " found in font string.";
return currentFont;
}
}
}
return newFont;
}
class QQuickContext2DEngineData : public QV4::ExecutionEngine::Deletable
{
public:
QQuickContext2DEngineData(QV4::ExecutionEngine *engine);
~QQuickContext2DEngineData();
QV4::PersistentValue contextPrototype;
QV4::PersistentValue gradientProto;
QV4::PersistentValue pixelArrayProto;
};
V4_DEFINE_EXTENSION(QQuickContext2DEngineData, engineData)
namespace QV4 {
namespace Heap {
struct QQuickJSContext2D : Object {
void init()
{
Object::init();
m_context = nullptr;
}
void destroy()
{
delete m_context;
Object::destroy();
}
QQuickContext2D *context() { return m_context ? *m_context : nullptr; }
void setContext(QQuickContext2D *context)
{
if (m_context)
*m_context = context;
else
m_context = new QPointer<QQuickContext2D>(context);
}
private:
QPointer<QQuickContext2D>* m_context;
};
struct QQuickJSContext2DPrototype : Object {
void init() { Object::init(); }
};
struct QQuickContext2DStyle : Object {
void init()
{
brush = new QBrush;
patternRepeatX = false;
patternRepeatY = false;
}
void destroy() {
delete brush;
Object::destroy();
}
QBrush *brush;
bool patternRepeatX:1;
bool patternRepeatY:1;
};
struct QQuickJSContext2DPixelData : Object {
void init();
void destroy() {
if (image) {
auto *mm = internalClass->engine->memoryManager;
mm->changeUnmanagedHeapSizeUsage(-image->sizeInBytes());
delete image;
}
Object::destroy();
}
QImage *image;
};
struct QQuickJSContext2DImageData : Object {
void init();
static void markObjects(QV4::Heap::Base *that, QV4::MarkStack *markStack) {
static_cast<QQuickJSContext2DImageData *>(that)->pixelData.mark(markStack);
Object::markObjects(that, markStack);
}
QV4::Value pixelData;
};
}
}
struct QQuickJSContext2D : public QV4::Object
{
V4_OBJECT2(QQuickJSContext2D, QV4::Object)
V4_NEEDS_DESTROY
static QV4::ReturnedValue method_get_globalAlpha(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_globalAlpha(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_fillStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_fillStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_strokeStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_strokeStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_lineWidth(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_lineWidth(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_miterLimit(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_miterLimit(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_shadowBlur(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_shadowBlur(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_shadowColor(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_shadowColor(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_shadowOffsetX(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_shadowOffsetX(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_shadowOffsetY(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_shadowOffsetY(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
// should these two be on the proto?
#if QT_CONFIG(quick_path)
static QV4::ReturnedValue method_get_path(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_path(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
#endif
static QV4::ReturnedValue method_get_font(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_font(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_set_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
};
DEFINE_OBJECT_VTABLE(QQuickJSContext2D);
struct QQuickJSContext2DPrototype : public QV4::Object
{
V4_OBJECT2(QQuickJSContext2DPrototype, QV4::Object)
public:
static QV4::Heap::QQuickJSContext2DPrototype *create(QV4::ExecutionEngine *engine)
{
QV4::Scope scope(engine);
QV4::Scoped<QQuickJSContext2DPrototype> o(scope, engine->memoryManager->allocate<QQuickJSContext2DPrototype>());
o->defineDefaultProperty(QStringLiteral("quadraticCurveTo"), method_quadraticCurveTo, 0);
o->defineDefaultProperty(QStringLiteral("restore"), method_restore, 0);
o->defineDefaultProperty(QStringLiteral("moveTo"), method_moveTo, 0);
o->defineDefaultProperty(QStringLiteral("lineTo"), method_lineTo, 0);
o->defineDefaultProperty(QStringLiteral("caretBlinkRate"), method_caretBlinkRate, 0);
o->defineDefaultProperty(QStringLiteral("clip"), method_clip, 0);
o->defineDefaultProperty(QStringLiteral("setTransform"), method_setTransform, 0);
o->defineDefaultProperty(QStringLiteral("text"), method_text, 0);
o->defineDefaultProperty(QStringLiteral("roundedRect"), method_roundedRect, 0);
o->defineDefaultProperty(QStringLiteral("createPattern"), method_createPattern, 0);
o->defineDefaultProperty(QStringLiteral("stroke"), method_stroke, 0);
o->defineDefaultProperty(QStringLiteral("arc"), method_arc, 0);
o->defineDefaultProperty(QStringLiteral("createImageData"), method_createImageData, 0);
o->defineDefaultProperty(QStringLiteral("measureText"), method_measureText, 0);
o->defineDefaultProperty(QStringLiteral("ellipse"), method_ellipse, 0);
o->defineDefaultProperty(QStringLiteral("fill"), method_fill, 0);
o->defineDefaultProperty(QStringLiteral("save"), method_save, 0);
o->defineDefaultProperty(QStringLiteral("scale"), method_scale, 0);
o->defineDefaultProperty(QStringLiteral("drawImage"), method_drawImage, 0);
o->defineDefaultProperty(QStringLiteral("transform"), method_transform, 0);
o->defineDefaultProperty(QStringLiteral("fillText"), method_fillText, 0);
o->defineDefaultProperty(QStringLiteral("strokeText"), method_strokeText, 0);
o->defineDefaultProperty(QStringLiteral("translate"), method_translate, 0);
o->defineDefaultProperty(QStringLiteral("createRadialGradient"), method_createRadialGradient, 0);
o->defineDefaultProperty(QStringLiteral("shear"), method_shear, 0);
o->defineDefaultProperty(QStringLiteral("isPointInPath"), method_isPointInPath, 0);
o->defineDefaultProperty(QStringLiteral("bezierCurveTo"), method_bezierCurveTo, 0);
o->defineDefaultProperty(QStringLiteral("resetTransform"), method_resetTransform, 0);
o->defineDefaultProperty(QStringLiteral("arcTo"), method_arcTo, 0);
o->defineDefaultProperty(QStringLiteral("fillRect"), method_fillRect, 0);
o->defineDefaultProperty(QStringLiteral("createConicalGradient"), method_createConicalGradient, 0);
o->defineDefaultProperty(QStringLiteral("drawFocusRing"), method_drawFocusRing, 0);
o->defineDefaultProperty(QStringLiteral("beginPath"), method_beginPath, 0);
o->defineDefaultProperty(QStringLiteral("clearRect"), method_clearRect, 0);
o->defineDefaultProperty(QStringLiteral("rect"), method_rect, 0);
o->defineDefaultProperty(QStringLiteral("reset"), method_reset, 0);
o->defineDefaultProperty(QStringLiteral("rotate"), method_rotate, 0);
o->defineDefaultProperty(QStringLiteral("setCaretSelectionRect"), method_setCaretSelectionRect, 0);
o->defineDefaultProperty(QStringLiteral("putImageData"), method_putImageData, 0);
o->defineDefaultProperty(QStringLiteral("getImageData"), method_getImageData, 0);
o->defineDefaultProperty(QStringLiteral("createLinearGradient"), method_createLinearGradient, 0);
o->defineDefaultProperty(QStringLiteral("strokeRect"), method_strokeRect, 0);
o->defineDefaultProperty(QStringLiteral("closePath"), method_closePath, 0);
o->defineDefaultProperty(QStringLiteral("setLineDash"), method_setLineDash, 0);
o->defineDefaultProperty(QStringLiteral("getLineDash"), method_getLineDash, 0);
o->defineAccessorProperty(QStringLiteral("canvas"), QQuickJSContext2DPrototype::method_get_canvas, nullptr);
return o->d();
}
static QV4::ReturnedValue method_get_canvas(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_restore(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_reset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_save(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_rotate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_scale(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_translate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_setTransform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_transform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_resetTransform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_shear(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_createLinearGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_createRadialGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_createConicalGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_createPattern(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_clearRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_fillRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_strokeRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_arc(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_arcTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_beginPath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_bezierCurveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_clip(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_closePath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_fill(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_lineTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_moveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_quadraticCurveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_rect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_roundedRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_ellipse(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_text(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_stroke(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_isPointInPath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_drawFocusRing(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_setCaretSelectionRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_caretBlinkRate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_fillText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_strokeText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_measureText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_drawImage(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_createImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_getImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_putImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_setLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_getLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
};
DEFINE_OBJECT_VTABLE(QQuickJSContext2DPrototype);
struct QQuickContext2DStyle : public QV4::Object
{
V4_OBJECT2(QQuickContext2DStyle, QV4::Object)
V4_NEEDS_DESTROY
static QV4::ReturnedValue gradient_proto_addColorStop(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
};
DEFINE_OBJECT_VTABLE(QQuickContext2DStyle);
QImage qt_image_convolute_filter(const QImage& src, const QList<qreal>& weights, int radius = 0)
{
// weights 3x3 => delta 1
int delta = radius ? radius : qFloor(qSqrt(weights.size()) / qreal(2));
int filterDim = 2 * delta + 1;
QImage dst = QImage(src.size(), src.format());
int w = src.width();
int h = src.height();
const QRgb *sr = (const QRgb *)(src.constBits());
int srcStride = src.bytesPerLine() / 4;
QRgb *dr = (QRgb*)dst.bits();
int dstStride = dst.bytesPerLine() / 4;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int red = 0;
int green = 0;
int blue = 0;
int alpha = 0;
qreal redF = 0;
qreal greenF = 0;
qreal blueF = 0;
qreal alphaF = 0;
int sy = y;
int sx = x;
for (int cy = 0; cy < filterDim; ++cy) {
int scy = sy + cy - delta;
if (scy < 0 || scy >= h)
continue;
const QRgb *sry = sr + scy * srcStride;
for (int cx = 0; cx < filterDim; ++cx) {
int scx = sx + cx - delta;
if (scx < 0 || scx >= w)
continue;
const QRgb col = sry[scx];
if (radius) {
red += qRed(col);
green += qGreen(col);
blue += qBlue(col);
alpha += qAlpha(col);
} else {
qreal wt = weights[cy * filterDim + cx];
redF += qRed(col) * wt;
greenF += qGreen(col) * wt;
blueF += qBlue(col) * wt;
alphaF += qAlpha(col) * wt;
}
}
}
if (radius)
dr[x] = qRgba(qRound(red * weights[0]), qRound(green * weights[0]), qRound(blue * weights[0]), qRound(alpha * weights[0]));
else
dr[x] = qRgba(qRound(redF), qRound(greenF), qRound(blueF), qRound(alphaF));
}
dr += dstStride;
}
return dst;
}
void qt_image_boxblur(QImage& image, int radius, bool quality)
{
int passes = quality? 3: 1;
int filterSize = 2 * radius + 1;
for (int i = 0; i < passes; ++i)
image = qt_image_convolute_filter(image, QList<qreal>() << 1.0 / (filterSize * filterSize), radius);
}
static QPainter::CompositionMode qt_composite_mode_from_string(const QString &compositeOperator)
{
if (compositeOperator == QLatin1String("source-over")) {
return QPainter::CompositionMode_SourceOver;
} else if (compositeOperator == QLatin1String("source-out")) {
return QPainter::CompositionMode_SourceOut;
} else if (compositeOperator == QLatin1String("source-in")) {
return QPainter::CompositionMode_SourceIn;
} else if (compositeOperator == QLatin1String("source-atop")) {
return QPainter::CompositionMode_SourceAtop;
} else if (compositeOperator == QLatin1String("destination-atop")) {
return QPainter::CompositionMode_DestinationAtop;
} else if (compositeOperator == QLatin1String("destination-in")) {
return QPainter::CompositionMode_DestinationIn;
} else if (compositeOperator == QLatin1String("destination-out")) {
return QPainter::CompositionMode_DestinationOut;
} else if (compositeOperator == QLatin1String("destination-over")) {
return QPainter::CompositionMode_DestinationOver;
} else if (compositeOperator == QLatin1String("lighter")) {
return QPainter::CompositionMode_Plus;
} else if (compositeOperator == QLatin1String("copy")) {
return QPainter::CompositionMode_Source;
} else if (compositeOperator == QLatin1String("xor")) {
return QPainter::CompositionMode_Xor;
} else if (compositeOperator == QLatin1String("qt-clear")) {
return QPainter::CompositionMode_Clear;
} else if (compositeOperator == QLatin1String("qt-destination")) {
return QPainter::CompositionMode_Destination;
} else if (compositeOperator == QLatin1String("qt-multiply")) {
return QPainter::CompositionMode_Multiply;
} else if (compositeOperator == QLatin1String("qt-screen")) {
return QPainter::CompositionMode_Screen;
} else if (compositeOperator == QLatin1String("qt-overlay")) {
return QPainter::CompositionMode_Overlay;
} else if (compositeOperator == QLatin1String("qt-darken")) {
return QPainter::CompositionMode_Darken;
} else if (compositeOperator == QLatin1String("qt-lighten")) {
return QPainter::CompositionMode_Lighten;
} else if (compositeOperator == QLatin1String("qt-color-dodge")) {
return QPainter::CompositionMode_ColorDodge;
} else if (compositeOperator == QLatin1String("qt-color-burn")) {
return QPainter::CompositionMode_ColorBurn;
} else if (compositeOperator == QLatin1String("qt-hard-light")) {
return QPainter::CompositionMode_HardLight;
} else if (compositeOperator == QLatin1String("qt-soft-light")) {
return QPainter::CompositionMode_SoftLight;
} else if (compositeOperator == QLatin1String("qt-difference")) {
return QPainter::CompositionMode_Difference;
} else if (compositeOperator == QLatin1String("qt-exclusion")) {
return QPainter::CompositionMode_Exclusion;
}
return QPainter::CompositionMode_SourceOver;
}
static QString qt_composite_mode_to_string(QPainter::CompositionMode op)
{
switch (op) {
case QPainter::CompositionMode_SourceOver:
return QStringLiteral("source-over");
case QPainter::CompositionMode_DestinationOver:
return QStringLiteral("destination-over");
case QPainter::CompositionMode_Clear:
return QStringLiteral("qt-clear");
case QPainter::CompositionMode_Source:
return QStringLiteral("copy");
case QPainter::CompositionMode_Destination:
return QStringLiteral("qt-destination");
case QPainter::CompositionMode_SourceIn:
return QStringLiteral("source-in");
case QPainter::CompositionMode_DestinationIn:
return QStringLiteral("destination-in");
case QPainter::CompositionMode_SourceOut:
return QStringLiteral("source-out");
case QPainter::CompositionMode_DestinationOut:
return QStringLiteral("destination-out");
case QPainter::CompositionMode_SourceAtop:
return QStringLiteral("source-atop");
case QPainter::CompositionMode_DestinationAtop:
return QStringLiteral("destination-atop");
case QPainter::CompositionMode_Xor:
return QStringLiteral("xor");
case QPainter::CompositionMode_Plus:
return QStringLiteral("lighter");
case QPainter::CompositionMode_Multiply:
return QStringLiteral("qt-multiply");
case QPainter::CompositionMode_Screen:
return QStringLiteral("qt-screen");
case QPainter::CompositionMode_Overlay:
return QStringLiteral("qt-overlay");
case QPainter::CompositionMode_Darken:
return QStringLiteral("qt-darken");
case QPainter::CompositionMode_Lighten:
return QStringLiteral("lighter");
case QPainter::CompositionMode_ColorDodge:
return QStringLiteral("qt-color-dodge");
case QPainter::CompositionMode_ColorBurn:
return QStringLiteral("qt-color-burn");
case QPainter::CompositionMode_HardLight:
return QStringLiteral("qt-hard-light");
case QPainter::CompositionMode_SoftLight:
return QStringLiteral("qt-soft-light");
case QPainter::CompositionMode_Difference:
return QStringLiteral("qt-difference");
case QPainter::CompositionMode_Exclusion:
return QStringLiteral("qt-exclusion");
default:
break;
}
return QString();
}
struct QQuickJSContext2DPixelData : public QV4::Object
{
V4_OBJECT2(QQuickJSContext2DPixelData, QV4::Object)
V4_NEEDS_DESTROY
static QV4::ReturnedValue virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty);
static bool virtualPut(QV4::Managed *m, QV4::PropertyKey id, const QV4::Value &value, Value *receiver);
static QV4::ReturnedValue proto_get_length(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
};
void QV4::Heap::QQuickJSContext2DPixelData::init()
{
Object::init();
image = new QImage;
QV4::Scope scope(internalClass->engine);
QV4::ScopedObject o(scope, this);
o->setArrayType(QV4::Heap::ArrayData::Custom);
}
DEFINE_OBJECT_VTABLE(QQuickJSContext2DPixelData);
struct QQuickJSContext2DImageData : public QV4::Object
{
V4_OBJECT2(QQuickJSContext2DImageData, QV4::Object)
static QV4::ReturnedValue method_get_width(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_height(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue method_get_data(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
};
void QV4::Heap::QQuickJSContext2DImageData::init()
{
Object::init();
pixelData = QV4::Value::undefinedValue();
QV4::Scope scope(internalClass->engine);
QV4::ScopedObject o(scope, this);
o->defineAccessorProperty(QStringLiteral("width"), ::QQuickJSContext2DImageData::method_get_width, nullptr);
o->defineAccessorProperty(QStringLiteral("height"), ::QQuickJSContext2DImageData::method_get_height, nullptr);
o->defineAccessorProperty(QStringLiteral("data"), ::QQuickJSContext2DImageData::method_get_data, nullptr);
}
DEFINE_OBJECT_VTABLE(QQuickJSContext2DImageData);
static QV4::ReturnedValue qt_create_image_data(qreal w, qreal h, QV4::ExecutionEngine *v4, QImage&& image)
{
QV4::Scope scope(v4);
QQuickContext2DEngineData *ed = engineData(scope.engine);
QV4::Scoped<QQuickJSContext2DPixelData> pixelData(scope, scope.engine->memoryManager->allocate<QQuickJSContext2DPixelData>());
v4->memoryManager->changeUnmanagedHeapSizeUsage(image.sizeInBytes());
QV4::ScopedObject p(scope, ed->pixelArrayProto.value());
pixelData->setPrototypeOf(p);
if (image.isNull()) {
*pixelData->d()->image = QImage(qRound(w), qRound(h), QImage::Format_ARGB32);
pixelData->d()->image->fill(0x00000000);
} else {
// After qtbase 88e56d0932a3615231adf40d5ae033e742d72c33, the image size can be off by one.
Q_ASSERT(qAbs(image.width() - qRound(w * image.devicePixelRatio())) <= 1 && qAbs(image.height() - qRound(h * image.devicePixelRatio())) <= 1);
*pixelData->d()->image = image.format() == QImage::Format_ARGB32 ? std::move(image) : std::move(image).convertToFormat(QImage::Format_ARGB32);
}
QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, scope.engine->memoryManager->allocate<QQuickJSContext2DImageData>());
imageData->d()->pixelData = pixelData.asReturnedValue();
return imageData.asReturnedValue();
}
//static script functions
/*!
\qmlproperty QtQuick::Canvas QtQuick::Context2D::canvas
Holds the canvas item that the context paints on.
This property is read only.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_get_canvas(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
RETURN_RESULT(QV4::QObjectWrapper::wrap(scope.engine, r->d()->context()->canvas()));
}
/*!
\qmlmethod Context2D QtQuick::Context2D::restore()
Pops the top state on the stack, restoring the context to that state.
\sa save()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_restore(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
r->d()->context()->popState();
RETURN_RESULT(thisObject->asReturnedValue());
}
/*!
\qmlmethod Context2D QtQuick::Context2D::reset()
Resets the context state and properties to the default values.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_reset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
r->d()->context()->reset();
RETURN_RESULT(thisObject->asReturnedValue());
}
/*!
\qmlmethod Context2D QtQuick::Context2D::save()
Pushes the current state onto the state stack.
Before changing any state attributes, you should save the current state
for future reference. The context maintains a stack of drawing states.
Each state consists of the current transformation matrix, clipping region,
and values of the following attributes:
\list
\li strokeStyle
\li fillStyle
\li fillRule
\li globalAlpha
\li lineWidth
\li lineCap
\li lineJoin
\li miterLimit
\li shadowOffsetX
\li shadowOffsetY
\li shadowBlur
\li shadowColor
\li globalCompositeOperation
\li \l font
\li textAlign
\li textBaseline
\endlist
The current path is NOT part of the drawing state. The path can be reset by
invoking the beginPath() method.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_save(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
r->d()->context()->pushState();
RETURN_RESULT(*thisObject);
}
// transformations
/*!
\qmlmethod Context2D QtQuick::Context2D::rotate(real angle)
Rotate the canvas around the current origin by \a angle in radians and clockwise direction.
\code
ctx.rotate(Math.PI/2);
\endcode
\image qml-item-canvas-rotate.png {Canvas coordinate system before
and after rotating π/2 radians clockwise}
The rotation transformation matrix is as follows:
\image qml-item-canvas-math-rotate.png {Rotation transformation matrix
with cosine and sine of angle}
where the \a angle of rotation is in radians.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_rotate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 1)
r->d()->context()->rotate(argv[0].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::scale(real x, real y)
Increases or decreases the size of each unit in the canvas grid by multiplying the scale factors
to the current tranform matrix.
\a x is the scale factor in the horizontal direction and \a y is the scale factor in the
vertical direction.
The following code doubles the horizontal size of an object drawn on the canvas and halves its
vertical size:
\code
ctx.scale(2.0, 0.5);
\endcode
\image qml-item-canvas-scale.png {Circle transformed to ellipse
by scale(2, 0.5) showing doubled width and halved height}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_scale(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 2)
r->d()->context()->scale(argv[0].toNumber(), argv[1].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::setTransform(real a, real b, real c, real d, real e, real f)
Changes the transformation matrix to the matrix given by the arguments as described below.
Modifying the transformation matrix directly enables you to perform scaling,
rotating, and translating transformations in a single step.
Each point on the canvas is multiplied by the matrix before anything is
drawn. The \l{http://www.w3.org/TR/2dcontext/#transformations}{HTML Canvas 2D Context specification}
defines the transformation matrix as:
\image qml-item-canvas-math.png {3x3 transformation matrix with
elements a,c,e in first row, b,d,f in second row, 0,0,1 in third}
where:
\list
\li \a{a} is the scale factor in the horizontal (x) direction
\image qml-item-canvas-scalex.png {Square stretched horizontally}
\li \a{c} is the skew factor in the x direction
\image qml-item-canvas-skewx.png {Square skewed into parallelogram}
\li \a{e} is the translation in the x direction
\image qml-item-canvas-translate.png {Square translated horizontally}
\li \a{b} is the skew factor in the y (vertical) direction
\image qml-item-canvas-skewy.png {Square skewed vertically}
\li \a{d} is the scale factor in the y direction
\image qml-item-canvas-scaley.png {Square stretched vertically}
\li \a{f} is the translation in the y direction
\image qml-item-canvas-translatey.png {Square translated vertically}
\li the last row remains constant
\endlist
The scale factors and skew factors are multiples; \a{e} and \a{f} are
coordinate space units, just like the units in the translate(x,y)
method.
\sa transform()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_setTransform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 6)
r->d()->context()->setTransform( argv[0].toNumber()
, argv[1].toNumber()
, argv[2].toNumber()
, argv[3].toNumber()
, argv[4].toNumber()
, argv[5].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::transform(real a, real b, real c, real d, real e, real f)
This method is very similar to setTransform(), but instead of replacing
the old transform matrix, this method applies the given tranform matrix
to the current matrix by multiplying to it.
The setTransform(\a a, \a b, \a c, \a d, \a e, \a f) method actually
resets the current transform to the identity matrix, and then invokes
the transform(\a a, \a b, \a c, \a d, \a e, \a f) method with the same
arguments.
\sa setTransform()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_transform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 6)
r->d()->context()->transform( argv[0].toNumber()
, argv[1].toNumber()
, argv[2].toNumber()
, argv[3].toNumber()
, argv[4].toNumber()
, argv[5].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::translate(real x, real y)
Translates the origin of the canvas by a horizontal distance of \a x,
and a vertical distance of \a y, in coordinate space units.
Translating the origin enables you to draw patterns of different objects on the canvas
without having to measure the coordinates manually for each shape.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_translate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 2)
r->d()->context()->translate(argv[0].toNumber(), argv[1].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::resetTransform()
Reset the transformation matrix to the default value (equivalent to calling
setTransform(\c 1, \c 0, \c 0, \c 1, \c 0, \c 0)).
\sa transform(), setTransform(), reset()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_resetTransform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
r->d()->context()->setTransform(1, 0, 0, 1, 0, 0);
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::shear(real sh, real sv)
Shears the transformation matrix by \a sh in the horizontal direction and
\a sv in the vertical direction.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_shear(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 2)
r->d()->context()->shear(argv[0].toNumber(), argv[1].toNumber());
RETURN_RESULT(*thisObject);
}
// compositing
/*!
\qmlproperty real QtQuick::Context2D::globalAlpha
Holds the current alpha value applied to rendering operations.
The value must be in the range from \c 0.0 (fully transparent) to \c 1.0 (fully opaque).
The default value is \c 1.0.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_globalAlpha(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
RETURN_RESULT(QV4::Encode(r->d()->context()->state.globalAlpha));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_globalAlpha(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT_SETTER(r)
double globalAlpha = argc ? argv[0].toNumber() : qt_qnan();
if (!qt_is_finite(globalAlpha))
RETURN_UNDEFINED();
if (globalAlpha >= 0.0 && globalAlpha <= 1.0 && r->d()->context()->state.globalAlpha != globalAlpha) {
r->d()->context()->state.globalAlpha = globalAlpha;
r->d()->context()->buffer()->setGlobalAlpha(r->d()->context()->state.globalAlpha);
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty string QtQuick::Context2D::globalCompositeOperation
Holds the current the current composition operation. Allowed operations are:
\value "source-atop"
QPainter::CompositionMode_SourceAtop
A atop B. Display the source image wherever both images are opaque.
Display the destination image wherever the destination image is opaque
but the source image is transparent. Display transparency elsewhere.
\value "source-in"
QPainter::CompositionMode_SourceIn
A in B. Display the source image wherever both the source image and
destination image are opaque. Display transparency elsewhere.
\value "source-out"
QPainter::CompositionMode_SourceOut
A out B. Display the source image wherever the source image is opaque
and the destination image is transparent. Display transparency elsewhere.
\value "source-over"
QPainter::CompositionMode_SourceOver (default)
A over B. Display the source image wherever the source image is opaque.
Display the destination image elsewhere.
\value "destination-atop"
QPainter::CompositionMode_DestinationAtop
B atop A. Same as \c source-atop but using the destination image instead
of the source image and vice versa.
\value "destination-in"
QPainter::CompositionMode_DestinationIn
B in A. Same as \c source-in but using the destination image instead of
the source image and vice versa.
\value "destination-out"
QPainter::CompositionMode_DestinationOut
B out A. Same as \c source-out but using the destination image instead
of the source image and vice versa.
\value "destination-over"
QPainter::CompositionMode_DestinationOver
B over A. Same as \c source-over but using the destination image
instead of the source image and vice versa.
\value "lighter"
QPainter::CompositionMode_Plus
A plus B. Display the sum of the source image and destination image,
with color values approaching \c 255 (100%) as a limit.
\value "copy"
QPainter::CompositionMode_Source
A (B is ignored). Display the source image instead of the destination image.
\value "xor"
QPainter::CompositionMode_Xor
A xor B. Exclusive OR of the source image and destination image.
\value "qt-clear"
QPainter::CompositionMode_Clear
\value "qt-destination"
QPainter::CompositionMode_Destination
\value "qt-multiply"
QPainter::CompositionMode_Multiply
\value "qt-screen"
QPainter::CompositionMode_Screen
\value "qt-overlay"
QPainter::CompositionMode_Overlay
\value "qt-darken"
QPainter::CompositionMode_Darken
\value "qt-lighten"
QPainter::CompositionMode_Lighten
\value "qt-color-dodge"
QPainter::CompositionMode_ColorDodge
\value "qt-color-burn"
QPainter::CompositionMode_ColorBurn
\value "qt-hard-light"
QPainter::CompositionMode_HardLight
\value "qt-soft-light"
QPainter::CompositionMode_SoftLight
\value "qt-difference"
QPainter::CompositionMode_Difference
\value "qt-exclusion"
QPainter::CompositionMode_Exclusion
In compliance with the W3C standard, the extended composition modes beyond
the required modes are provided as "vendorName-operationName" syntax, for
example: QPainter::CompositionMode_Exclusion is provided as "qt-exclusion".
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
RETURN_RESULT(scope.engine->newString(qt_composite_mode_to_string(r->d()->context()->state.globalCompositeOperation)));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT_SETTER(r)
if (!argc)
THROW_TYPE_ERROR();
QString mode = argv[0].toQString();
QPainter::CompositionMode cm = qt_composite_mode_from_string(mode);
if (cm == QPainter::CompositionMode_SourceOver && mode != QLatin1String("source-over"))
RETURN_UNDEFINED();
if (cm != r->d()->context()->state.globalCompositeOperation) {
r->d()->context()->state.globalCompositeOperation = cm;
r->d()->context()->buffer()->setGlobalCompositeOperation(cm);
}
RETURN_UNDEFINED();
}
static QString makeColorString(QColor color)
{
if (color.isValid()) {
if (color.alpha() == 255)
return color.name();
QString alphaString = QString::number(color.alphaF(), 'f');
while (alphaString.endsWith(QLatin1Char('0')))
alphaString.chop(1);
if (alphaString.endsWith(QLatin1Char('.')))
alphaString += QLatin1Char('0');
return QString::fromLatin1("rgba(%1, %2, %3, %4)").arg(color.red()).arg(color.green()).arg(color.blue()).arg(alphaString);
}
return {};
}
// colors and styles
/*!
\qmlproperty variant QtQuick::Context2D::fillStyle
Holds the current style used for filling shapes.
The style can be either a string containing a CSS color, a CanvasGradient or CanvasPattern object. Invalid values are ignored.
This property accepts several color syntaxes:
\list
\li 'rgb(red, green, blue)' - for example: 'rgb(255, 100, 55)' or 'rgb(100%, 70%, 30%)'
\li 'rgba(red, green, blue, alpha)' - for example: 'rgb(255, 100, 55, 1.0)' or 'rgb(100%, 70%, 30%, 0.5)'
\li 'hsl(hue, saturation, lightness)'
\li 'hsla(hue, saturation, lightness, alpha)'
\li '#RRGGBB' - for example: '#00FFCC'
\li Qt.rgba(red, green, blue, alpha) - for example: Qt.rgba(0.3, 0.7, 1, 1.0)
\endlist
If the \c fillStyle or \l strokeStyle is assigned many times in a loop, the last Qt.rgba() syntax should be chosen, as it has the
best performance, because it's already a valid QColor value, does not need to be parsed everytime.
The default value is '#000000'.
\sa createLinearGradient()
\sa createRadialGradient()
\sa createPattern()
\sa strokeStyle
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_fillStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (auto str = makeColorString(r->d()->context()->state.fillStyle.color().toRgb()); !str.isEmpty())
RETURN_RESULT(scope.engine->newString(std::move(str)));
RETURN_RESULT(r->d()->context()->m_fillStyle.value());
}
QV4::ReturnedValue QQuickJSContext2D::method_set_fillStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT_SETTER(r)
QV4::ScopedValue value(scope, argc ? argv[0] : QV4::Value::undefinedValue());
if (value->as<Object>()) {
QColor color = QV4::ExecutionEngine::toVariant(value, QMetaType::fromType<QColor>()).value<QColor>();
if (color.isValid()) {
r->d()->context()->state.fillStyle = color;
r->d()->context()->buffer()->setFillStyle(color);
r->d()->context()->m_fillStyle.set(scope.engine, value);
} else {
QV4::Scoped<QQuickContext2DStyle> style(scope, value->as<QQuickContext2DStyle>());
if (style && *style->d()->brush != r->d()->context()->state.fillStyle) {
r->d()->context()->state.fillStyle = *style->d()->brush;
r->d()->context()->buffer()->setFillStyle(*style->d()->brush, style->d()->patternRepeatX, style->d()->patternRepeatY);
r->d()->context()->m_fillStyle.set(scope.engine, value);
r->d()->context()->state.fillPatternRepeatX = style->d()->patternRepeatX;
r->d()->context()->state.fillPatternRepeatY = style->d()->patternRepeatY;
}
}
} else if (value->isString()) {
QColor color = qt_color_from_string(value);
if (color.isValid() && r->d()->context()->state.fillStyle != QBrush(color)) {
r->d()->context()->state.fillStyle = QBrush(color);
r->d()->context()->buffer()->setFillStyle(r->d()->context()->state.fillStyle);
r->d()->context()->m_fillStyle.set(scope.engine, value);
}
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty enumeration QtQuick::Context2D::fillRule
Holds the current fill rule used for filling shapes. The following fill rules are supported:
\value Qt.OddEvenFill Qt::OddEvenFill
\value Qt.WindingFill (default) Qt::WindingFill
\note Unlike QPainterPath, the Canvas API uses the winding fill as the default fill rule.
The fillRule property is part of the context rendering state.
\sa fillStyle
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
RETURN_RESULT(scope.engine->fromVariant(r->d()->context()->state.fillRule));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT_SETTER(r)
QV4::ScopedValue value(scope, argc ? argv[0] : QV4::Value::undefinedValue());
if ((value->isString() && value->toQString() == QLatin1String("WindingFill"))
|| (value->isInt32() && value->integerValue() == Qt::WindingFill)) {
r->d()->context()->state.fillRule = Qt::WindingFill;
} else if ((value->isString() && value->toQStringNoThrow() == QLatin1String("OddEvenFill"))
|| (value->isInt32() && value->integerValue() == Qt::OddEvenFill)) {
r->d()->context()->state.fillRule = Qt::OddEvenFill;
} else {
//error
}
r->d()->context()->m_path.setFillRule(r->d()->context()->state.fillRule);
RETURN_UNDEFINED();
}
/*!
\qmlproperty variant QtQuick::Context2D::strokeStyle
Holds the current color or style to use for the lines around shapes,
The style can be either a string containing a CSS color, a CanvasGradient or CanvasPattern object.
Invalid values are ignored.
The default value is '#000000'.
\sa createLinearGradient()
\sa createRadialGradient()
\sa createPattern()
\sa fillStyle
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_strokeStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (auto str = makeColorString(r->d()->context()->state.strokeStyle.color().toRgb()); !str.isEmpty())
RETURN_RESULT(scope.engine->newString(std::move(str)));
RETURN_RESULT(r->d()->context()->m_strokeStyle.value());
}
QV4::ReturnedValue QQuickJSContext2D::method_set_strokeStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT_SETTER(r)
QV4::ScopedValue value(scope, argc ? argv[0] : QV4::Value::undefinedValue());
if (value->as<Object>()) {
QColor color = QV4::ExecutionEngine::toVariant(value, QMetaType::fromType<QColor>()).value<QColor>();
if (color.isValid()) {
r->d()->context()->state.strokeStyle = color;
r->d()->context()->buffer()->setStrokeStyle(color);
r->d()->context()->m_strokeStyle.set(scope.engine, value);
} else {
QV4::Scoped<QQuickContext2DStyle> style(scope, value->as<QQuickContext2DStyle>());
if (style && *style->d()->brush != r->d()->context()->state.strokeStyle) {
r->d()->context()->state.strokeStyle = *style->d()->brush;
r->d()->context()->buffer()->setStrokeStyle(*style->d()->brush, style->d()->patternRepeatX, style->d()->patternRepeatY);
r->d()->context()->m_strokeStyle.set(scope.engine, value);
r->d()->context()->state.strokePatternRepeatX = style->d()->patternRepeatX;
r->d()->context()->state.strokePatternRepeatY = style->d()->patternRepeatY;
} else if (!style && r->d()->context()->state.strokeStyle != QBrush(QColor())) {
// If there is no style object, then ensure that the strokeStyle is at least
// QColor in case it was previously set
r->d()->context()->state.strokeStyle = QBrush(QColor());
r->d()->context()->buffer()->setStrokeStyle(r->d()->context()->state.strokeStyle);
r->d()->context()->m_strokeStyle.set(scope.engine, value);
}
}
} else if (value->isString()) {
QColor color = qt_color_from_string(value);
if (color.isValid() && r->d()->context()->state.strokeStyle != QBrush(color)) {
r->d()->context()->state.strokeStyle = QBrush(color);
r->d()->context()->buffer()->setStrokeStyle(r->d()->context()->state.strokeStyle);
r->d()->context()->m_strokeStyle.set(scope.engine, value);
}
}
RETURN_UNDEFINED();
}
/*!
\qmlmethod CanvasGradient QtQuick::Context2D::createLinearGradient(real x0, real y0, real x1, real y1)
Returns a CanvasGradient object that represents a linear gradient that transitions the color along a line between
the start point (\a x0, \a y0) and the end point (\a x1, \a y1).
A gradient is a smooth transition between colors. There are two types of gradients: linear and radial.
Gradients must have two or more color stops, representing color shifts positioned from 0 to 1 between
to the gradient's starting and end points or circles.
\sa CanvasGradient::addColorStop()
\sa createRadialGradient()
\sa createConicalGradient()
\sa createPattern()
\sa fillStyle
\sa strokeStyle
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_createLinearGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 4) {
qreal x0 = argv[0].toNumber();
qreal y0 = argv[1].toNumber();
qreal x1 = argv[2].toNumber();
qreal y1 = argv[3].toNumber();
if (!qt_is_finite(x0)
|| !qt_is_finite(y0)
|| !qt_is_finite(x1)
|| !qt_is_finite(y1)) {
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createLinearGradient(): Incorrect arguments")
}
QQuickContext2DEngineData *ed = engineData(scope.engine);
QV4::Scoped<QQuickContext2DStyle> gradient(scope, scope.engine->memoryManager->allocate<QQuickContext2DStyle>());
QV4::ScopedObject p(scope, ed->gradientProto.value());
gradient->setPrototypeOf(p);
*gradient->d()->brush = QLinearGradient(x0, y0, x1, y1);
RETURN_RESULT(*gradient);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod CanvasGradient QtQuick::Context2D::createRadialGradient(real x0, real y0, real r0, real x1, real y1, real r1)
Returns a CanvasGradient object that represents a radial gradient that
paints along the cone given by the start circle with origin (\a x0, \a y0)
and radius \a r0, and the end circle with origin (\a x1, \a y1) and radius
\a r1.
\sa CanvasGradient::addColorStop()
\sa createLinearGradient()
\sa createConicalGradient()
\sa createPattern()
\sa fillStyle
\sa strokeStyle
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_createRadialGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 6) {
qreal x0 = argv[0].toNumber();
qreal y0 = argv[1].toNumber();
qreal r0 = argv[2].toNumber();
qreal x1 = argv[3].toNumber();
qreal y1 = argv[4].toNumber();
qreal r1 = argv[5].toNumber();
if (!qt_is_finite(x0)
|| !qt_is_finite(y0)
|| !qt_is_finite(x1)
|| !qt_is_finite(r0)
|| !qt_is_finite(r1)
|| !qt_is_finite(y1)) {
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createRadialGradient(): Incorrect arguments")
}
if (r0 < 0 || r1 < 0)
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "createRadialGradient(): Incorrect arguments")
QQuickContext2DEngineData *ed = engineData(scope.engine);
QV4::Scoped<QQuickContext2DStyle> gradient(scope, scope.engine->memoryManager->allocate<QQuickContext2DStyle>());
QV4::ScopedObject p(scope, ed->gradientProto.value());
gradient->setPrototypeOf(p);
*gradient->d()->brush = QRadialGradient(QPointF(x1, y1), r1, QPointF(x0, y0), r0);
RETURN_RESULT(*gradient);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod CanvasGradient QtQuick::Context2D::createConicalGradient(real x, real y, real angle)
Returns a CanvasGradient object that represents a conical gradient that
interpolates colors counter-clockwise around a center point (\a x, \a y)
with a start angle \a angle in units of radians.
\sa CanvasGradient::addColorStop()
\sa createLinearGradient()
\sa createRadialGradient()
\sa createPattern()
\sa fillStyle
\sa strokeStyle
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_createConicalGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 3) {
qreal x = argv[0].toNumber();
qreal y = argv[1].toNumber();
qreal angle = qRadiansToDegrees(argv[2].toNumber());
if (!qt_is_finite(x) || !qt_is_finite(y)) {
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createConicalGradient(): Incorrect arguments");
}
if (!qt_is_finite(angle)) {
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "createConicalGradient(): Incorrect arguments");
}
QQuickContext2DEngineData *ed = engineData(scope.engine);
QV4::Scoped<QQuickContext2DStyle> gradient(scope, scope.engine->memoryManager->allocate<QQuickContext2DStyle>());
QV4::ScopedObject p(scope, ed->gradientProto.value());
gradient->setPrototypeOf(p);
*gradient->d()->brush = QConicalGradient(x, y, angle);
RETURN_RESULT(*gradient);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod variant QtQuick::Context2D::createPattern(color color, enumeration patternMode)
This is an overloaded function.
Returns a CanvasPattern object that uses the given \a color and \a patternMode.
The valid pattern modes are:
\value Qt.SolidPattern Qt::SolidPattern
\value Qt.Dense1Pattern Qt::Dense1Pattern
\value Qt.Dense2Pattern Qt::Dense2Pattern
\value Qt.Dense3Pattern Qt::Dense3Pattern
\value Qt.Dense4Pattern Qt::Dense4Pattern
\value Qt.Dense5Pattern Qt::Dense5Pattern
\value Qt.Dense6Pattern Qt::Dense6Pattern
\value Qt.Dense7Pattern Qt::Dense7Pattern
\value Qt.HorPattern Qt::HorPattern
\value Qt.VerPattern Qt::VerPattern
\value Qt.CrossPattern Qt::CrossPattern
\value Qt.BDiagPattern Qt::BDiagPattern
\value Qt.FDiagPattern Qt::FDiagPattern
\value Qt.DiagCrossPattern Qt::DiagCrossPattern
\sa Qt::BrushStyle
*/
/*!
\qmlmethod variant QtQuick::Context2D::createPattern(Image image, string repetition)
Returns a CanvasPattern object that uses the given image and repeats in the
direction(s) given by the repetition argument.
The \a image parameter must be a valid Image item, a valid CanvasImageData
object or loaded image url. If there is no image data, thus function throws an
INVALID_STATE_ERR exception.
The allowed values for \a repetition are:
\value "repeat" both directions
\value "repeat-x horizontal only
\value "repeat-y" vertical only
\value "no-repeat" neither
If the repetition argument is empty or null, the value "repeat" is used.
\sa strokeStyle
\sa fillStyle
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_createPattern(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 2) {
QV4::Scoped<QQuickContext2DStyle> pattern(scope, scope.engine->memoryManager->allocate<QQuickContext2DStyle>());
QColor color = QV4::ExecutionEngine::toVariant(
argv[0], QMetaType::fromType<QColor>()).value<QColor>();
if (color.isValid()) {
int patternMode = argv[1].toInt32();
Qt::BrushStyle style = Qt::SolidPattern;
if (patternMode >= 0 && patternMode < Qt::LinearGradientPattern) {
style = static_cast<Qt::BrushStyle>(patternMode);
}
*pattern->d()->brush = QBrush(color, style);
} else {
QImage patternTexture;
if (const QV4::Object *o = argv[0].as<Object>()) {
QV4::ScopedString s(scope, scope.engine->newString(QStringLiteral("data")));
QV4::Scoped<QQuickJSContext2DPixelData> pixelData(scope, o->get(s));
if (!!pixelData) {
patternTexture = *pixelData->d()->image;
}
} else {
patternTexture = r->d()->context()->createPixmap(QUrl(argv[0].toQStringNoThrow()))->image();
}
if (!patternTexture.isNull()) {
pattern->d()->brush->setTextureImage(patternTexture);
QString repetition = argv[1].toQStringNoThrow();
if (repetition == QLatin1String("repeat") || repetition.isEmpty()) {
pattern->d()->patternRepeatX = true;
pattern->d()->patternRepeatY = true;
} else if (repetition == QLatin1String("repeat-x")) {
pattern->d()->patternRepeatX = true;
pattern->d()->patternRepeatY = false;
} else if (repetition == QLatin1String("repeat-y")) {
pattern->d()->patternRepeatX = false;
pattern->d()->patternRepeatY = true;
} else if (repetition == QLatin1String("no-repeat")) {
pattern->d()->patternRepeatX = false;
pattern->d()->patternRepeatY = false;
} else {
//TODO: exception: SYNTAX_ERR
}
}
}
RETURN_RESULT(*pattern);
}
RETURN_UNDEFINED();
}
// line styles
/*!
\qmlproperty string QtQuick::Context2D::lineCap
Holds the current line cap style.
The possible line cap styles are:
\value "butt"
(default) Qt::FlatCap the end of each line has a flat edge
perpendicular to the direction of the line.
\value "round"
Qt::RoundCap a semi-circle with the diameter equal to the width of the
line is added on to the end of the line.
\value "square"
Qt::SquareCap a rectangle with the length of the line width and the
width of half the line width, placed flat against the edge
perpendicular to the direction of the line.
Other values are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
switch (r->d()->context()->state.lineCap) {
case Qt::RoundCap:
RETURN_RESULT(scope.engine->newString(QStringLiteral("round")));
case Qt::SquareCap:
RETURN_RESULT(scope.engine->newString(QStringLiteral("square")));
case Qt::FlatCap:
default:
break;
}
RETURN_RESULT(scope.engine->newString(QStringLiteral("butt")));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
if (!argc)
return QV4::Encode::undefined();
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
QString lineCap = argv[0].toQString();
Qt::PenCapStyle cap;
if (lineCap == QLatin1String("round"))
cap = Qt::RoundCap;
else if (lineCap == QLatin1String("butt"))
cap = Qt::FlatCap;
else if (lineCap == QLatin1String("square"))
cap = Qt::SquareCap;
else
RETURN_UNDEFINED();
if (cap != r->d()->context()->state.lineCap) {
r->d()->context()->state.lineCap = cap;
r->d()->context()->buffer()->setLineCap(cap);
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty string QtQuick::Context2D::lineJoin
Holds the current line join style. A join exists at any point in a subpath
shared by two consecutive lines. When a subpath is closed, then a join also
exists at its first point (equivalent to its last point) connecting the
first and last lines in the subpath.
The possible line join styles are:
\value "bevel" Qt::BevelJoin The triangular notch between the two lines is filled.
\value "round" Qt::RoundJoin A circular arc between the two lines is filled.
\value "miter" (default) Qt::MiterJoin The outer edges of the lines are extended to
meet at an angle, and this area is filled.
Other values are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
switch (r->d()->context()->state.lineJoin) {
case Qt::RoundJoin:
RETURN_RESULT(scope.engine->newString(QStringLiteral("round")));
case Qt::BevelJoin:
RETURN_RESULT(scope.engine->newString(QStringLiteral("bevel")));
case Qt::MiterJoin:
default:
break;
}
RETURN_RESULT(scope.engine->newString(QStringLiteral("miter")));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
if (!argc)
THROW_TYPE_ERROR();
QString lineJoin = argv[0].toQString();
Qt::PenJoinStyle join;
if (lineJoin == QLatin1String("round"))
join = Qt::RoundJoin;
else if (lineJoin == QLatin1String("bevel"))
join = Qt::BevelJoin;
else if (lineJoin == QLatin1String("miter"))
join = Qt::SvgMiterJoin;
else
RETURN_UNDEFINED();
if (join != r->d()->context()->state.lineJoin) {
r->d()->context()->state.lineJoin = join;
r->d()->context()->buffer()->setLineJoin(join);
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty real QtQuick::Context2D::lineWidth
Holds the current line width. Values that are not finite values greater than zero are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_lineWidth(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(QV4::Encode(r->d()->context()->state.lineWidth));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_lineWidth(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
qreal w = argc ? argv[0].toNumber() : -1;
if (w > 0 && qt_is_finite(w) && w != r->d()->context()->state.lineWidth) {
r->d()->context()->state.lineWidth = w;
r->d()->context()->buffer()->setLineWidth(w);
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty real QtQuick::Context2D::miterLimit
Holds the current miter limit ratio.
The default miter limit value is 10.0.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_miterLimit(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(QV4::Encode(r->d()->context()->state.miterLimit));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_miterLimit(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
qreal ml = argc ? argv[0].toNumber() : -1;
if (ml > 0 && qt_is_finite(ml) && ml != r->d()->context()->state.miterLimit) {
r->d()->context()->state.miterLimit = ml;
r->d()->context()->buffer()->setMiterLimit(ml);
}
RETURN_UNDEFINED();
}
/*!
\qmlmethod array QtQuick::Context2D::getLineDash()
\since QtQuick 2.11
Returns an array of qreals representing the dash pattern of the line.
\sa setLineDash(), lineDashOffset
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_getLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
const QList<qreal> pattern = r->d()->context()->state.lineDash;
QV4::ScopedArrayObject array(scope, scope.engine->newArrayObject(pattern.size()));
array->arrayReserve(pattern.size());
for (int i = 0; i < pattern.size(); i++)
array->put(i, QV4::Value::fromDouble(pattern[i]));
array->setArrayLengthUnchecked(pattern.size());
RETURN_RESULT(*array);
}
/*!
\qmlmethod void QtQuick::Context2D::setLineDash(array pattern)
\since QtQuick 2.11
Sets the dash pattern to the given pattern.
\a pattern a list of numbers that specifies distances to alternately draw a line and a gap.
If the number of elements in the array is odd, the elements of the array get copied
and concatenated. For example, [5, 15, 25] will become [5, 15, 25, 5, 15, 25].
\table 100%
\row
\li \inlineimage qml-item-canvas-lineDash.png
{Dashed line with varying segment lengths}
\li
\code
var space = 4
ctx.setLineDash([1, space, 3, space, 9, space, 27, space, 9, space])
...
ctx.stroke();
\endcode
\endtable
\sa getLineDash(), lineDashOffset
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_setLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
if (!argc)
RETURN_UNDEFINED();
QV4::ScopedArrayObject array(scope, argv[0]);
if (!array)
RETURN_UNDEFINED();
QV4::ScopedValue v(scope);
const uint arrayLength = array->getLength();
QList<qreal> dashes;
dashes.reserve(arrayLength);
for (uint i = 0; i < arrayLength; ++i) {
v = array->get(i);
const double number = v->toNumber();
if (!qt_is_finite(number) || (number < 0))
RETURN_UNDEFINED();
dashes.append(v->toNumber());
}
if (dashes.size() % 2 != 0) {
dashes += dashes;
}
r->d()->context()->state.lineDash = dashes;
r->d()->context()->buffer()->setLineDash(dashes);
RETURN_UNDEFINED();
}
/*!
\qmlproperty real QtQuick::Context2D::lineDashOffset
\since QtQuick 2.11
Holds the current line dash offset.
The default line dash offset value is \c 0.
\sa getLineDash(), setLineDash()
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(QV4::Encode(r->d()->context()->state.lineDashOffset));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
const qreal offset = argc ? argv[0].toNumber() : -1;
if (qt_is_finite(offset) && offset != r->d()->context()->state.lineDashOffset) {
r->d()->context()->state.lineDashOffset = offset;
r->d()->context()->buffer()->setLineDashOffset(offset);
}
RETURN_UNDEFINED();
}
// shadows
/*!
\qmlproperty real QtQuick::Context2D::shadowBlur
Holds the current level of blur applied to shadows
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_shadowBlur(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(QV4::Encode(r->d()->context()->state.shadowBlur));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_shadowBlur(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
qreal blur = argc ? argv[0].toNumber() : -1;
if (blur > 0 && qt_is_finite(blur) && blur != r->d()->context()->state.shadowBlur) {
r->d()->context()->state.shadowBlur = blur;
r->d()->context()->buffer()->setShadowBlur(blur);
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty string QtQuick::Context2D::shadowColor
Holds the current shadow color.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_shadowColor(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(scope.engine->newString(r->d()->context()->state.shadowColor.name()));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_shadowColor(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
QColor color;
if (argc)
color = qt_color_from_string(argv[0]);
if (color.isValid() && color != r->d()->context()->state.shadowColor) {
r->d()->context()->state.shadowColor = color;
r->d()->context()->buffer()->setShadowColor(color);
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty real QtQuick::Context2D::shadowOffsetX
Holds the current shadow offset in the positive horizontal distance.
\sa shadowOffsetY
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_shadowOffsetX(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(QV4::Encode(r->d()->context()->state.shadowOffsetX));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_shadowOffsetX(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
qreal offsetX = argc ? argv[0].toNumber() : qt_qnan();
if (qt_is_finite(offsetX) && offsetX != r->d()->context()->state.shadowOffsetX) {
r->d()->context()->state.shadowOffsetX = offsetX;
r->d()->context()->buffer()->setShadowOffsetX(offsetX);
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty real QtQuick::Context2D::shadowOffsetY
Holds the current shadow offset in the positive vertical distance.
\sa shadowOffsetX
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_shadowOffsetY(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(QV4::Encode(r->d()->context()->state.shadowOffsetY));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_shadowOffsetY(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
qreal offsetY = argc ? argv[0].toNumber() : qt_qnan();
if (qt_is_finite(offsetY) && offsetY != r->d()->context()->state.shadowOffsetY) {
r->d()->context()->state.shadowOffsetY = offsetY;
r->d()->context()->buffer()->setShadowOffsetY(offsetY);
}
RETURN_UNDEFINED();
}
#if QT_CONFIG(quick_path)
QV4::ReturnedValue QQuickJSContext2D::method_get_path(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(r->d()->context()->m_v4path.value());
}
QV4::ReturnedValue QQuickJSContext2D::method_set_path(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
QV4::ScopedValue value(scope, argc ? argv[0] : QV4::Value::undefinedValue());
r->d()->context()->beginPath();
QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope, value);
if (!!qobjectWrapper) {
if (QQuickPath *path = qobject_cast<QQuickPath*>(qobjectWrapper->object()))
r->d()->context()->m_path = path->path();
} else {
QString path =value->toQStringNoThrow();
std::optional<QPainterPath> qpath = QGuiSvg::parsePath(path);
if (qpath)
r->d()->context()->m_path = qpath.value();
}
r->d()->context()->m_v4path.set(scope.engine, value);
RETURN_UNDEFINED();
}
#endif // QT_CONFIG(quick_path)
//rects
/*!
\qmlmethod Context2D QtQuick::Context2D::clearRect(real x, real y, real w, real h)
Clears all pixels on the canvas in the rectangle specified by
(\a x, \a y, \a w, \a h) to transparent black.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_clearRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 4)
r->d()->context()->clearRect(argv[0].toNumber(),
argv[1].toNumber(),
argv[2].toNumber(),
argv[3].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::fillRect(real x, real y, real w, real h)
Paints a rectangular area specified by (\a x, \a y, \a w, \a h) using fillStyle.
\sa fillStyle
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_fillRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 4)
r->d()->context()->fillRect(argv[0].toNumber(), argv[1].toNumber(), argv[2].toNumber(), argv[3].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::strokeRect(real x, real y, real w, real h)
Strokes the path of the rectangle specified by (\a x, \a y, \a w, \a h) using
strokeStyle, lineWidth, lineJoin, and (if appropriate) miterLimit attributes.
\sa strokeStyle, lineWidth, lineJoin, miterLimit
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_strokeRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 4)
r->d()->context()->strokeRect(argv[0].toNumber(), argv[1].toNumber(), argv[2].toNumber(), argv[3].toNumber());
RETURN_RESULT(*thisObject);
}
// Complex shapes (paths) API
/*!
\qmlmethod Context2D QtQuick::Context2D::arc(real x, real y, real radius,
real startAngle, real endAngle, bool anticlockwise)
Adds an arc to the current subpath that lies on the circumference of the
circle whose center is at the point (\a x, \a y) and whose radius is
\a radius.
Both \a startAngle and \a endAngle are measured from the x-axis in radians.
\image qml-item-canvas-arc.png {Circle and arc showing center point
(x,y) and radius}
\image qml-item-canvas-startAngle.png {Four arcs showing different
endAngle values from π/2 to 2π, all starting at angle 0}
The \a anticlockwise parameter is \c false for each arc in the figure above
because they are all drawn in the clockwise direction.
Context Standard for arc()}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_arc(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 5) {
bool antiClockwise = false;
if (argc == 6)
antiClockwise = argv[5].toBoolean();
qreal radius = argv[2].toNumber();
if (qt_is_finite(radius) && radius < 0)
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "Incorrect argument radius");
r->d()->context()->arc(argv[0].toNumber(),
argv[1].toNumber(),
radius,
argv[3].toNumber(),
argv[4].toNumber(),
antiClockwise);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::arcTo(real x1, real y1, real x2,
real y2, real radius)
Adds an arc with the given control points and radius to the current subpath,
connected to the previous point by a straight line. To draw an arc, you
begin with the same steps you followed to create a line:
\list
\li Call the beginPath() method to set a new path.
\li Call the moveTo(\c x, \c y) method to set your starting position on the
canvas at the point (\c x, \c y).
\li To draw an arc or circle, call the arcTo(\a x1, \a y1, \a x2, \a y2,
\a radius) method. This adds an arc with starting point (\a x1, \a y1),
ending point (\a x2, \a y2), and \a radius to the current subpath and
connects it to the previous subpath by a straight line.
\endlist
\image qml-item-canvas-arcTo.png {Arc construction showing control
points (x1,y1), (x2,y2) and radius for tangent arc}
Context Standard for arcTo()}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_arcTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 5) {
qreal radius = argv[4].toNumber();
if (qt_is_finite(radius) && radius < 0)
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "Incorrect argument radius");
r->d()->context()->arcTo(argv[0].toNumber(),
argv[1].toNumber(),
argv[2].toNumber(),
argv[3].toNumber(),
radius);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::beginPath()
Resets the current path to a new path.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_beginPath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
r->d()->context()->beginPath();
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::bezierCurveTo(real cp1x, real cp1y, real cp2x, real cp2y, real x, real y)
Adds a cubic bezier curve between the current position and the given endPoint using the control points specified by (\a {cp1x}, \a {cp1y}),
and (\a {cp2x}, \a {cp2y}).
After the curve is added, the current position is updated to be at the end point (\a {x}, \a {y}) of the curve.
The following code produces the path shown below:
\code
ctx.strokeStyle = Qt.rgba(0, 0, 0, 1);
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(20, 0);//start point
ctx.bezierCurveTo(-10, 90, 210, 90, 180, 0);
ctx.stroke();
\endcode
\image qml-item-canvas-bezierCurveTo.png {Cubic bezier curve forming
a smooth downward arc}
\sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-beziercurveto}{W3C 2d context standard for bezierCurveTo}
\sa {https://web.archive.org/web/20130505222636if_/http://www.openrise.com/lab/FlowerPower/}{The beautiful flower demo by using bezierCurveTo}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_bezierCurveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 6) {
qreal cp1x = argv[0].toNumber();
qreal cp1y = argv[1].toNumber();
qreal cp2x = argv[2].toNumber();
qreal cp2y = argv[3].toNumber();
qreal x = argv[4].toNumber();
qreal y = argv[5].toNumber();
if (!qt_is_finite(cp1x) || !qt_is_finite(cp1y) || !qt_is_finite(cp2x) || !qt_is_finite(cp2y) || !qt_is_finite(x) || !qt_is_finite(y))
RETURN_UNDEFINED();
r->d()->context()->bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::clip()
Creates the clipping region from the current path.
Any parts of the shape outside the clipping path are not displayed.
To create a complex shape using the \c clip() method:
\list 1
\li Call the \c{context.beginPath()} method to set the clipping path.
\li Define the clipping path by calling any combination of the \c{lineTo},
\c{arcTo}, \c{arc}, \c{moveTo}, etc and \c{closePath} methods.
\li Call the \c{context.clip()} method.
\endlist
The new shape displays. The following shows how a clipping path can
modify how an image displays:
\image qml-item-canvas-clip-complex.png {Image before and after
clipping to a star shape, showing only the clipped region}
\sa beginPath()
\sa closePath()
\sa stroke()
\sa fill()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_clip(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
r->d()->context()->clip();
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::closePath()
Closes the current subpath by drawing a line to the beginning of the subpath, automatically starting a new path.
The current point of the new path is the previous subpath's first point.
\sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-closepath}{W3C 2d context standard for closePath}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_closePath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
r->d()->context()->closePath();
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::fill()
Fills the subpaths with the current fill style.
\sa fillStyle
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_fill(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r);
r->d()->context()->fill();
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::lineTo(real x, real y)
Draws a line from the current position to the point at (\a x, \a y).
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_lineTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 2) {
qreal x = argv[0].toNumber();
qreal y = argv[1].toNumber();
if (!qt_is_finite(x) || !qt_is_finite(y))
RETURN_UNDEFINED();
r->d()->context()->lineTo(x, y);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::moveTo(real x, real y)
Creates a new subpath with a point at (\a x, \a y).
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_moveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 2) {
qreal x = argv[0].toNumber();
qreal y = argv[1].toNumber();
if (!qt_is_finite(x) || !qt_is_finite(y))
RETURN_UNDEFINED();
r->d()->context()->moveTo(x, y);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::quadraticCurveTo(real cpx, real cpy, real x, real y)
Adds a quadratic bezier curve between the current point and the endpoint
(\a x, \a y) with the control point specified by (\a cpx, \a cpy).
\sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-quadraticcurveto}{W3C 2d context standard for quadraticCurveTo}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_quadraticCurveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 4) {
qreal cpx = argv[0].toNumber();
qreal cpy = argv[1].toNumber();
qreal x = argv[2].toNumber();
qreal y = argv[3].toNumber();
if (!qt_is_finite(cpx) || !qt_is_finite(cpy) || !qt_is_finite(x) || !qt_is_finite(y))
RETURN_UNDEFINED();
r->d()->context()->quadraticCurveTo(cpx, cpy, x, y);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::rect(real x, real y, real w, real h)
Adds a rectangle at position (\a x, \a y), with the given width \a w and
height \a h, as a closed subpath.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_rect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 4)
r->d()->context()->rect(argv[0].toNumber(), argv[1].toNumber(), argv[2].toNumber(), argv[3].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::roundedRect(real x, real y, real w, real h, real xRadius, real yRadius)
Adds a rounded-corner rectangle, specified by (\a x, \a y, \a w, \a h), to the path.
The \a xRadius and \a yRadius arguments specify the radius of the
ellipses defining the corners of the rounded rectangle.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_roundedRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 6)
r->d()->context()->roundedRect(argv[0].toNumber()
, argv[1].toNumber()
, argv[2].toNumber()
, argv[3].toNumber()
, argv[4].toNumber()
, argv[5].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::ellipse(real x, real y, real w, real h)
Creates an ellipse within the bounding rectangle defined by its top-left
corner at (\a x, \a y), width \a w and height \a h, and adds it to the
path as a closed subpath.
The ellipse is composed of a clockwise curve, starting and finishing at
zero degrees (the 3 o'clock position).
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_ellipse(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 4)
r->d()->context()->ellipse(argv[0].toNumber(), argv[1].toNumber(), argv[2].toNumber(), argv[3].toNumber());
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::text(string text, real x, real y)
Adds the given \a text to the path as a set of closed subpaths created
from the current context font supplied.
The subpaths are positioned so that the left end of the text's baseline
lies at the point specified by (\a x, \a y).
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_text(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 3) {
qreal x = argv[1].toNumber();
qreal y = argv[2].toNumber();
if (!qt_is_finite(x) || !qt_is_finite(y))
RETURN_UNDEFINED();
r->d()->context()->text(argv[0].toQStringNoThrow(), x, y);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::stroke()
Strokes the subpaths with the current stroke style.
\sa strokeStyle, {http://www.w3.org/TR/2dcontext/#dom-context-2d-stroke}{W3C 2d context standard for stroke}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_stroke(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
r->d()->context()->stroke();
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod bool QtQuick::Context2D::isPointInPath(real x, real y)
Returns \c true if the point (\a x, \a y) is in the current path.
\sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-ispointinpath}{W3C 2d context standard for isPointInPath}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_isPointInPath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
bool pointInPath = false;
if (argc >= 2)
pointInPath = r->d()->context()->isPointInPath(argv[0].toNumber(), argv[1].toNumber());
RETURN_RESULT(QV4::Value::fromBoolean(pointInPath).asReturnedValue());
}
QV4::ReturnedValue QQuickJSContext2DPrototype::method_drawFocusRing(const QV4::FunctionObject *b, const QV4::Value *, const QV4::Value *, int)
{
QV4::Scope scope(b);
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "Context2D::drawFocusRing is not supported");
}
QV4::ReturnedValue QQuickJSContext2DPrototype::method_setCaretSelectionRect(const QV4::FunctionObject *b, const QV4::Value *, const QV4::Value *, int)
{
QV4::Scope scope(b);
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "Context2D::setCaretSelectionRect is not supported");
}
QV4::ReturnedValue QQuickJSContext2DPrototype::method_caretBlinkRate(const QV4::FunctionObject *b, const QV4::Value *, const QV4::Value *, int)
{
QV4::Scope scope(b);
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "Context2D::caretBlinkRate is not supported");
}
/*!
\qmlproperty string QtQuick::Context2D::font
Holds the current font settings.
A subset of the
is supported:
\list
\li font-style (optional):
normal | italic | oblique
\li font-variant (optional): normal | small-caps
\li font-weight (optional): normal | bold | 1 ... 1000
\li font-size: Npx | Npt (where N is a positive number)
\endlist
\note The font-size and font-family properties are mandatory and must be in
the order they are shown in above. In addition, a font family with spaces in
its name must be quoted.
The default font value is "10px sans-serif".
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_font(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
RETURN_RESULT(scope.engine->newString(r->d()->context()->state.font.toString()));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_font(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
QV4::ScopedString s(scope, argc ? argv[0] : QV4::Value::undefinedValue(), QV4::ScopedString::Convert);
if (scope.hasException())
RETURN_UNDEFINED();
QFont font = qt_font_from_string(s->toQString(), r->d()->context()->state.font);
if (font != r->d()->context()->state.font) {
r->d()->context()->state.font = font;
}
RETURN_UNDEFINED();
}
/*!
\qmlproperty string QtQuick::Context2D::textAlign
Holds the current text alignment settings. The possible values are:
\value "start" (default) Align to the start edge of the text (left side in
left-to-right text, right side in right-to-left text).
\value "end" Align to the end edge of the text (right side in left-to-right
text, left side in right-to-left text).
\value "left" Qt::AlignLeft
\value "right" Qt::AlignRight
\value "center" Qt::AlignHCenter
Other values are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
switch (r->d()->context()->state.textAlign) {
case QQuickContext2D::End:
RETURN_RESULT(scope.engine->newString(QStringLiteral("end")));
case QQuickContext2D::Left:
RETURN_RESULT(scope.engine->newString(QStringLiteral("left")));
case QQuickContext2D::Right:
RETURN_RESULT(scope.engine->newString(QStringLiteral("right")));
case QQuickContext2D::Center:
RETURN_RESULT(scope.engine->newString(QStringLiteral("center")));
case QQuickContext2D::Start:
default:
break;
}
RETURN_RESULT(scope.engine->newString(QStringLiteral("start")));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
QV4::ScopedString s(scope, argc ? argv[0] : QV4::Value::undefinedValue(), QV4::ScopedString::Convert);
if (scope.hasException())
RETURN_UNDEFINED();
QString textAlign = s->toQString();
QQuickContext2D::TextAlignType ta;
if (textAlign == QLatin1String("start"))
ta = QQuickContext2D::Start;
else if (textAlign == QLatin1String("end"))
ta = QQuickContext2D::End;
else if (textAlign == QLatin1String("left"))
ta = QQuickContext2D::Left;
else if (textAlign == QLatin1String("right"))
ta = QQuickContext2D::Right;
else if (textAlign == QLatin1String("center"))
ta = QQuickContext2D::Center;
else
RETURN_UNDEFINED();
if (ta != r->d()->context()->state.textAlign)
r->d()->context()->state.textAlign = ta;
RETURN_UNDEFINED();
}
/*!
\qmlproperty string QtQuick::Context2D::textBaseline
Holds the current baseline alignment settings. The possible values are:
\value "top" The top of the em square
\value "hanging" The hanging baseline
\value "middle" The middle of the em square
\value "alphabetic" (default) The alphabetic baseline
\value "ideographic" The ideographic-under baseline
\value "bottom" The bottom of the em square
Other values are ignored. The default value is "alphabetic".
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
switch (r->d()->context()->state.textBaseline) {
case QQuickContext2D::Hanging:
RETURN_RESULT(scope.engine->newString(QStringLiteral("hanging")));
case QQuickContext2D::Top:
RETURN_RESULT(scope.engine->newString(QStringLiteral("top")));
case QQuickContext2D::Bottom:
RETURN_RESULT(scope.engine->newString(QStringLiteral("bottom")));
case QQuickContext2D::Middle:
RETURN_RESULT(scope.engine->newString(QStringLiteral("middle")));
case QQuickContext2D::Alphabetic:
default:
break;
}
RETURN_RESULT(scope.engine->newString(QStringLiteral("alphabetic")));
}
QV4::ReturnedValue QQuickJSContext2D::method_set_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT_SETTER(r)
QV4::ScopedString s(scope, argc ? argv[0] : QV4::Value::undefinedValue(), QV4::ScopedString::Convert);
if (scope.hasException())
RETURN_UNDEFINED();
QString textBaseline = s->toQString();
QQuickContext2D::TextBaseLineType tb;
if (textBaseline == QLatin1String("alphabetic"))
tb = QQuickContext2D::Alphabetic;
else if (textBaseline == QLatin1String("hanging"))
tb = QQuickContext2D::Hanging;
else if (textBaseline == QLatin1String("top"))
tb = QQuickContext2D::Top;
else if (textBaseline == QLatin1String("bottom"))
tb = QQuickContext2D::Bottom;
else if (textBaseline == QLatin1String("middle"))
tb = QQuickContext2D::Middle;
else
RETURN_UNDEFINED();
if (tb != r->d()->context()->state.textBaseline)
r->d()->context()->state.textBaseline = tb;
RETURN_UNDEFINED();
}
/*!
\qmlmethod Context2D QtQuick::Context2D::fillText(text, x, y)
Fills the specified \a text at the given position (\a x, \a y).
\sa font
\sa textAlign
\sa textBaseline
\sa strokeText
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_fillText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 3) {
qreal x = argv[1].toNumber();
qreal y = argv[2].toNumber();
if (!qt_is_finite(x) || !qt_is_finite(y))
RETURN_UNDEFINED();
QPainterPath textPath = r->d()->context()->createTextGlyphs(x, y, argv[0].toQStringNoThrow());
r->d()->context()->buffer()->fill(textPath);
}
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod Context2D QtQuick::Context2D::strokeText(text, x, y)
Strokes the given \a text at a position specified by (\a x, \a y).
\sa font
\sa textAlign
\sa textBaseline
\sa fillText
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_strokeText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 3)
r->d()->context()->drawText(argv[0].toQStringNoThrow(), argv[1].toNumber(), argv[2].toNumber(), false);
RETURN_RESULT(*thisObject);
}
/*!
\qmlmethod var QtQuick::Context2D::measureText(text)
Returns an object with a \c width property, whose value is equivalent to
calling QFontMetrics::horizontalAdvance() with the given \a text in the
current font.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_measureText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
if (argc >= 1) {
QFontMetrics fm(r->d()->context()->state.font);
uint width = fm.horizontalAdvance(argv[0].toQStringNoThrow());
QV4::ScopedObject tm(scope, scope.engine->newObject());
tm->put(QV4::ScopedString(scope, scope.engine->newIdentifier(QStringLiteral("width"))).getPointer(),
QV4::ScopedValue(scope, QV4::Value::fromDouble(width)));
RETURN_RESULT(*tm);
}
RETURN_UNDEFINED();
}
// drawing images
/*!
\qmlmethod void QtQuick::Context2D::drawImage(variant image, real dx, real dy)
Draws the given \a image on the canvas at position (\a dx, \a dy).
Note:
The \a image type can be an Image item, an image url or a CanvasImageData object.
When given as Image item, if the image isn't fully loaded, this method draws nothing.
When given as url string, the image should be loaded by calling Canvas item's Canvas::loadImage() method first.
This image been drawing is subject to the current context clip path, even the given \c image is a CanvasImageData object.
\sa CanvasImageData
\sa Image
\sa Canvas::loadImage
\sa Canvas::isImageLoaded
\sa Canvas::imageLoaded
\sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-drawimage}{W3C 2d context standard for drawImage}
*/
/*!
\qmlmethod void QtQuick::Context2D::drawImage(variant image, real dx, real dy, real dw, real dh)
This is an overloaded function.
Draws the given item as \a image onto the canvas at point (\a dx, \a dy) and with width \a dw,
height \a dh.
Note:
The \a image type can be an Image item, an image url or a CanvasImageData object.
When given as Image item, if the image isn't fully loaded, this method draws nothing.
When given as url string, the image should be loaded by calling Canvas item's Canvas::loadImage() method first.
This image been drawing is subject to the current context clip path, even the given \c image is a CanvasImageData object.
\sa CanvasImageData
\sa Image
\sa Canvas::loadImage()
\sa Canvas::isImageLoaded
\sa Canvas::imageLoaded
\sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-drawimage}{W3C 2d context standard for drawImage}
*/
/*!
\qmlmethod void QtQuick::Context2D::drawImage(variant image, real sx, real sy, real sw, real sh, real dx, real dy, real dw, real dh)
This is an overloaded function.
Draws the given item as \a image from source point (\a sx, \a sy) and source width \a sw, source height \a sh
onto the canvas at point (\a dx, \a dy) and with width \a dw, height \a dh.
Note:
The \a image type can be an Image or Canvas item, an image url or a CanvasImageData object.
When given as Image item, if the image isn't fully loaded, this method draws nothing.
When given as url string, the image should be loaded by calling Canvas item's Canvas::loadImage() method first.
This image been drawing is subject to the current context clip path, even the given \c image is a CanvasImageData object.
\sa CanvasImageData
\sa Image
\sa Canvas::loadImage()
\sa Canvas::isImageLoaded
\sa Canvas::imageLoaded
\sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-drawimage}{W3C 2d context standard for drawImage}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_drawImage(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
CHECK_CONTEXT(r)
qreal sx, sy, sw, sh, dx, dy, dw, dh;
if (!argc)
RETURN_UNDEFINED();
//FIXME:This function should be moved to QQuickContext2D::drawImage(...)
if (!r->d()->context()->state.invertibleCTM)
RETURN_UNDEFINED();
QQmlRefPointer<QQuickCanvasPixmap> pixmap;
QV4::ScopedValue arg(scope, argv[0]);
if (arg->isString()) {
QUrl url(arg->toQString());
if (!url.isValid())
THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
pixmap = r->d()->context()->createPixmap(url);
} else if (arg->isObject()) {
QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope, arg);
if (!!qobjectWrapper) {
if (QQuickImage *imageItem = qobject_cast<QQuickImage*>(qobjectWrapper->object())) {
pixmap = r->d()->context()->createPixmap(imageItem->source());
} else if (QQuickCanvasItem *canvas = qobject_cast<QQuickCanvasItem*>(qobjectWrapper->object())) {
QImage img = canvas->toImage();
if (!img.isNull())
pixmap.adopt(new QQuickCanvasPixmap(img));
} else {
THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
}
} else {
QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, arg);
if (!!imageData) {
QV4::Scoped<QQuickJSContext2DPixelData> pix(scope, imageData->d()->pixelData.as<QQuickJSContext2DPixelData>());
if (pix && !pix->d()->image->isNull()) {
pixmap.adopt(new QQuickCanvasPixmap(*pix->d()->image));
} else {
THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
}
} else {
QUrl url(arg->toQStringNoThrow());
if (url.isValid())
pixmap = r->d()->context()->createPixmap(url);
else
THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
}
}
} else {
THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
}
if (pixmap.isNull() || !pixmap->isValid())
RETURN_UNDEFINED();
if (argc >= 9) {
sx = argv[1].toNumber();
sy = argv[2].toNumber();
sw = argv[3].toNumber();
sh = argv[4].toNumber();
dx = argv[5].toNumber();
dy = argv[6].toNumber();
dw = argv[7].toNumber();
dh = argv[8].toNumber();
} else if (argc >= 5) {
sx = 0;
sy = 0;
sw = pixmap->width();
sh = pixmap->height();
dx = argv[1].toNumber();
dy = argv[2].toNumber();
dw = argv[3].toNumber();
dh = argv[4].toNumber();
} else if (argc >= 3) {
dx = argv[1].toNumber();
dy = argv[2].toNumber();
sx = 0;
sy = 0;
sw = pixmap->width();
sh = pixmap->height();
dw = sw;
dh = sh;
} else {
RETURN_UNDEFINED();
}
if (!qt_is_finite(sx)
|| !qt_is_finite(sy)
|| !qt_is_finite(sw)
|| !qt_is_finite(sh)
|| !qt_is_finite(dx)
|| !qt_is_finite(dy)
|| !qt_is_finite(dw)
|| !qt_is_finite(dh))
RETURN_UNDEFINED();
if (sx < 0
|| sy < 0
|| sw == 0
|| sh == 0
|| sx + sw > pixmap->width()
|| sy + sh > pixmap->height()
|| sx + sw < 0 || sy + sh < 0) {
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "drawImage(), index size error");
}
r->d()->context()->buffer()->drawPixmap(pixmap, QRectF(sx, sy, sw, sh), QRectF(dx, dy, dw, dh));
RETURN_RESULT(*thisObject);
}
// pixel manipulation
/*!
\qmltype CanvasImageData
\inqmlmodule QtQuick
\ingroup qtquick-canvas
\brief Contains image pixel data in RGBA order.
The CanvasImageData object holds the image pixel data.
The CanvasImageData object has the actual dimensions of the data stored in
this object and holds the one-dimensional array containing the data in RGBA order,
as integers in the range 0 to 255.
\sa width
\sa height
\sa data
\sa Context2D::createImageData()
\sa Context2D::getImageData()
\sa Context2D::putImageData()
*/
/*!
\qmlproperty int QtQuick::CanvasImageData::width
Holds the actual width dimension of the data in the ImageData object, in device pixels.
*/
QV4::ReturnedValue QQuickJSContext2DImageData::method_get_width(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, *thisObject);
if (!imageData)
THROW_TYPE_ERROR();
QV4::Scoped<QQuickJSContext2DPixelData> r(scope, imageData->d()->pixelData.as<QQuickJSContext2DPixelData>());
int width = r ? r->d()->image->width() : 0;
RETURN_RESULT(QV4::Encode(width));
}
/*!
\qmlproperty int QtQuick::CanvasImageData::height
Holds the actual height dimension of the data in the ImageData object, in device pixels.
*/
QV4::ReturnedValue QQuickJSContext2DImageData::method_get_height(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, *thisObject);
if (!imageData)
THROW_TYPE_ERROR();
QV4::Scoped<QQuickJSContext2DPixelData> r(scope, imageData->d()->pixelData.as<QQuickJSContext2DPixelData>());
int height = r ? r->d()->image->height() : 0;
RETURN_RESULT(QV4::Encode(height));
}
/*!
\qmlproperty CanvasPixelArray QtQuick::CanvasImageData::data
Holds the one-dimensional array containing the data in RGBA order, as integers in the range 0 to 255.
*/
QV4::ReturnedValue QQuickJSContext2DImageData::method_get_data(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, *thisObject);
if (!imageData)
THROW_TYPE_ERROR();
RETURN_RESULT(imageData->d()->pixelData);
}
/*!
\qmltype CanvasPixelArray
\inqmlmodule QtQuick
\ingroup qtquick-canvas
\brief Provides ordered and indexed access to the components of each pixel in image data.
The CanvasPixelArray object provides ordered, indexed access to the color components of each pixel of the image data.
The CanvasPixelArray can be accessed as normal Javascript array.
\sa CanvasImageData
*/
/*!
\qmlproperty int QtQuick::CanvasPixelArray::length
The CanvasPixelArray object represents h×w×4 integers which w and h comes from CanvasImageData.
The length attribute of a CanvasPixelArray object must return this h×w×4 number value.
This property is read only.
*/
QV4::ReturnedValue QQuickJSContext2DPixelData::proto_get_length(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2DPixelData> r(scope, thisObject->as<QQuickJSContext2DPixelData>());
if (!r || r->d()->image->isNull())
RETURN_UNDEFINED();
RETURN_RESULT(QV4::Encode(r->d()->image->width() * r->d()->image->height() * 4));
}
QV4::ReturnedValue QQuickJSContext2DPixelData::virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty)
{
if (!id.isArrayIndex())
return QV4::Object::virtualGet(m, id, receiver, hasProperty);
uint index = id.asArrayIndex();
Q_ASSERT(m->as<QQuickJSContext2DPixelData>());
QV4::ExecutionEngine *v4 = static_cast<const QQuickJSContext2DPixelData *>(m)->engine();
QV4::Scope scope(v4);
QV4::Scoped<QQuickJSContext2DPixelData> r(scope, static_cast<const QQuickJSContext2DPixelData *>(m));
if (index < static_cast<quint32>(r->d()->image->width() * r->d()->image->height() * 4)) {
if (hasProperty)
*hasProperty = true;
const quint32 w = r->d()->image->width();
const quint32 row = (index / 4) / w;
const quint32 col = (index / 4) % w;
const QRgb* pixel = reinterpret_cast<const QRgb*>(r->d()->image->constScanLine(row));
pixel += col;
switch (index % 4) {
case 0:
return QV4::Encode(qRed(*pixel));
case 1:
return QV4::Encode(qGreen(*pixel));
case 2:
return QV4::Encode(qBlue(*pixel));
case 3:
return QV4::Encode(qAlpha(*pixel));
}
}
if (hasProperty)
*hasProperty = false;
return QV4::Encode::undefined();
}
bool QQuickJSContext2DPixelData::virtualPut(QV4::Managed *m, QV4::PropertyKey id, const QV4::Value &value, QV4::Value *receiver)
{
if (!id.isArrayIndex())
return Object::virtualPut(m, id, value, receiver);
Q_ASSERT(m->as<QQuickJSContext2DPixelData>());
QV4::ExecutionEngine *v4 = static_cast<QQuickJSContext2DPixelData *>(m)->engine();
QV4::Scope scope(v4);
if (scope.hasException())
return false;
uint index = id.asArrayIndex();
QV4::Scoped<QQuickJSContext2DPixelData> r(scope, static_cast<QQuickJSContext2DPixelData *>(m));
const int v = value.toInt32();
if (r && index < static_cast<quint32>(r->d()->image->width() * r->d()->image->height() * 4) && v >= 0 && v <= 255) {
const quint32 w = r->d()->image->width();
const quint32 row = (index / 4) / w;
const quint32 col = (index / 4) % w;
QRgb* pixel = reinterpret_cast<QRgb*>(r->d()->image->scanLine(row));
pixel += col;
switch (index % 4) {
case 0:
*pixel = qRgba(v, qGreen(*pixel), qBlue(*pixel), qAlpha(*pixel));
break;
case 1:
*pixel = qRgba(qRed(*pixel), v, qBlue(*pixel), qAlpha(*pixel));
break;
case 2:
*pixel = qRgba(qRed(*pixel), qGreen(*pixel), v, qAlpha(*pixel));
break;
case 3:
*pixel = qRgba(qRed(*pixel), qGreen(*pixel), qBlue(*pixel), v);
break;
}
return true;
}
return false;
}
/*!
\qmlmethod CanvasImageData QtQuick::Context2D::createImageData(real sw, real sh)
Creates a CanvasImageData object with the given dimensions(\a sw, \a sh).
*/
/*!
\qmlmethod CanvasImageData QtQuick::Context2D::createImageData(CanvasImageData imageData)
Creates a CanvasImageData object with the same dimensions as the \a imageData argument.
*/
/*!
\qmlmethod CanvasImageData QtQuick::Context2D::createImageData(Url imageUrl)
Creates a CanvasImageData object with the given image loaded from \a imageUrl.
\note The \a imageUrl must be already loaded before this function call,
otherwise an empty CanvasImageData obect will be returned.
\sa Canvas::loadImage(), QtQuick::Canvas::unloadImage(),
QtQuick::Canvas::isImageLoaded
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_createImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc == 1) {
QV4::ScopedValue arg0(scope, argv[0]);
QV4::Scoped<QQuickJSContext2DImageData> imgData(scope, arg0);
if (!!imgData) {
QV4::Scoped<QQuickJSContext2DPixelData> pa(scope, imgData->d()->pixelData.as<QQuickJSContext2DPixelData>());
if (pa) {
qreal w = pa->d()->image->width();
qreal h = pa->d()->image->height();
RETURN_RESULT(qt_create_image_data(w, h, scope.engine, QImage()));
}
} else if (arg0->isString()) {
QImage image = r->d()->context()->createPixmap(QUrl(arg0->toQStringNoThrow()))->image();
RETURN_RESULT(qt_create_image_data(image.width(), image.height(), scope.engine, std::move(image)));
}
} else if (argc == 2) {
qreal w = argv[0].toNumber();
qreal h = argv[1].toNumber();
if (!qt_is_finite(w) || !qt_is_finite(h))
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createImageData(): invalid arguments");
if (w > 0 && h > 0)
RETURN_RESULT(qt_create_image_data(w, h, scope.engine, QImage()));
else
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "createImageData(): invalid arguments");
}
RETURN_UNDEFINED();
}
/*!
\qmlmethod CanvasImageData QtQuick::Context2D::getImageData(real x, real y, real w, real h)
Returns an CanvasImageData object containing the image data for the canvas
rectangle specified by (\a x, \a y, \a w, \a h).
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_getImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc >= 4) {
qreal x = argv[0].toNumber();
qreal y = argv[1].toNumber();
qreal w = argv[2].toNumber();
qreal h = argv[3].toNumber();
if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "getImageData(): Invalid arguments");
if (w <= 0 || h <= 0)
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "getImageData(): Invalid arguments");
QImage image = r->d()->context()->canvas()->toImage(QRectF(x, y, w, h));
RETURN_RESULT(qt_create_image_data(w, h, scope.engine, std::move(image)));
}
RETURN_RESULT(QV4::Encode::null());
}
/*!
\qmlmethod void QtQuick::Context2D::putImageData(CanvasImageData imageData, real dx, real dy, real dirtyX, real dirtyY, real dirtyWidth, real dirtyHeight)
Paints the data from the given \a imageData object onto the canvas at
(\a dx, \a dy).
If a dirty rectangle (\a dirtyX, \a dirtyY, \a dirtyWidth, \a dirtyHeight)
is provided, only the pixels from that rectangle are painted.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_putImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
CHECK_CONTEXT(r)
if (argc < 7)
RETURN_UNDEFINED();
QV4::ScopedValue arg0(scope, argv[0]);
if (!arg0->isObject())
THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "Context2D::putImageData, the image data type mismatch");
qreal dx = argv[1].toNumber();
qreal dy = argv[2].toNumber();
qreal w, h, dirtyX, dirtyY, dirtyWidth, dirtyHeight;
if (!qt_is_finite(dx) || !qt_is_finite(dy))
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "putImageData() : Invalid arguments");
QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, arg0);
if (!imageData)
RETURN_UNDEFINED();
QV4::Scoped<QQuickJSContext2DPixelData> pixelArray(scope, imageData->d()->pixelData.as<QQuickJSContext2DPixelData>());
if (pixelArray) {
w = pixelArray->d()->image->width();
h = pixelArray->d()->image->height();
if (argc == 7) {
dirtyX = argv[3].toNumber();
dirtyY = argv[4].toNumber();
dirtyWidth = argv[5].toNumber();
dirtyHeight = argv[6].toNumber();
if (!qt_is_finite(dirtyX) || !qt_is_finite(dirtyY) || !qt_is_finite(dirtyWidth) || !qt_is_finite(dirtyHeight))
THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "putImageData() : Invalid arguments");
if (dirtyWidth < 0) {
dirtyX = dirtyX+dirtyWidth;
dirtyWidth = -dirtyWidth;
}
if (dirtyHeight < 0) {
dirtyY = dirtyY+dirtyHeight;
dirtyHeight = -dirtyHeight;
}
if (dirtyX < 0) {
dirtyWidth = dirtyWidth+dirtyX;
dirtyX = 0;
}
if (dirtyY < 0) {
dirtyHeight = dirtyHeight+dirtyY;
dirtyY = 0;
}
if (dirtyX+dirtyWidth > w) {
dirtyWidth = w - dirtyX;
}
if (dirtyY+dirtyHeight > h) {
dirtyHeight = h - dirtyY;
}
if (dirtyWidth <=0 || dirtyHeight <= 0)
RETURN_UNDEFINED();
} else {
dirtyX = 0;
dirtyY = 0;
dirtyWidth = w;
dirtyHeight = h;
}
QImage image = pixelArray->d()->image->copy(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
r->d()->context()->buffer()->drawImage(image, QRectF(dirtyX, dirtyY, dirtyWidth, dirtyHeight), QRectF(dx, dy, dirtyWidth, dirtyHeight));
}
RETURN_RESULT(*thisObject);
}
/*!
\qmltype CanvasGradient
\inqmlmodule QtQuick
\since 5.0
\ingroup qtquick-canvas
\brief Provides an opaque CanvasGradient interface.
*/
/*!
\qmlmethod CanvasGradient QtQuick::CanvasGradient::addColorStop(real offset, string color)
Adds a color stop with the given \a color to the gradient at the given \a offset.
0.0 is the offset at one end of the gradient, 1.0 is the offset at the other end.
For example:
\code
var gradient = ctx.createLinearGradient(0, 0, 100, 100);
gradient.addColorStop(0.3, Qt.rgba(1, 0, 0, 1));
gradient.addColorStop(0.7, 'rgba(0, 255, 255, 1)');
\endcode
*/
QV4::ReturnedValue QQuickContext2DStyle::gradient_proto_addColorStop(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQuickContext2DStyle> style(scope, thisObject->as<QQuickContext2DStyle>());
if (!style)
THROW_GENERIC_ERROR("Not a CanvasGradient object");
if (argc == 2) {
if (!style->d()->brush->gradient())
THROW_GENERIC_ERROR("Not a valid CanvasGradient object, can't get the gradient information");
QGradient gradient = *(style->d()->brush->gradient());
qreal pos = argv[0].toNumber();
QColor color;
if (argv[1].as<Object>()) {
color = QV4::ExecutionEngine::toVariant(
argv[1], QMetaType::fromType<QColor>()).value<QColor>();
} else {
color = qt_color_from_string(argv[1]);
}
if (pos < 0.0 || pos > 1.0 || !qt_is_finite(pos)) {
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "CanvasGradient: parameter offset out of range");
}
if (color.isValid()) {
gradient.setColorAt(pos, color);
} else {
THROW_DOM(DOMEXCEPTION_SYNTAX_ERR, "CanvasGradient: parameter color is not a valid color string");
}
*style->d()->brush = gradient;
}
return thisObject->asReturnedValue();
}
void QQuickContext2D::scale(qreal x, qreal y)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y))
return;
QTransform newTransform = state.matrix;
newTransform.scale(x, y);
if (!newTransform.isInvertible()) {
state.invertibleCTM = false;
return;
}
state.matrix = newTransform;
buffer()->updateMatrix(state.matrix);
m_path = QTransform().scale(1.0 / x, 1.0 / y).map(m_path);
}
void QQuickContext2D::rotate(qreal angle)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(angle))
return;
QTransform newTransform =state.matrix;
newTransform.rotate(qRadiansToDegrees(angle));
if (!newTransform.isInvertible()) {
state.invertibleCTM = false;
return;
}
state.matrix = newTransform;
buffer()->updateMatrix(state.matrix);
m_path = QTransform().rotate(-qRadiansToDegrees(angle)).map(m_path);
}
void QQuickContext2D::shear(qreal h, qreal v)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(h) || !qt_is_finite(v))
return ;
QTransform newTransform = state.matrix;
newTransform.shear(h, v);
if (!newTransform.isInvertible()) {
state.invertibleCTM = false;
return;
}
state.matrix = newTransform;
buffer()->updateMatrix(state.matrix);
m_path = QTransform().shear(-h, -v).map(m_path);
}
void QQuickContext2D::translate(qreal x, qreal y)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y))
return ;
QTransform newTransform = state.matrix;
newTransform.translate(x, y);
if (!newTransform.isInvertible()) {
state.invertibleCTM = false;
return;
}
state.matrix = newTransform;
buffer()->updateMatrix(state.matrix);
m_path = QTransform().translate(-x, -y).map(m_path);
}
void QQuickContext2D::transform(qreal a, qreal b, qreal c, qreal d, qreal e, qreal f)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(a) || !qt_is_finite(b) || !qt_is_finite(c) || !qt_is_finite(d) || !qt_is_finite(e) || !qt_is_finite(f))
return;
QTransform transform(a, b, c, d, e, f);
QTransform newTransform = state.matrix * transform;
if (!newTransform.isInvertible()) {
state.invertibleCTM = false;
return;
}
state.matrix = newTransform;
buffer()->updateMatrix(state.matrix);
m_path = transform.inverted().map(m_path);
}
void QQuickContext2D::setTransform(qreal a, qreal b, qreal c, qreal d, qreal e, qreal f)
{
if (!qt_is_finite(a) || !qt_is_finite(b) || !qt_is_finite(c) || !qt_is_finite(d) || !qt_is_finite(e) || !qt_is_finite(f))
return;
QTransform ctm = state.matrix;
if (!ctm.isInvertible())
return;
state.matrix = ctm.inverted() * state.matrix;
m_path = ctm.map(m_path);
state.invertibleCTM = true;
transform(a, b, c, d, e, f);
}
void QQuickContext2D::fill()
{
if (!state.invertibleCTM)
return;
if (!m_path.elementCount())
return;
m_path.setFillRule(state.fillRule);
buffer()->fill(m_path);
}
void QQuickContext2D::clip()
{
if (!state.invertibleCTM)
return;
QPainterPath clipPath = m_path;
clipPath.closeSubpath();
if (state.clip) {
state.clipPath = clipPath.intersected(state.clipPath);
} else {
state.clip = true;
state.clipPath = clipPath;
}
buffer()->clip(state.clip, state.clipPath);
}
void QQuickContext2D::stroke()
{
if (!state.invertibleCTM)
return;
if (!m_path.elementCount())
return;
buffer()->stroke(m_path);
}
void QQuickContext2D::fillRect(qreal x, qreal y, qreal w, qreal h)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
return;
buffer()->fillRect(QRectF(x, y, w, h));
}
void QQuickContext2D::strokeRect(qreal x, qreal y, qreal w, qreal h)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
return;
buffer()->strokeRect(QRectF(x, y, w, h));
}
void QQuickContext2D::clearRect(qreal x, qreal y, qreal w, qreal h)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
return;
buffer()->clearRect(QRectF(x, y, w, h));
}
void QQuickContext2D::drawText(const QString& text, qreal x, qreal y, bool fill)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y))
return;
QPainterPath textPath = createTextGlyphs(x, y, text);
if (fill)
buffer()->fill(textPath);
else
buffer()->stroke(textPath);
}
void QQuickContext2D::beginPath()
{
if (!m_path.elementCount())
return;
m_path = QPainterPath();
}
void QQuickContext2D::closePath()
{
if (!m_path.elementCount())
return;
QRectF boundRect = m_path.boundingRect();
if (boundRect.width() || boundRect.height())
m_path.closeSubpath();
//FIXME:QPainterPath set the current point to (0,0) after close subpath
//should be the first point of the previous subpath
}
void QQuickContext2D::moveTo( qreal x, qreal y)
{
if (!state.invertibleCTM)
return;
//FIXME: moveTo should not close the previous subpath
m_path.moveTo(QPointF(x, y));
}
void QQuickContext2D::lineTo( qreal x, qreal y)
{
if (!state.invertibleCTM)
return;
QPointF pt(x, y);
if (!m_path.elementCount())
m_path.moveTo(pt);
else if (m_path.currentPosition() != pt)
m_path.lineTo(pt);
}
void QQuickContext2D::quadraticCurveTo(qreal cpx, qreal cpy,
qreal x, qreal y)
{
if (!state.invertibleCTM)
return;
if (!m_path.elementCount())
m_path.moveTo(QPointF(cpx, cpy));
QPointF pt(x, y);
if (m_path.currentPosition() != pt)
m_path.quadTo(QPointF(cpx, cpy), pt);
}
void QQuickContext2D::bezierCurveTo(qreal cp1x, qreal cp1y,
qreal cp2x, qreal cp2y,
qreal x, qreal y)
{
if (!state.invertibleCTM)
return;
if (!m_path.elementCount())
m_path.moveTo(QPointF(cp1x, cp1y));
QPointF pt(x, y);
if (m_path.currentPosition() != pt)
m_path.cubicTo(QPointF(cp1x, cp1y), QPointF(cp2x, cp2y), pt);
}
void QQuickContext2D::addArcTo(const QPointF& p1, const QPointF& p2, qreal radius)
{
QPointF p0(m_path.currentPosition());
QPointF p1p0((p0.x() - p1.x()), (p0.y() - p1.y()));
QPointF p1p2((p2.x() - p1.x()), (p2.y() - p1.y()));
qreal p1p0_length = std::hypot(p1p0.x(), p1p0.y());
qreal p1p2_length = std::hypot(p1p2.x(), p1p2.y());
qreal cos_phi = QPointF::dotProduct(p1p0, p1p2) / (p1p0_length * p1p2_length);
// The points p0, p1, and p2 are on the same straight line (HTML5, 4.8.11.1.8)
// We could have used areCollinear() here, but since we're reusing
// the variables computed above later on we keep this logic.
if (qFuzzyCompare(std::abs(cos_phi), qreal(1.0))) {
m_path.lineTo(p1);
return;
}
qreal tangent = radius / std::tan(std::acos(cos_phi) / 2);
qreal factor_p1p0 = tangent / p1p0_length;
QPointF t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y()));
QPointF orth_p1p0(p1p0.y(), -p1p0.x());
qreal orth_p1p0_length = std::hypot(orth_p1p0.x(), orth_p1p0.y());
qreal factor_ra = radius / orth_p1p0_length;
// angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0
qreal cos_alpha = QPointF::dotProduct(orth_p1p0, p1p2) / (orth_p1p0_length * p1p2_length);
if (cos_alpha < 0.f)
orth_p1p0 = QPointF(-orth_p1p0.x(), -orth_p1p0.y());
QPointF p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y()));
// calculate angles for addArc
orth_p1p0 = QPointF(-orth_p1p0.x(), -orth_p1p0.y());
qreal sa = std::atan2(orth_p1p0.y(), orth_p1p0.x());
// anticlockwise logic
bool anticlockwise = false;
qreal factor_p1p2 = tangent / p1p2_length;
QPointF t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y()));
QPointF orth_p1p2((t_p1p2.x() - p.x()), (t_p1p2.y() - p.y()));
qreal ea = std::atan2(orth_p1p2.y(), orth_p1p2.x());
if ((sa > ea) && ((sa - ea) < M_PI))
anticlockwise = true;
if ((sa < ea) && ((ea - sa) > M_PI))
anticlockwise = true;
arc(p.x(), p.y(), radius, sa, ea, anticlockwise);
}
void QQuickContext2D::arcTo(qreal x1, qreal y1,
qreal x2, qreal y2,
qreal radius)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x1) || !qt_is_finite(y1) || !qt_is_finite(x2) || !qt_is_finite(y2) || !qt_is_finite(radius))
return;
QPointF st(x1, y1);
QPointF end(x2, y2);
if (!m_path.elementCount())
m_path.moveTo(st);
else if (st == m_path.currentPosition() || st == end || !radius)
lineTo(x1, y1);
else
addArcTo(st, end, radius);
}
void QQuickContext2D::rect(qreal x, qreal y, qreal w, qreal h)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
return;
if (!w && !h) {
m_path.moveTo(x, y);
return;
}
m_path.addRect(x, y, w, h);
}
void QQuickContext2D::roundedRect(qreal x, qreal y,
qreal w, qreal h,
qreal xr, qreal yr)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h) || !qt_is_finite(xr) || !qt_is_finite(yr))
return;
if (!w && !h) {
m_path.moveTo(x, y);
return;
}
m_path.addRoundedRect(QRectF(x, y, w, h), xr, yr, Qt::AbsoluteSize);
}
void QQuickContext2D::ellipse(qreal x, qreal y,
qreal w, qreal h)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
return;
if (!w && !h) {
m_path.moveTo(x, y);
return;
}
m_path.addEllipse(x, y, w, h);
}
void QQuickContext2D::text(const QString& str, qreal x, qreal y)
{
if (!state.invertibleCTM)
return;
QPainterPath path;
path.addText(x, y, state.font, str);
m_path.addPath(path);
}
void QQuickContext2D::arc(qreal xc, qreal yc, qreal radius, qreal sar, qreal ear, bool antiClockWise)
{
if (!state.invertibleCTM)
return;
if (!qt_is_finite(xc) || !qt_is_finite(yc) || !qt_is_finite(sar) || !qt_is_finite(ear) || !qt_is_finite(radius))
return;
if (sar == ear)
return;
//### HACK
// In Qt we don't switch the coordinate system for degrees
// and still use the 0,0 as bottom left for degrees so we need
// to switch
sar = -sar;
ear = -ear;
antiClockWise = !antiClockWise;
//end hack
float sa = qRadiansToDegrees(sar);
float ea = qRadiansToDegrees(ear);
double span = 0;
double xs = xc - radius;
double ys = yc - radius;
double width = radius*2;
double height = radius*2;
if ((!antiClockWise && (ea - sa >= 360)) || (antiClockWise && (sa - ea >= 360)))
// If the anticlockwise argument is false and endAngle-startAngle is equal to or greater than 2*PI, or, if the
// anticlockwise argument is true and startAngle-endAngle is equal to or greater than 2*PI, then the arc is the whole
// circumference of this circle.
span = 360;
else {
if (!antiClockWise && (ea < sa)) {
span += 360;
} else if (antiClockWise && (sa < ea)) {
span -= 360;
}
//### this is also due to switched coordinate system
// we would end up with a 0 span instead of 360
if (!(qFuzzyCompare(span + (ea - sa) + 1, 1) &&
qFuzzyCompare(qAbs(span), 360))) {
span += ea - sa;
}
}
// If the path is empty, move to where the arc will start to avoid painting a line from (0,0)
if (!m_path.elementCount())
m_path.arcMoveTo(xs, ys, width, height, sa);
else if (!radius) {
m_path.lineTo(xc, yc);
return;
}
m_path.arcTo(xs, ys, width, height, sa, span);
}
int baseLineOffset(QQuickContext2D::TextBaseLineType value, const QFontMetrics &metrics)
{
int offset = 0;
switch (value) {
case QQuickContext2D::Top:
case QQuickContext2D::Hanging:
break;
case QQuickContext2D::Middle:
offset = (metrics.ascent() >> 1) + metrics.height() - metrics.ascent();
break;
case QQuickContext2D::Alphabetic:
offset = metrics.ascent();
break;
case QQuickContext2D::Bottom:
offset = metrics.height();
break;
}
return offset;
}
static int textAlignOffset(QQuickContext2D::TextAlignType value, const QFontMetrics &metrics, const QString &text)
{
int offset = 0;
if (value == QQuickContext2D::Start)
value = QGuiApplication::layoutDirection() == Qt::LeftToRight ? QQuickContext2D::Left : QQuickContext2D::Right;
else if (value == QQuickContext2D::End)
value = QGuiApplication::layoutDirection() == Qt::LeftToRight ? QQuickContext2D::Right: QQuickContext2D::Left;
switch (value) {
case QQuickContext2D::Center:
offset = metrics.horizontalAdvance(text) / 2;
break;
case QQuickContext2D::Right:
offset = metrics.horizontalAdvance(text);
break;
case QQuickContext2D::Left:
default:
break;
}
return offset;
}
void QQuickContext2D::setGrabbedImage(const QImage& grab)
{
m_grabbedImage = grab;
m_grabbed = true;
}
QQmlRefPointer<QQuickCanvasPixmap> QQuickContext2D::createPixmap(const QUrl& url, QSizeF sourceSize)
{
return m_canvas->loadedPixmap(url, sourceSize);
}
QPainterPath QQuickContext2D::createTextGlyphs(qreal x, qreal y, const QString& text)
{
const QFontMetrics metrics(state.font);
int yoffset = baseLineOffset(static_cast<QQuickContext2D::TextBaseLineType>(state.textBaseline), metrics);
int xoffset = textAlignOffset(static_cast<QQuickContext2D::TextAlignType>(state.textAlign), metrics, text);
QPainterPath textPath;
textPath.addText(x - xoffset, y - yoffset+metrics.ascent(), state.font, text);
return textPath;
}
static inline bool areCollinear(const QPointF& a, const QPointF& b, const QPointF& c)
{
// Solved from comparing the slopes of a to b and b to c: (ay-by)/(ax-bx) == (cy-by)/(cx-bx)
return qFuzzyCompare((c.y() - b.y()) * (a.x() - b.x()), (a.y() - b.y()) * (c.x() - b.x()));
}
static inline bool withinRange(qreal p, qreal a, qreal b)
{
return (p >= a && p <= b) || (p >= b && p <= a);
}
bool QQuickContext2D::isPointInPath(qreal x, qreal y) const
{
if (!state.invertibleCTM)
return false;
if (!m_path.elementCount())
return false;
if (!qt_is_finite(x) || !qt_is_finite(y))
return false;
QPointF point(x, y);
QTransform ctm = state.matrix;
QPointF p = ctm.inverted().map(point);
if (!qt_is_finite(p.x()) || !qt_is_finite(p.y()))
return false;
const_cast<QQuickContext2D *>(this)->m_path.setFillRule(state.fillRule);
bool contains = m_path.contains(p);
if (!contains) {
// check whether the point is on the border
QPolygonF border = m_path.toFillPolygon();
QPointF p1 = border.at(0);
QPointF p2;
for (int i = 1; i < border.size(); ++i) {
p2 = border.at(i);
if (areCollinear(p, p1, p2)
// Once we know that the points are collinear we
// only need to check one of the coordinates
&& (qAbs(p2.x() - p1.x()) > qAbs(p2.y() - p1.y()) ?
withinRange(p.x(), p1.x(), p2.x()) :
withinRange(p.y(), p1.y(), p2.y()))) {
return true;
}
p1 = p2;
}
}
return contains;
}
QMutex QQuickContext2D::mutex;
QQuickContext2D::QQuickContext2D(QObject *parent)
: QQuickCanvasContext(parent)
, m_buffer(new QQuickContext2DCommandBuffer)
, m_v4engine(nullptr)
, m_surface(nullptr)
, m_thread(nullptr)
, m_grabbed(false)
{
}
QQuickContext2D::~QQuickContext2D()
{
mutex.lock();
m_texture->setItem(nullptr);
delete m_buffer;
m_texture->deleteLater();
mutex.unlock();
}
QV4::ReturnedValue QQuickContext2D::v4value() const
{
return m_v4value.value();
}
QStringList QQuickContext2D::contextNames() const
{
return QStringList() << QStringLiteral("2d");
}
void QQuickContext2D::init(QQuickCanvasItem *canvasItem, const QVariantMap &args)
{
Q_UNUSED(args);
m_canvas = canvasItem;
m_renderTarget = canvasItem->renderTarget();
m_renderStrategy = canvasItem->renderStrategy();
// Disable threaded background rendering if the platform has issues with it
if (m_renderTarget == QQuickCanvasItem::FramebufferObject
&& m_renderStrategy == QQuickCanvasItem::Threaded
&& !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) {
m_renderTarget = QQuickCanvasItem::Image;
}
// Disable framebuffer object based rendering always in Qt 6. It
// is not implemented in the new RHI-based graphics stack, but the
// enum value is still present. Switch to Image instead.
if (m_renderTarget == QQuickCanvasItem::FramebufferObject)
m_renderTarget = QQuickCanvasItem::Image;
m_texture = new QQuickContext2DImageTexture;
m_texture->setItem(canvasItem);
m_texture->setCanvasWindow(canvasItem->canvasWindow().toRect());
m_texture->setTileSize(canvasItem->tileSize());
m_texture->setCanvasSize(canvasItem->canvasSize().toSize());
m_texture->setSmooth(canvasItem->smooth());
m_texture->setAntialiasing(canvasItem->antialiasing());
m_texture->setOnCustomThread(m_renderStrategy == QQuickCanvasItem::Threaded);
m_thread = QThread::currentThread();
QThread *renderThread = m_thread;
if (m_renderStrategy == QQuickCanvasItem::Threaded)
renderThread = QQuickContext2DRenderThread::instance(qmlEngine(canvasItem));
if (renderThread && renderThread != QThread::currentThread())
m_texture->moveToThread(renderThread);
connect(m_texture, SIGNAL(textureChanged()), SIGNAL(textureChanged()));
reset();
}
void QQuickContext2D::prepare(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing)
{
if (m_texture->thread() == QThread::currentThread()) {
m_texture->canvasChanged(canvasSize, tileSize, canvasWindow, dirtyRect, smooth, antialiasing);
} else {
QEvent *e = new QQuickContext2DTexture::CanvasChangeEvent(canvasSize,
tileSize,
canvasWindow,
dirtyRect,
smooth,
antialiasing);
QCoreApplication::postEvent(m_texture, e);
}
}
void QQuickContext2D::flush()
{
if (m_buffer) {
if (m_texture->thread() == QThread::currentThread())
m_texture->paint(m_buffer);
else
QCoreApplication::postEvent(m_texture, new QQuickContext2DTexture::PaintEvent(m_buffer));
}
m_buffer = new QQuickContext2DCommandBuffer();
}
QQuickContext2DTexture *QQuickContext2D::texture() const
{
return m_texture;
}
QImage QQuickContext2D::toImage(const QRectF& bounds)
{
if (m_texture->thread() == QThread::currentThread()) {
flush();
m_texture->grabImage(bounds);
} else if (m_renderStrategy == QQuickCanvasItem::Cooperative) {
qWarning() << "Pixel readback is not supported in Cooperative mode, please try Threaded or Immediate mode";
return QImage();
} else {
flush();
QCoreApplication::postEvent(m_texture, new QEvent(QEvent::Type(QEvent::User + 10)));
QMetaObject::invokeMethod(m_texture,
"grabImage",
Qt::BlockingQueuedConnection,
Q_ARG(QRectF, bounds));
}
QImage img = m_grabbedImage;
m_grabbedImage = QImage();
m_grabbed = false;
return img;
}
QQuickContext2DEngineData::QQuickContext2DEngineData(QV4::ExecutionEngine *v4)
{
QV4::Scope scope(v4);
QV4::ScopedObject proto(scope, QQuickJSContext2DPrototype::create(v4));
proto->defineAccessorProperty(QStringLiteral("strokeStyle"), QQuickJSContext2D::method_get_strokeStyle, QQuickJSContext2D::method_set_strokeStyle);
proto->defineAccessorProperty(QStringLiteral("font"), QQuickJSContext2D::method_get_font, QQuickJSContext2D::method_set_font);
proto->defineAccessorProperty(QStringLiteral("fillRule"), QQuickJSContext2D::method_get_fillRule, QQuickJSContext2D::method_set_fillRule);
proto->defineAccessorProperty(QStringLiteral("globalAlpha"), QQuickJSContext2D::method_get_globalAlpha, QQuickJSContext2D::method_set_globalAlpha);
proto->defineAccessorProperty(QStringLiteral("lineCap"), QQuickJSContext2D::method_get_lineCap, QQuickJSContext2D::method_set_lineCap);
proto->defineAccessorProperty(QStringLiteral("shadowOffsetX"), QQuickJSContext2D::method_get_shadowOffsetX, QQuickJSContext2D::method_set_shadowOffsetX);
proto->defineAccessorProperty(QStringLiteral("shadowOffsetY"), QQuickJSContext2D::method_get_shadowOffsetY, QQuickJSContext2D::method_set_shadowOffsetY);
proto->defineAccessorProperty(QStringLiteral("globalCompositeOperation"), QQuickJSContext2D::method_get_globalCompositeOperation, QQuickJSContext2D::method_set_globalCompositeOperation);
proto->defineAccessorProperty(QStringLiteral("miterLimit"), QQuickJSContext2D::method_get_miterLimit, QQuickJSContext2D::method_set_miterLimit);
proto->defineAccessorProperty(QStringLiteral("fillStyle"), QQuickJSContext2D::method_get_fillStyle, QQuickJSContext2D::method_set_fillStyle);
proto->defineAccessorProperty(QStringLiteral("shadowColor"), QQuickJSContext2D::method_get_shadowColor, QQuickJSContext2D::method_set_shadowColor);
proto->defineAccessorProperty(QStringLiteral("textBaseline"), QQuickJSContext2D::method_get_textBaseline, QQuickJSContext2D::method_set_textBaseline);
#if QT_CONFIG(quick_path)
proto->defineAccessorProperty(QStringLiteral("path"), QQuickJSContext2D::method_get_path, QQuickJSContext2D::method_set_path);
#endif
proto->defineAccessorProperty(QStringLiteral("lineJoin"), QQuickJSContext2D::method_get_lineJoin, QQuickJSContext2D::method_set_lineJoin);
proto->defineAccessorProperty(QStringLiteral("lineWidth"), QQuickJSContext2D::method_get_lineWidth, QQuickJSContext2D::method_set_lineWidth);
proto->defineAccessorProperty(QStringLiteral("textAlign"), QQuickJSContext2D::method_get_textAlign, QQuickJSContext2D::method_set_textAlign);
proto->defineAccessorProperty(QStringLiteral("shadowBlur"), QQuickJSContext2D::method_get_shadowBlur, QQuickJSContext2D::method_set_shadowBlur);
proto->defineAccessorProperty(QStringLiteral("lineDashOffset"), QQuickJSContext2D::method_get_lineDashOffset, QQuickJSContext2D::method_set_lineDashOffset);
contextPrototype = proto;
proto = scope.engine->newObject();
proto->defineDefaultProperty(QStringLiteral("addColorStop"), QQuickContext2DStyle::gradient_proto_addColorStop, 0);
gradientProto = proto;
proto = scope.engine->newObject();
proto->defineAccessorProperty(scope.engine->id_length(), QQuickJSContext2DPixelData::proto_get_length, nullptr);
pixelArrayProto = proto;
}
QQuickContext2DEngineData::~QQuickContext2DEngineData()
{
}
void QQuickContext2D::popState()
{
if (m_stateStack.isEmpty())
return;
QQuickContext2D::State newState = m_stateStack.pop();
if (state.matrix != newState.matrix)
buffer()->updateMatrix(newState.matrix);
if (newState.globalAlpha != state.globalAlpha)
buffer()->setGlobalAlpha(newState.globalAlpha);
if (newState.globalCompositeOperation != state.globalCompositeOperation)
buffer()->setGlobalCompositeOperation(newState.globalCompositeOperation);
if (newState.fillStyle != state.fillStyle)
buffer()->setFillStyle(newState.fillStyle);
if (newState.strokeStyle != state.strokeStyle)
buffer()->setStrokeStyle(newState.strokeStyle);
if (newState.lineWidth != state.lineWidth)
buffer()->setLineWidth(newState.lineWidth);
if (newState.lineCap != state.lineCap)
buffer()->setLineCap(newState.lineCap);
if (newState.lineJoin != state.lineJoin)
buffer()->setLineJoin(newState.lineJoin);
if (newState.miterLimit != state.miterLimit)
buffer()->setMiterLimit(newState.miterLimit);
if (newState.clip != state.clip || newState.clipPath != state.clipPath)
buffer()->clip(newState.clip, newState.clipPath);
if (newState.shadowBlur != state.shadowBlur)
buffer()->setShadowBlur(newState.shadowBlur);
if (newState.shadowColor != state.shadowColor)
buffer()->setShadowColor(newState.shadowColor);
if (newState.shadowOffsetX != state.shadowOffsetX)
buffer()->setShadowOffsetX(newState.shadowOffsetX);
if (newState.shadowOffsetY != state.shadowOffsetY)
buffer()->setShadowOffsetY(newState.shadowOffsetY);
if (newState.lineDash != state.lineDash)
buffer()->setLineDash(newState.lineDash);
m_path = state.matrix.map(m_path);
state = newState;
m_path = state.matrix.inverted().map(m_path);
}
void QQuickContext2D::pushState()
{
m_stateStack.push(state);
}
void QQuickContext2D::reset()
{
QQuickContext2D::State newState;
m_path = QPainterPath();
newState.clipPath.setFillRule(Qt::WindingFill);
m_stateStack.clear();
m_stateStack.push(newState);
popState();
m_buffer->clearRect(QRectF(0, 0, m_canvas->width(), m_canvas->height()));
}
QV4::ExecutionEngine *QQuickContext2D::v4Engine() const
{
return m_v4engine;
}
void QQuickContext2D::setV4Engine(QV4::ExecutionEngine *engine)
{
if (m_v4engine != engine) {
m_v4engine = engine;
if (m_v4engine == nullptr)
return;
QQuickContext2DEngineData *ed = engineData(engine);
QV4::Scope scope(engine);
QV4::Scoped<QQuickJSContext2D> wrapper(scope, engine->memoryManager->allocate<QQuickJSContext2D>());
QV4::ScopedObject p(scope, ed->contextPrototype.value());
wrapper->setPrototypeOf(p);
wrapper->d()->setContext(this);
m_v4value = wrapper;
}
}
QT_END_NAMESPACE
#include "moc_qquickcontext2d_p.cpp"