[education/rkward] rkward/rbackend/rkwarddevice: Start implementing R 4.1.x pattern/gradients

Thomas Friedrichsmeier null at kde.org
Sat Mar 19 21:57:45 GMT 2022


Git commit e8ca8df9fdafca2b925f906da9d2baf17d828b94 by Thomas Friedrichsmeier.
Committed on 14/03/2022 at 21:44.
Pushed by tfry into branch 'master'.

Start implementing R 4.1.x pattern/gradients

Linear patterns work, nicely. But wouldn't it be just too easy, if qt and cairo were using the same semantics for radial gradients?!

M  +21   -4    rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
M  +7    -1    rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
M  +68   -9    rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
M  +28   -6    rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
M  +71   -11   rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp

https://invent.kde.org/education/rkward/commit/e8ca8df9fdafca2b925f906da9d2baf17d828b94

diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
index 487bcf3b..78c23ba2 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
@@ -121,12 +121,15 @@ void RKGraphicsDevice::closeDevice (int devnum) {
 	devices.take (devnum)->deleteLater ();
 }
 
-void RKGraphicsDevice::clear (const QColor& col) {
+void RKGraphicsDevice::clear(const QBrush& brush) {
 	RK_TRACE (GRAPHICS_DEVICE);
 
-	if (painter.isActive ()) painter.end ();
-	if (col.isValid ()) area.fill (col);
-	else area.fill (QColor (255, 255, 255, 255));
+	if (painter.isActive()) painter.end();
+	if (brush.style() == Qt::NoBrush) area.fill(QColor(255, 255, 255, 255));
+	else {
+		painter.setBrush(brush);
+		painter.drawRect(0, 0, area.width(), area.height());
+	}
 
 	updateNow ();
 	setClip (area.rect ());	// R's devX11.c resets clip on clear, so we do this, too.
@@ -143,6 +146,20 @@ void RKGraphicsDevice::setAreaSize (const QSize& size) {
 	clear ();
 }
 
+
+int RKGraphicsDevice::registerPattern(const QBrush& brush) {
+	RK_TRACE (GRAPHICS_DEVICE);
+	static int id = 0;
+	patterns.insert(++id, brush);
+	return id;
+}
+
+void RKGraphicsDevice::destroyPattern(int id) {
+	RK_TRACE (GRAPHICS_DEVICE);
+	if (id == 0) patterns.clear();
+	else patterns.remove(id);
+}
+
 void RKGraphicsDevice::setClip (const QRectF& new_clip) {
 	RK_TRACE (GRAPHICS_DEVICE);
 
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
index 7d643fa2..9598a4e9 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
@@ -65,7 +65,7 @@ public:
 	void polygon (const QPolygonF& pol, const QPen& pen, const QBrush &brush);
 	void polyline (const QPolygonF& pol, const QPen& pen);
 	void polypath (const QVector<QPolygonF>& polygons, bool winding, const QPen& pen, const QBrush& brush);
-	void clear (const QColor& col=QColor());
+	void clear(const QBrush& col=QBrush());
 	void image (const QImage &image, const QRectF &target_rect, double rot, bool interpolate);
 	QImage capture () const;
 	void setActive (bool active);
@@ -91,6 +91,11 @@ public:
  	QWidget* viewPort () const { return view; };
 	QSizeF currentSize () const { return view->size (); }
 	void setAreaSize (const QSize &size);
+
+/** Patterns / gradients are registered per device in R */
+	int registerPattern(const QBrush &brush);
+	void destroyPattern(int id);
+	QBrush getPattern(int id) const { return patterns.value(id); };
 public slots:
 	void stopInteraction ();
 signals:
@@ -118,6 +123,7 @@ private:
 	QLabel *view;
 	QString base_title;
 	QDialog *dialog;
+	QHash<int, QBrush> patterns;
 
 	int interaction_opcode;	/**< Current interactive operation (from RKDOpcodes enum), or -1 is there is no current interactive operation */
 
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
index e17f4817..f595b127 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
@@ -135,10 +135,62 @@ static QPen readPen (QDataStream &instream) {
 	return ret;
 }
 
-static QBrush readBrush (QDataStream &instream) {
-	QColor col = readColor (instream);
-	if (!col.isValid ()) return QBrush ();
-	return QBrush (col);
+static QBrush readBrush(QDataStream &instream, RKGraphicsDevice *dev) {
+	qint8 filltype;
+	instream >> filltype;
+	if (filltype  == ColorFill) {
+		QColor col = readColor(instream);
+		if (!col.isValid()) return QBrush();
+		return QBrush(col);
+	} else {
+		qint16 pattern_num;
+		instream >> pattern_num;
+		return dev->getPattern(pattern_num);
+	}
+}
+
+static void readGradientStopsAndExtent(QDataStream &instream, QGradient* g) {
+	QGradientStops stops;
+	qint16 nstops;
+	instream >> nstops;
+	for (int i = 0; i < nstops; ++i) {
+		double pos;
+		QColor col = readColor(instream);
+		instream >> pos;
+		stops.append(QGradientStop(pos, col));
+	}
+	qint8 extend;
+	instream >> extend;
+	if (extend == GradientExtendPad) g->setSpread(QGradient::PadSpread);
+	else if (extend == GradientExtendReflect) g->setSpread(QGradient::ReflectSpread);
+	else if (extend == GradientExtendRepeat) g->setSpread(QGradient::RepeatSpread);
+	else {
+		// Qt does not provide extend "none", so emulate by adding transparent before the first and after the last stop
+		stops.prepend(QGradientStop(0.oo, Qt::transparent));
+		stops.append(QGradientStop(1.0, Qt::transparent));
+	}
+	g->setStops(stops);
+}
+
+static int readNewPattern(QDataStream &instream, RKGraphicsDevice *device) {
+	qint8 patterntype;
+	instream >> patterntype;
+
+	if (patterntype == LinearPattern) {
+		double x1, x2, y1, y2;
+		instream >> x1 >> x2 >> y1 >> y2;
+		QLinearGradient g(x1, y1, x2, y2);
+		readGradientStopsAndExtent(instream, &g);
+		return device->registerPattern(QBrush(g));
+	} else if (patterntype == RadialPattern) {
+		double cx1, cy1, r1, cx2, cy2, r2;
+		instream >> cx1 >> cy1 >> r1 >> cx2 >> cy2 >> r2;
+		QRadialGradient g(cx1, cy1, r1, cx2, cy2, r2);
+		readGradientStopsAndExtent(instream, &g);
+		return device->registerPattern(QBrush(g));
+	} else {
+		return -1;
+	}
 }
 
 static QFont readFont (QDataStream &instream) {
@@ -219,7 +271,7 @@ void RKGraphicsDeviceFrontendTransmitter::newData () {
 			double x, y, r;
 			streamer.instream >> x >> y >> r;
 			QPen pen = readSimplePen (streamer.instream);
-			device->circle (x, y, r, pen, readBrush (streamer.instream));
+			device->circle(x, y, r, pen, readBrush(streamer.instream, device));
 		} else if (opcode == RKDLine) {
 			double x1, y1, x2, y2;
 			streamer.instream >> x1 >> y1 >> x2 >> y2;
@@ -227,7 +279,7 @@ void RKGraphicsDeviceFrontendTransmitter::newData () {
 		} else if (opcode == RKDPolygon) {
 			QPolygonF pol (readPoints (streamer.instream));
 			QPen pen = readPen (streamer.instream);
-			device->polygon (pol, pen, readBrush (streamer.instream));
+			device->polygon(pol, pen, readBrush(streamer.instream, device));
 		} else if (opcode == RKDPolyline) {
 			QPolygonF pol (readPoints (streamer.instream));
 			device->polyline (pol, readPen (streamer.instream));
@@ -242,12 +294,12 @@ void RKGraphicsDeviceFrontendTransmitter::newData () {
 			bool winding;
 			streamer.instream >> winding;
 			QPen pen = readPen (streamer.instream);
-			device->polypath (polygons, winding, pen, readBrush (streamer.instream));
+			device->polypath(polygons, winding, pen, readBrush(streamer.instream, device));
 		} else if (opcode == RKDRect) {
 			QRectF rect;
 			streamer.instream >> rect;
 			QPen pen = readPen (streamer.instream);
-			device->rect (rect, pen, readBrush (streamer.instream));
+			device->rect(rect, pen, readBrush(streamer.instream, device));
 		} else if (opcode == RKDStrWidthUTF8) {
 			QString out;
 			streamer.instream >> out;
@@ -268,7 +320,7 @@ void RKGraphicsDeviceFrontendTransmitter::newData () {
 			QColor col = readColor (streamer.instream);
 			device->text (x, y, out, rot, hadj, col, readFont (streamer.instream));
 		} else if (opcode == RKDNewPage) {
-			device->clear (readColor (streamer.instream));
+			device->clear(readBrush(streamer.instream, device));
 		} else if (opcode == RKDClose) {
 			RKGraphicsDevice::closeDevice (devnum);
 		} else if (opcode == RKDActivate) {
@@ -297,6 +349,13 @@ void RKGraphicsDeviceFrontendTransmitter::newData () {
 			bool interpolate;
 			streamer.instream >> target >> rotation >> interpolate;
 			device->image (image, target.normalized (), rotation, interpolate);
+		} else if (opcode == RKDSetPattern) {
+			streamer.outstream << (qint32) readNewPattern(streamer.instream, device);
+			streamer.writeOutBuffer();
+		} else if (opcode == RKDReleasePattern) {
+			qint32 index;
+			streamer.instream >> index;
+			device->destroyPattern(index);
 		} 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 9712759c..2f79ed70 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
@@ -60,9 +60,29 @@ enum RKLineJoinStyles {
 	BevelJoin = 3
 };
 
+enum RKDFillType {
+	ColorFill,
+	PatternFill
+};
+
+enum RKDPatternType {
+	LinearPattern,
+	RadialPattern,
+	TilingPattern,
+	UnknonwnPattern
+};
+
+enum RKDGradientExtend {
+	GradientExtendNone,
+	GradientExtendPad,
+	GradientExtendReflect,
+	GradientExtendRepeat
+};
+
 enum RKDOpcodes {
+	// NOTE: the only point of the assigned int values is to ease debugging in case of trouble
 	// Asynchronous operations
-	RKDCreate,             // 0
+	RKDCreate               = 0,
 	RKDCircle,
 	RKDLine,
 	RKDPolygon,
@@ -80,19 +100,21 @@ enum RKDOpcodes {
 	RKDSetSize,            // 15
 	RKDStartGettingEvents,
 	RKDStopGettingEvents,
+	RKDReleasePattern,
 
 	// Synchronous operations
-	RKDFetchNextEvent,
+	RKDFetchNextEvent      = 100,
 	RKDStrWidthUTF8,
-	RKDMetricInfo,         // 20
+	RKDMetricInfo,
 	RKDLocator,
 	RKDNewPageConfirm,
-	RKDCapture,
+	RKDCapture,           // 105
 	RKDQueryResolution,
-	RKDGetSize,            // 25
+	RKDGetSize,
+	RKDSetPattern,
 
 	// Protocol operations
-	RKDCancel
+	RKDCancel              = 200
 };
 
 enum RKDEventCodes {
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
index addabba2..20bf18dd 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
@@ -164,8 +164,15 @@ public:
 // actually lmitre is only needed if linejoin is GE_MITRE_JOIN, so we could optimize a bit
 #define WRITE_LINE_ENDS() \
 	RKD_OUT_STREAM << SAFE_LINE_END (gc->lend) << SAFE_LINE_JOIN (gc->ljoin) << gc->lmitre
-#define WRITE_FILL() \
-	WRITE_COLOR_BYTES (gc->fill)
+#if R_VERSION >= R_Version(4, 1, 0)
+#  define WRITE_FILL() \
+	if (gc->patternFill != R_NilValue) RKD_OUT_STREAM << (qint8) PatternFill << (qint16) (INTEGER(gc->patternFill)[0]); \
+	else { \
+		RKD_OUT_STREAM << (qint8) ColorFill; WRITE_COLOR_BYTES (gc->fill); \
+	}
+#else
+	RKD_OUT_STREAM << (qint8) ColorFill; WRITE_COLOR_BYTES (gc->fill);
+#endif
 #define WRITE_FONT(dev) \
 	RKD_OUT_STREAM << gc->cex << gc->ps << gc->lineheight << (quint8) gc->fontface << (gc->fontfamily[0] ? QString (gc->fontfamily) : (static_cast<RKGraphicsDeviceDesc*> (dev->deviceSpecific)->getFontFamily (gc->fontface == 5)))
 
@@ -591,17 +598,70 @@ int RKD_HoldFlush (pDevDesc dev, int level) {
 #endif
 
 #if R_VERSION >= R_Version (4, 1, 0)
-SEXP RKD_SetPattern (SEXP pattern, pDevDesc dd) {
-#ifdef __GNUC__
-#warning implement me
-#endif
-	return R_NilValue;
+qint8 getGradientExtend(int Rextent) {
+	if (Rextent == R_GE_patternExtendPad) return GradientExtendPad;
+	if (Rextent == R_GE_patternExtendReflect) return GradientExtendReflect;
+	if (Rextent == R_GE_patternExtendRepeat) return GradientExtendRepeat;
+	/* if (Rextent == R_GE_patternExtendNone) */ return GradientExtendNone;
 }
 
-void RKD_ReleasePattern (SEXP ref, pDevDesc dd) {
-#ifdef __GNUC__
-#warning implement me
-#endif
+SEXP RKD_SetPattern (SEXP pattern, pDevDesc dev) {
+	{
+		RKGraphicsDataStreamWriteGuard wguard;
+		WRITE_HEADER (RKDSetPattern, dev);
+		auto ptype = R_GE_patternType(pattern);
+		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);
+			qint16 nstops = R_GE_linearGradientNumStops(pattern);
+			RKD_OUT_STREAM << nstops;
+			for (int i = 0; i < nstops; ++i) {
+				WRITE_COLOR_BYTES(R_GE_linearGradientColour(pattern, i));
+				RKD_OUT_STREAM << (double) R_GE_linearGradientStop(pattern, i);
+			}
+			RKD_OUT_STREAM << getGradientExtend(R_GE_linearGradientExtend(pattern));
+		} else if (ptype == R_GE_radialGradientPattern) {
+			RKD_OUT_STREAM << (qint8) RKDPatternType::RadialPattern;
+			RKD_OUT_STREAM << (double) R_GE_radialGradientCX1(pattern) << (double) R_GE_radialGradientCY1(pattern) << (double) R_GE_radialGradientR1(pattern);
+			RKD_OUT_STREAM << (double) R_GE_radialGradientCX2(pattern) << (double) R_GE_radialGradientCY2(pattern) << (double) R_GE_radialGradientR2(pattern);
+			qint16 nstops = R_GE_radialGradientNumStops(pattern);
+			RKD_OUT_STREAM << nstops;
+			for (int i = 0; i < nstops; ++i) {
+				WRITE_COLOR_BYTES(R_GE_radialGradientColour(pattern, i));
+				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;
+		}
+	}
+
+	qint32 index;
+	{
+		RKGraphicsDataStreamReadGuard rguard;
+		RKD_IN_STREAM >> index;
+	}
+
+	// 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;
+}
+
+void RKD_ReleasePattern (SEXP ref, pDevDesc dev) {
+	qint32 index;
+	if (ref == R_NilValue) index = 0;  // means: destroy all patterns
+	else index = INTEGER(ref)[0];
+	{
+		RKGraphicsDataStreamWriteGuard wguard;
+		WRITE_HEADER(RKDReleasePattern, dev);
+		RKD_OUT_STREAM << index;
+	}
 }
 
 SEXP RKD_SetClipPath (SEXP path, SEXP ref, pDevDesc dd) {



More information about the rkward-tracker mailing list