diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py
index 2356930429f0..8192d514f4ff 100644
--- a/lib/matplotlib/artist.py
+++ b/lib/matplotlib/artist.py
@@ -671,19 +671,21 @@ def get_sketch_params(self):
-------
tuple or None
- A 3-tuple with the following elements:
+ A 4-tuple with the following elements:
- *scale*: The amplitude of the wiggle perpendicular to the
source line.
- *length*: The length of the wiggle along the line.
- *randomness*: The scale factor by which the length is
shrunken or expanded.
+ - *seed*: Seed for the internal pseudo-random number
+ generator.
Returns *None* if no sketch parameters were set.
"""
return self._sketch
- def set_sketch_params(self, scale=None, length=None, randomness=None):
+ def set_sketch_params(self, scale=None, length=None, randomness=None, seed=0):
"""
Set the sketch parameters.
@@ -702,13 +704,18 @@ def set_sketch_params(self, scale=None, length=None, randomness=None):
The PGF backend uses this argument as an RNG seed and not as
described above. Using the same seed yields the same random shape.
+ seed : int, optional
+ Seed for the internal pseudo-random number generator.
+ For the same seed, the result will be exactly the same.
- .. ACCEPTS: (scale: float, length: float, randomness: float)
+ .. versionadded:: 3.8
+
+ .. ACCEPTS: (scale: float, length: float, randomness: float, seed: int)
"""
if scale is None:
self._sketch = None
else:
- self._sketch = (scale, length or 128.0, randomness or 16.0)
+ self._sketch = (scale, length or 128.0, randomness or 16.0, seed)
self.stale = True
def set_path_effects(self, path_effects):
diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py
index d0ff3fec9c4d..136266c7e7f3 100644
--- a/lib/matplotlib/backend_bases.py
+++ b/lib/matplotlib/backend_bases.py
@@ -1062,7 +1062,7 @@ def get_sketch_params(self):
"""
return self._sketch
- def set_sketch_params(self, scale=None, length=None, randomness=None):
+ def set_sketch_params(self, scale=None, length=None, randomness=None, seed=0):
"""
Set the sketch parameters.
@@ -1076,10 +1076,12 @@ def set_sketch_params(self, scale=None, length=None, randomness=None):
The length of the wiggle along the line, in pixels.
randomness : float, default: 16
The scale factor by which the length is shrunken or expanded.
+ seed : int, optional
+ Seed for the internal pseudo-random number generator.
"""
self._sketch = (
None if scale is None
- else (scale, length or 128., randomness or 16.))
+ else (scale, length or 128., randomness or 16., seed))
class TimerBase:
diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py
index 8b6c3669b967..e76009315e6e 100644
--- a/lib/matplotlib/lines.py
+++ b/lib/matplotlib/lines.py
@@ -823,8 +823,8 @@ def draw(self, renderer):
ec_rgba = ec_rgba[:3] + (fc_rgba[3],)
gc.set_foreground(ec_rgba, isRGBA=True)
if self.get_sketch_params() is not None:
- scale, length, randomness = self.get_sketch_params()
- gc.set_sketch_params(scale/2, length/2, 2*randomness)
+ scale, length, randomness, seed = self.get_sketch_params()
+ gc.set_sketch_params(scale/2, length/2, 2*randomness, seed)
marker = self._marker
diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py
index 27b677195470..ceee64ed053b 100644
--- a/lib/matplotlib/pyplot.py
+++ b/lib/matplotlib/pyplot.py
@@ -668,7 +668,7 @@ def setp(obj, *args, **kwargs):
def xkcd(
- scale: float = 1, length: float = 100, randomness: float = 2
+ scale: float = 1, length: float = 100, randomness: float = 2, seed: int = -1
) -> ExitStack:
"""
Turn on `xkcd `_ sketch-style drawing mode. This will
@@ -685,6 +685,10 @@ def xkcd(
The length of the wiggle along the line.
randomness : float, optional
The scale factor by which the length is shrunken or expanded.
+ seed: int, optional
+ Seed for the internal pseudo-random number generator. The special
+ value of -1 will make the randomness different for each object
+ processed.
Notes
-----
@@ -717,7 +721,7 @@ def xkcd(
'font.family': ['xkcd', 'xkcd Script', 'Humor Sans', 'Comic Neue',
'Comic Sans MS'],
'font.size': 14.0,
- 'path.sketch': (scale, length, randomness),
+ 'path.sketch': (scale, length, randomness, seed),
'path.effects': [
patheffects.withStroke(linewidth=4, foreground="w")],
'axes.linewidth': 1.5,
diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py
index a9bebd209077..658df518a9e1 100644
--- a/lib/matplotlib/rcsetup.py
+++ b/lib/matplotlib/rcsetup.py
@@ -549,7 +549,14 @@ def validate_sketch(s):
try:
return tuple(_listify_validator(validate_float, n=3)(s))
except ValueError:
- raise ValueError("Expected a (scale, length, randomness) triplet")
+ try:
+ result = tuple(_listify_validator(validate_float, n=4)(s))
+ # make sure seed is an integer
+ return (result[0], result[1], result[2], int(result[3]))
+ except ValueError:
+ raise ValueError(
+ "path.sketch must be a 3-tuple (scale, length, randomness) or"
+ " a 4-tuple (scale, length, randomness, seed)")
def _validate_greaterequal0_lessthan1(s):
diff --git a/setupext.py b/setupext.py
index a91c681e0f58..351bea41dd62 100644
--- a/setupext.py
+++ b/setupext.py
@@ -434,6 +434,7 @@ def get_extensions(self):
ext = Extension(
"matplotlib._path", [
"src/py_converters.cpp",
+ "src/path_converters.cpp",
"src/_path_wrapper.cpp",
])
add_numpy_flags(ext)
diff --git a/src/_backend_agg.h b/src/_backend_agg.h
index f15fa05dd5fd..6ed5d519b52b 100644
--- a/src/_backend_agg.h
+++ b/src/_backend_agg.h
@@ -483,7 +483,7 @@ RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans,
snapped_t snapped(clipped, gc.snap_mode, path.total_vertices(), snapping_linewidth);
simplify_t simplified(snapped, simplify, path.simplify_threshold());
curve_t curve(simplified);
- sketch_t sketch(curve, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness);
+ sketch_t sketch(curve, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness, gc.sketch.seed);
_draw_path(sketch, has_clippath, face, gc);
}
diff --git a/src/_backend_agg_basic_types.h b/src/_backend_agg_basic_types.h
index 21a84bb6a5ae..ad0254ae04ab 100644
--- a/src/_backend_agg_basic_types.h
+++ b/src/_backend_agg_basic_types.h
@@ -23,6 +23,7 @@ struct SketchParams
double scale;
double length;
double randomness;
+ int seed;
};
class Dashes
diff --git a/src/_path.h b/src/_path.h
index bfbb7b49d254..aff6a0579ddf 100644
--- a/src/_path.h
+++ b/src/_path.h
@@ -1067,7 +1067,7 @@ void cleanup_path(PathIterator &path,
__cleanup_path(simplified, vertices, codes);
} else {
curve_t curve(simplified);
- sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness);
+ sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness, sketch_params.seed);
__cleanup_path(sketch, vertices, codes);
}
}
@@ -1232,7 +1232,7 @@ bool convert_to_string(PathIterator &path,
return __convert_to_string(simplified, precision, codes, postfix, buffer);
} else {
curve_t curve(simplified);
- sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness);
+ sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness, sketch_params.seed);
return __convert_to_string(sketch, precision, codes, postfix, buffer);
}
diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp
index e1cb0a4f89af..c372ca956563 100644
--- a/src/_path_wrapper.cpp
+++ b/src/_path_wrapper.cpp
@@ -4,6 +4,7 @@
#include "py_converters.h"
#include "py_adaptors.h"
+#include "path_converters.h"
PyObject *convert_polygon_vector(std::vector &polygons)
{
@@ -842,6 +843,22 @@ static PyObject *Py_is_sorted(PyObject *self, PyObject *obj)
}
+const char *Py_sketch_reset_previous_seed__doc__ = "sketch_reset_previous_seed(seed)";
+
+static PyObject *Py_sketch_reset_previous_seed(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ int seed;
+
+ if (!PyArg_ParseTuple(args,"i:sketch_reset_previous_seed", &seed)) {
+ return NULL;
+ }
+
+ CALL_CPP("SketchBase::reset_previous_seed",(SketchBase::reset_previous_seed(seed)));
+
+ Py_RETURN_NONE;
+}
+
+
static PyMethodDef module_functions[] = {
{"point_in_path", (PyCFunction)Py_point_in_path, METH_VARARGS, Py_point_in_path__doc__},
{"points_in_path", (PyCFunction)Py_points_in_path, METH_VARARGS, Py_points_in_path__doc__},
@@ -861,6 +878,7 @@ static PyMethodDef module_functions[] = {
{"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__},
{"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__},
{"is_sorted", (PyCFunction)Py_is_sorted, METH_O, Py_is_sorted__doc__},
+ {"sketch_reset_previous_seed", (PyCFunction)Py_sketch_reset_previous_seed, METH_VARARGS, Py_sketch_reset_previous_seed__doc__},
{NULL}
};
diff --git a/src/path_converters.cpp b/src/path_converters.cpp
new file mode 100644
index 000000000000..70d1438c8462
--- /dev/null
+++ b/src/path_converters.cpp
@@ -0,0 +1,8 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#define NO_IMPORT_ARRAY
+
+
+#include "path_converters.h"
+
+int SketchBase::previous_seed=0;
diff --git a/src/path_converters.h b/src/path_converters.h
index 8583d84855aa..ce7bd1efea90 100644
--- a/src/path_converters.h
+++ b/src/path_converters.h
@@ -991,8 +991,36 @@ class PathSimplifier : protected EmbeddedQueue<9>
}
};
+// XXX: remove!!!
+// #include
+
+class SketchBase
+{
+ static int previous_seed;
+ protected:
+ /* handle the special-case value -1 which means: use the
+ previous seed plus one. If not a special value, only
+ remember it as the last one and return it.
+ */
+ int get_prng_seed(int seed)
+ {
+ if (seed==-1) {
+ previous_seed++;
+ } else {
+ previous_seed = seed;
+ }
+ // std::cerr<<"Seeding PRNG with "<
-class Sketch
+class Sketch: SketchBase
{
public:
/*
@@ -1004,8 +1032,10 @@ class Sketch
randomness: the factor that the sketch length will randomly
shrink and expand.
+
+ seed: seed for the built-in pseudo-random number generator.
*/
- Sketch(VertexSource &source, double scale, double length, double randomness)
+ Sketch(VertexSource &source, double scale, double length, double randomness, int seed)
: m_source(&source),
m_scale(scale),
m_length(length),
@@ -1015,9 +1045,13 @@ class Sketch
m_last_y(0.0),
m_has_last(false),
m_p(0.0),
- m_rand(0)
+ m_rand(0),
+ /* if scale==0. (e.g. with test), then seed is 0
+ that would set previous_seed to 0 which is not what we want.
+ */
+ m_seed0( scale!=0 ? get_prng_seed(seed) : 0)
{
- rewind(0);
+ rewind(0); // re-seeds PRNG with m_seed0 again
const double d_M_PI = 3.14159265358979323846;
m_p_scale = (2.0 * d_M_PI) / (m_length * m_randomness);
m_log_randomness = 2.0 * log(m_randomness);
@@ -1081,7 +1115,7 @@ class Sketch
m_has_last = false;
m_p = 0.0;
if (m_scale != 0.0) {
- m_rand.seed(0);
+ m_rand.seed(m_seed0);
m_segmented.rewind(path_id);
} else {
m_source->rewind(path_id);
@@ -1101,6 +1135,7 @@ class Sketch
RandomNumberGenerator m_rand;
double m_p_scale;
double m_log_randomness;
+ int m_seed0;
};
#endif // MPL_PATH_CONVERTERS_H
diff --git a/src/py_converters.cpp b/src/py_converters.cpp
index cd4013bb30ad..b472fb30668f 100644
--- a/src/py_converters.cpp
+++ b/src/py_converters.cpp
@@ -451,13 +451,16 @@ int convert_sketch_params(PyObject *obj, void *sketchp)
{
SketchParams *sketch = (SketchParams *)sketchp;
+ sketch->seed=0; // default
+
if (obj == NULL || obj == Py_None) {
sketch->scale = 0.0;
} else if (!PyArg_ParseTuple(obj,
- "ddd:sketch_params",
+ "ddd|i:sketch_params",
&sketch->scale,
&sketch->length,
- &sketch->randomness)) {
+ &sketch->randomness,
+ &sketch->seed)) {
return 0;
}