[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