[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