[education/rkward] /: Implement tiling patterns
Thomas Friedrichsmeier
null at kde.org
Sat Mar 19 21:57:45 GMT 2022
Git commit ace9302b569f90d8c08dfb2473f30919e2e39337 by Thomas Friedrichsmeier.
Committed on 16/03/2022 at 16:00.
Pushed by tfry into branch 'master'.
Implement tiling patterns
M +4 -0 ChangeLog
M +103 -18 rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
M +14 -0 rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
M +10 -0 rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
M +2 -0 rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
M +33 -12 rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
https://invent.kde.org/education/rkward/commit/ace9302b569f90d8c08dfb2473f30919e2e39337
diff --git a/ChangeLog b/ChangeLog
index 361fa422..d7a93261 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+TODO:
+- RKDClose needs to be synchronous to avoid race-condition while closing device
+- Sometimes pattern takes bits from main paint area? Some syncing needed?
+
TODOS for autotests:
- Check and update the standards files
- Use options(warn=1), in order to get warnings into the test?
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
index 78c23ba2..49340b26 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
@@ -69,6 +69,91 @@ RKGraphicsDevice::~RKGraphicsDevice () {
delete view;
}
+void RKGraphicsDevice::beginPainter() {
+ if(!painter.isActive()) {
+ if (contexts.isEmpty()) {
+ painter.begin(&area); // plain old painting on the canvas itself
+ } else {
+ auto &c = contexts.last();
+ painter.begin(&(c.surface));
+ painter.setTransform(c.transform);
+ }
+ }
+}
+
+void RKGraphicsDevice::pushContext(double width, double height, double x, double y) {
+ RK_TRACE (GRAPHICS_DEVICE);
+ painter.end();
+ PaintContext c;
+// NOTE: R cairo device uses an all different method for pattern capture:
+// drawing is scaled up to full device coordinates, the shrunk and offset back to pattern size.
+// probably due to cairo internals, somehow. Here, instead we paint on a separate surface with the same coords,
+// the extract the rectangle of interest.
+ c.surface = QImage(area.width(), area.height(), QImage::Format_ARGB32);
+ if (width < 0) { // may happen, at least in R 4.1.2
+ width = -width;
+ x -= width;
+ }
+ if (height < 0) {
+ height = -height;
+ y -= height;
+ }
+ c.capture_coords = QRect(x, y, width, height);
+ contexts.push_back(c);
+ beginPainter();
+}
+
+RKGraphicsDevice::PaintContext RKGraphicsDevice::popContext() {
+ RK_TRACE (GRAPHICS_DEVICE);
+
+ if (contexts.isEmpty()) {
+ RK_ASSERT(!contexts.isEmpty());
+ return PaintContext();
+ }
+
+ painter.end();
+ auto ret = contexts.takeLast();
+ beginPainter();
+ return ret;
+}
+
+void RKGraphicsDevice::startRecordTilingPattern(double width, double height, double x, double y) {
+ RK_TRACE (GRAPHICS_DEVICE);
+ pushContext(width, height, x, y);
+}
+
+int RKGraphicsDevice::finalizeTilingPattern(int extend) {
+ RK_TRACE (GRAPHICS_DEVICE);
+
+ auto c = popContext();
+ if (extend == GradientExtendNone || extend == GradientExtendPad) {
+ // For extend type pad it is unclear, what that should even mean, we simply treat it the same as none.
+ // For none, obviously, we want to not repeat the pattern. This is not so easy to achieve in QBrush, but also, it does
+ // look like a very like use of a tiling pattern. What we do, therefore, is to simply copy the _full_ surface, rather than
+ // just the reqion of interest. This way, we will - usually - not see any repeats.
+ return (registerPattern(QBrush(c.surface)));
+ }
+ if (extend == GradientExtendReflect) {
+ QImage single = c.surface.copy(c.capture_coords);
+ QImage reflected(single.width()*2, single.height()*2, single.format());
+ QPainter p(&reflected);
+ p.drawImage(0, 0, single);
+ p.drawImage(single.width(), 0, single.mirrored(true, false));
+ p.drawImage(0, single.height(), single.mirrored(false, true));
+ p.drawImage(single.width(), single.height(), single.mirrored(true, true));
+ p.end();
+ QBrush brush(reflected);
+ brush.setTransform(QTransform().translate(c.capture_coords.left(), c.capture_coords.top()));
+ return registerPattern(brush);
+ }
+
+ // else: GradientExtendRepeat
+ QImage img = c.surface.copy(c.capture_coords);
+ QBrush brush(img);
+ brush.setTransform(QTransform().translate(c.capture_coords.left(), c.capture_coords.top()));
+ return registerPattern(brush);
+}
+
void RKGraphicsDevice::viewKilled () {
RK_TRACE (GRAPHICS_DEVICE);
view = 0;
@@ -92,7 +177,7 @@ void RKGraphicsDevice::updateNow () {
view->show ();
}
checkSize ();
- painter.begin (&area);
+ beginPainter();
}
void RKGraphicsDevice::checkSize() {
@@ -163,7 +248,7 @@ void RKGraphicsDevice::destroyPattern(int id) {
void RKGraphicsDevice::setClip (const QRectF& new_clip) {
RK_TRACE (GRAPHICS_DEVICE);
- if (!painter.isActive ()) painter.begin (&area);
+ beginPainter();
painter.setClipRect (new_clip);
}
@@ -201,26 +286,27 @@ void RKGraphicsDevice::rect (const QRectF& rec, const QPen& pen, const QBrush& b
triggerUpdate ();
}
-QSizeF RKGraphicsDevice::strSize (const QString& text, const QFont& font) {
- RK_TRACE (GRAPHICS_DEVICE);
+QSizeF RKGraphicsDevice::strSize(const QString& text, const QFont& font) {
+ RK_TRACE(GRAPHICS_DEVICE);
- painter.setFont (font);
- QSizeF size = painter.boundingRect (QRectF (area.rect ()), text).size ();
+ painter.setFont(font);
+ QSizeF size = painter.fontMetrics().boundingRect(text).size();
return size;
}
-void RKGraphicsDevice::text (double x, double y, const QString& text, double rot, double hadj, const QColor& col, const QFont& font) {
- RK_TRACE (GRAPHICS_DEVICE);
+void RKGraphicsDevice::text(double x, double y, const QString& text, double rot, double hadj, const QColor& col, const QFont& font) {
+ RK_TRACE(GRAPHICS_DEVICE);
- painter.save ();
- QSizeF size = strSize (text, font); // NOTE: side-effect of setting font!
-// painter.setFont (font);
- painter.setPen (QPen (col));
- painter.translate (x, y);
- painter.rotate (-rot);
- painter.drawText (-(hadj * size.width ()), 0, text);
- painter.restore (); // undo rotation / translation
- triggerUpdate ();
+ painter.save();
+ QSizeF size = strSize(text, font); // NOTE: side-effect of setting font!
+// painter.setFont(font);
+ painter.setPen(QPen(col));
+ painter.translate(x-(hadj * size.width()), y);
+ painter.rotate(-rot);
+ painter.drawText(0, 0, text);
+// painter.drawRect(painter.fontMetrics().boundingRect(text)); // for debugging
+ painter.restore(); // undo rotation / translation
+ triggerUpdate();
}
void RKGraphicsDevice::metricInfo (const QChar& c, const QFont& font, double* ascent, double* descent, double* width) {
@@ -236,7 +322,6 @@ void RKGraphicsDevice::metricInfo (const QChar& c, const QFont& font, double* as
void RKGraphicsDevice::polygon (const QPolygonF& pol, const QPen& pen, const QBrush& brush) {
RK_TRACE (GRAPHICS_DEVICE);
-
painter.setPen (pen);
painter.setBrush (brush);
painter.drawPolygon (pol);
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
index 9598a4e9..d3a91627 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
@@ -96,6 +96,8 @@ public:
int registerPattern(const QBrush &brush);
void destroyPattern(int id);
QBrush getPattern(int id) const { return patterns.value(id); };
+ void startRecordTilingPattern(double width, double height, double x, double y);
+ int finalizeTilingPattern(int extend);
public slots:
void stopInteraction ();
signals:
@@ -128,6 +130,18 @@ private:
int interaction_opcode; /**< Current interactive operation (from RKDOpcodes enum), or -1 is there is no current interactive operation */
QList<StoredEvent> stored_events;
+
+ struct PaintContext {
+ // TODO: this probably also needs info like clipping paths, transforms, add mode, etc. Just an initial attempt.
+ QImage surface;
+ QTransform transform;
+ QRect capture_coords;
+ };
+ QList<PaintContext> contexts;
+ // make sure the painter is active on the current context
+ void beginPainter();
+ void pushContext(double width, double height, double x, double y);
+ PaintContext popContext();
};
#endif
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
index 59ff542a..679312a6 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
@@ -365,6 +365,16 @@ void RKGraphicsDeviceFrontendTransmitter::newData () {
qint32 index;
streamer.instream >> index;
device->destroyPattern(index);
+ } else if (opcode == RKDStartRecordTilingPattern) {
+ double width, height, x, y;
+ streamer.instream >> width >> height;
+ streamer.instream >> x >> y;
+ device->startRecordTilingPattern(width, height, x, y);
+ } else if (opcode == RKDEndRecordTilingPattern) {
+ qint8 extend;
+ streamer.instream >> extend;
+ streamer.outstream << (qint32) device->finalizeTilingPattern((RKDGradientExtend) extend);
+ streamer.writeOutBuffer();
} else if (opcode == RKDCapture) {
QImage image = device->capture ();
quint32 w = image.width ();
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
index 2f79ed70..1c91a004 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
@@ -101,6 +101,7 @@ enum RKDOpcodes {
RKDStartGettingEvents,
RKDStopGettingEvents,
RKDReleasePattern,
+ RKDStartRecordTilingPattern, // part of setPattern in R
// Synchronous operations
RKDFetchNextEvent = 100,
@@ -112,6 +113,7 @@ enum RKDOpcodes {
RKDQueryResolution,
RKDGetSize,
RKDSetPattern,
+ RKDEndRecordTilingPattern, // part of setPattern in R
// Protocol operations
RKDCancel = 200
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
index 20bf18dd..8b511af0 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
@@ -605,11 +605,19 @@ qint8 getGradientExtend(int Rextent) {
/* if (Rextent == R_GE_patternExtendNone) */ return GradientExtendNone;
}
+SEXP makeInt(int val) {
+ SEXP ret;
+ PROTECT(ret = Rf_allocVector(INTSXP, 1));
+ INTEGER(ret)[0] = val;
+ UNPROTECT(1);
+ return ret;
+}
+
SEXP RKD_SetPattern (SEXP pattern, pDevDesc dev) {
- {
+ auto ptype = R_GE_patternType(pattern);
+ if ((ptype == R_GE_linearGradientPattern) || (ptype == R_GE_radialGradientPattern)) {
RKGraphicsDataStreamWriteGuard wguard;
- WRITE_HEADER (RKDSetPattern, dev);
- auto ptype = R_GE_patternType(pattern);
+ WRITE_HEADER(RKDSetPattern, dev);
if (ptype == R_GE_linearGradientPattern) {
RKD_OUT_STREAM << (qint8) RKDPatternType::LinearPattern;
RKD_OUT_STREAM << (double) R_GE_linearGradientX1(pattern) << (double) R_GE_linearGradientX2(pattern) << (double) R_GE_linearGradientY1(pattern) << (double) R_GE_linearGradientY2(pattern);
@@ -631,11 +639,28 @@ SEXP RKD_SetPattern (SEXP pattern, pDevDesc dev) {
RKD_OUT_STREAM << (double) R_GE_radialGradientStop(pattern, i);
}
RKD_OUT_STREAM << getGradientExtend(R_GE_radialGradientExtend(pattern));
- } else if (ptype == R_GE_tilingPattern) {
- RKD_OUT_STREAM << (qint8) RKDPatternType::TilingPattern;
- } else {
- RKD_OUT_STREAM << (qint8) RKDPatternType::UnknonwnPattern;
}
+ } else if (ptype == R_GE_tilingPattern) {
+ {
+ RKGraphicsDataStreamWriteGuard wguard;
+ WRITE_HEADER(RKDStartRecordTilingPattern, dev);
+ RKD_OUT_STREAM << (double) R_GE_tilingPatternWidth(pattern) << (double) R_GE_tilingPatternHeight(pattern);
+ RKD_OUT_STREAM << (double) R_GE_tilingPatternX(pattern) << (double) R_GE_tilingPatternY(pattern);
+ }
+ // Play the pattern generator function. Contrary to cairo device, we use tryEval, here, to avoid getting into a
+ // bad device state in case of errors
+ int error;
+ SEXP pattern_func = PROTECT(Rf_lang1(R_GE_tilingPatternFunction(pattern)));
+ R_tryEval(pattern_func, R_GlobalEnv, &error);
+ UNPROTECT(1);
+ {
+ RKGraphicsDataStreamWriteGuard wguard;
+ WRITE_HEADER(RKDEndRecordTilingPattern, dev);
+ RKD_OUT_STREAM << getGradientExtend(R_GE_tilingPatternExtend(pattern));
+ }
+ } else {
+ Rf_warning("Pattern type not (yet) supported");
+ return makeInt(-1);
}
qint32 index;
@@ -646,11 +671,7 @@ SEXP RKD_SetPattern (SEXP pattern, pDevDesc dev) {
// NOTE: we are free to chose a return value of our liking. It is used as an identifier for this pattern.
if (index < 0) Rf_warning("Pattern type not (yet) supported");
- SEXP ret;
- PROTECT(ret = Rf_allocVector(INTSXP, 1));
- INTEGER(ret)[0] = index;
- UNPROTECT(1);
- return ret;
+ return makeInt(index);
}
void RKD_ReleasePattern (SEXP ref, pDevDesc dev) {
More information about the rkward-tracker
mailing list