diff --git a/doc/api/next_api_changes/behavior/26050-AS.rst b/doc/api/next_api_changes/behavior/26050-AS.rst new file mode 100644 index 000000000000..0f8424f4d047 --- /dev/null +++ b/doc/api/next_api_changes/behavior/26050-AS.rst @@ -0,0 +1,13 @@ +Seed for ``path.sketch`` will have a rolling (auto incrementing) behaviour +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The seed for the internal Pseudo number generator will now have an auto changing behavior. +This means that the C code of every artist will get a different seed every time it is called +and this will be done in a deterministic manner. + +Two figures sketched with the same parameters and different seed will look different from one another. + +``Artist.get_sketch_params()`` will now return a 4-tuple instead of a 3-tuple consisting of +(scale, length, randomness, seed) of the form (float, float, float, int). + +See 'What's new' on how to set a value to the seed and its behaviour. diff --git a/doc/users/next_whats_new/sketch_seed.rst b/doc/users/next_whats_new/sketch_seed.rst new file mode 100644 index 000000000000..97ebea87fe2c --- /dev/null +++ b/doc/users/next_whats_new/sketch_seed.rst @@ -0,0 +1,67 @@ +``sketch_seed`` parameter for rcParams +-------------------------------------- + +`~matplotlib.rcParams` now has a new parameter ``path.sketch_seed``. +Its default value is 0 and accepted values are any non negative integer. +This allows the user to set the seed for the internal pseudo random number generator in one of three ways. + +1) Directly changing the rcParam: + + rcParams['path.sketch_seed'] = 20 + +2) Passing a value to the new *seed* parameter of `~matplotlib.pyplot.xkcd` function: + + plt.xkcd(seed=20) + +3) Passing a value to the new *seed* parameter of matplotlib.artist.set_sketch_params function: + + ln = plt.plot(x, y) + ln[0].set_sketch_params(seed = 20) + +The seed will also have a changing characteristic for every artist which will be done in a deterministic manner. + + +.. plot:: + :include-source: true + + import matplotlib.pyplot as plt + from matplotlib import rcParams + + with plt.xkcd(): + rcParams['path.sketch_seed']=0 + rcParams['path.sketch']=(2,120,40) + pat,txt=plt.pie([10,20,30,40],wedgeprops={'edgecolor':'black'}) + plt.legend(pat,['first','second','third','fourth'],loc='best') + plt.title("seed = 0") + plt.show() + +.. plot:: + :include-source: true + + import matplotlib.pyplot as plt + from matplotlib import rcParams + + fig, ax = plt.subplots() + x = np.linspace(0.7, 1.42, 100) + y = x ** 2 + ln = ax.plot(x, y, color='black') + ln[0].set_sketch_params(100, 100, 20, 40) + plt.title("seed = 40") + plt.show() + +.. plot:: + :include-source: true + + import matplotlib.pyplot as plt + from matplotlib import rcParams + + with plt.xkcd(seed=19680801): + import matplotlib + from matplotlib import gridspec + + rcParams['path.sketch']=(2,120,40) + + pat,txt=plt.pie([10,20,30,40],wedgeprops={'edgecolor':'black'}) + plt.legend(pat,['first','second','third','fourth'],loc='best') + plt.title("seed = 19680801") + plt.show() diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 5c3f6dc5952f..c53c3f88da7c 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -209,6 +209,7 @@ def __init__(self): self._gid = None self._snap = None self._sketch = mpl.rcParams['path.sketch'] + self._sketch_seed = mpl.rcParams['path.sketch_seed'] self._path_effects = mpl.rcParams['path.effects'] self._sticky_edges = _XYPair([], []) self._in_layout = True @@ -681,7 +682,8 @@ 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=None): """ Set the sketch parameters. @@ -701,12 +703,21 @@ 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. - .. ACCEPTS: (scale: float, length: float, randomness: float) + seed : int, optional + Seed for the internal pseudo-random number generator. + + .. versionadded:: 3.8 + + .. ACCEPTS: (scale: float, length: float, randomness: float, seed: int) """ + if seed is not None: + self._sketch_seed = seed + 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, + self._sketch_seed) self.stale = True def set_path_effects(self, path_effects): diff --git a/lib/matplotlib/artist.pyi b/lib/matplotlib/artist.pyi index 101e97a9a072..a9d5d7ed47b6 100644 --- a/lib/matplotlib/artist.pyi +++ b/lib/matplotlib/artist.pyi @@ -76,12 +76,13 @@ class Artist: def set_gid(self, gid: str | None) -> None: ... def get_snap(self) -> bool | None: ... def set_snap(self, snap: bool | None) -> None: ... - def get_sketch_params(self) -> tuple[float, float, float] | None: ... + def get_sketch_params(self) -> tuple[float, float, float, int] | None: ... def set_sketch_params( self, scale: float | None = ..., length: float | None = ..., randomness: float | None = ..., + seed: int | None = ..., ) -> None: ... def set_path_effects(self, path_effects: list[AbstractPathEffect]) -> None: ... def get_path_effects(self) -> list[AbstractPathEffect]: ... diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 009410593b34..dbf1788b97af 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -754,6 +754,19 @@ def _draw_disabled(self): return _setattr_cm(self, **no_ops) + @property + def _seed_increment(self): + """ + seed increment for renderer. + It is used to implement the rolling characteristic for seed + """ + self.__seed_increment += 1 + return self.__seed_increment + + @_seed_increment.setter + def _seed_increment(self, value): + self.__seed_increment = value + class GraphicsContextBase: """An abstract base class that provides color, line styles, etc.""" @@ -1062,7 +1075,8 @@ 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=None): """ Set the sketch parameters. @@ -1076,10 +1090,19 @@ 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. + + .. versionadded:: 3.8 """ + self._sketch = ( None if scale is None - else (scale, length or 128., randomness or 16.)) + else (scale, + length or rcParams['path.sketch'][1], + randomness or rcParams['path.sketch'][2], + seed or rcParams['path.sketch_seed']) + ) class TimerBase: diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index 0ae88cf18a42..3d47f39a1dae 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -136,6 +136,10 @@ class RendererBase: def stop_rasterizing(self) -> None: ... def start_filter(self) -> None: ... def stop_filter(self, filter_func) -> None: ... + @property + def _seed_increment(self) -> int: ... + @_seed_increment.setter + def _seed_increment(self, value: int) -> None: ... class GraphicsContextBase: def __init__(self) -> None: ... @@ -180,6 +184,7 @@ class GraphicsContextBase: scale: float | None = ..., length: float | None = ..., randomness: float | None = ..., + seed:int | None = ..., ) -> None: ... class TimerBase: diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 734c188b0048..2c05d1b6e182 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -575,7 +575,7 @@ def _print_pgf_path(self, gc, path, transform, rgbFace=None): # and has a separate "scale" argument for the amplitude. # -> Use "randomness" as PRNG seed to allow the user to force the # same shape on multiple sketched lines - scale, length, randomness = sketch_params + scale, length, randomness, seed = sketch_params if scale is not None: # make matplotlib and PGF rendering visually similar length *= 0.5 diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index ce263c3d8d1c..244100444507 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -45,6 +45,7 @@ Artist, allow_rasterization, _finalize_rasterization) from matplotlib.backend_bases import ( DrawEvent, FigureCanvasBase, NonGuiException, MouseButton, _get_renderer) + import matplotlib._api as _api import matplotlib.cbook as cbook import matplotlib.colorbar as cbar @@ -3141,6 +3142,7 @@ def draw(self, renderer): artists = self._get_draw_artists(renderer) try: + renderer._seed_increment = 0 renderer.open_group('figure', gid=self.get_gid()) if self.axes and self.get_layout_engine() is not None: try: diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 92d55a3fe6ae..f793d1cdf7f6 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -823,7 +823,8 @@ def draw(self, renderer): 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) + seed = self._sketch_seed + gc.set_sketch_params(scale/2, length/2, 2*randomness, seed) marker = self._marker diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 2c53651da3d6..f9ad70f00f24 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -677,6 +677,7 @@ # - *randomness* is the factor by which the length is # randomly scaled. #path.effects: +#path.sketch_seed: 0 # seed for the internal pseudo number generator. ## *************************************************************************** diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 92dc55940b8a..802fa34b79f5 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -563,7 +563,9 @@ def _draw_paths_with_artist_properties( gc.set_hatch_color(self._hatch_color) if self.get_sketch_params() is not None: - gc.set_sketch_params(*self.get_sketch_params()) + scale, length, randomness = self.get_sketch_params() + gc.set_sketch_params(scale, length, randomness, + self._sketch_seed+renderer._seed_increment) if self.get_path_effects(): from matplotlib.patheffects import PathEffectRenderer diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index a687db923c3c..f907e27dc362 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -382,8 +382,8 @@ def iter_segments(self, transform=None, remove_nans=True, clip=None, If True, curve segments will be returned as curve segments. If False, all curves will be converted to line segments. sketch : None or sequence, optional - If not None, must be a 3-tuple of the form - (scale, length, randomness), representing the sketch parameters. + If not None, must be a 4-tuple of the form + (scale, length, randomness, seed), representing the sketch parameters. """ if not len(self): return diff --git a/lib/matplotlib/path.pyi b/lib/matplotlib/path.pyi index c96c5a0ba9e4..91256e7fd1cd 100644 --- a/lib/matplotlib/path.pyi +++ b/lib/matplotlib/path.pyi @@ -61,7 +61,7 @@ class Path: stroke_width: float = ..., simplify: bool | None = ..., curves: bool = ..., - sketch: tuple[float, float, float] | None = ..., + sketch: tuple[float, float, float, int] | None = ..., ) -> Generator[tuple[np.ndarray, np.uint8], None, None]: ... def iter_bezier(self, **kwargs) -> Generator[BezierSegment, None, None]: ... def cleaned( @@ -74,7 +74,7 @@ class Path: curves: bool = ..., stroke_width: float = ..., snap: bool | None = ..., - sketch: tuple[float, float, float] | None = ... + sketch: tuple[float, float, float, int] | None = ... ) -> Path: ... def transformed(self, transform: Transform) -> Path: ... def contains_point( diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 415d5c042241..618cc1bb35e3 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -701,8 +701,8 @@ def setp(obj, *args, **kwargs): def xkcd( - scale: float = 1, length: float = 100, randomness: float = 2 -) -> ExitStack: + scale: float = 1, length: float = 100, randomness: float = 2, + seed: int | None = None) -> ExitStack: """ Turn on `xkcd `_ sketch-style drawing mode. This will only have effect on things drawn after this function is called. @@ -718,6 +718,8 @@ 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. Notes ----- @@ -738,6 +740,9 @@ def xkcd( # This cannot be implemented in terms of contextmanager() or rc_context() # because this needs to work as a non-contextmanager too. + if seed is not None: + rcParams['path.sketch_seed'] = seed + if rcParams['text.usetex']: raise RuntimeError( "xkcd mode is not compatible with text.usetex = True") diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 276bb9f812a9..09e5ebbd1eb9 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -565,6 +565,15 @@ def validate_sketch(s): raise ValueError("Expected a (scale, length, randomness) triplet") +def validate_sketch_seed(s): + s = validate_int(s) + + if s >= 0: + return s + else: + raise ValueError("seed must be a non negative integer") + + def _validate_greaterthan_minushalf(s): s = validate_float(s) if s > -0.5: @@ -1288,6 +1297,7 @@ def _convert_validator_spec(key, conv): "path.simplify_threshold": _validate_greaterequal0_lessequal1, "path.snap": validate_bool, "path.sketch": validate_sketch, + "path.sketch_seed": validate_sketch_seed, "path.effects": validate_anylist, "agg.path.chunksize": validate_int, # 0 to disable chunking diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi index 8a8a9e71d666..340f131670a0 100644 --- a/lib/matplotlib/rcsetup.pyi +++ b/lib/matplotlib/rcsetup.pyi @@ -140,6 +140,7 @@ def _validate_linestyle(s: Any) -> LineStyleType: ... def validate_markeverylist(s: Any) -> list[MarkEveryType]: ... def validate_bbox(s: Any) -> Literal["tight", "standard"] | None: ... def validate_sketch(s: Any) -> None | tuple[float, float, float]: ... +def validate_sketch_seed(s: Any) -> int: ... def validate_hatch(s: Any) -> str: ... def validate_hatchlist(s: Any) -> list[str]: ... def validate_dashlist(s: Any) -> list[list[float]]: ... diff --git a/lib/matplotlib/tests/baseline_images/test_path/xkcd.png b/lib/matplotlib/tests/baseline_images/test_path/xkcd.png index fd486c42305f..f57d07aab8f2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_path/xkcd.png and b/lib/matplotlib/tests/baseline_images/test_path/xkcd.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_path/xkcd_marker.png b/lib/matplotlib/tests/baseline_images/test_path/xkcd_marker.png index c4224f74c1ec..a163a2254217 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_path/xkcd_marker.png and b/lib/matplotlib/tests/baseline_images/test_path/xkcd_marker.png differ diff --git a/lib/matplotlib/tests/test_backend_pgf.py b/lib/matplotlib/tests/test_backend_pgf.py index a866916c58c6..6e15265f95c0 100644 --- a/lib/matplotlib/tests/test_backend_pgf.py +++ b/lib/matplotlib/tests/test_backend_pgf.py @@ -384,7 +384,7 @@ def test_sketch_params(): ax.set_yticks([]) ax.set_frame_on(False) handle, = ax.plot([0, 1]) - handle.set_sketch_params(scale=5, length=30, randomness=42) + handle.set_sketch_params(scale=5, length=30, randomness=42, seed=0) with BytesIO() as fd: fig.savefig(fd, format='pgf') diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 0a1d6c6b5e52..56ec4ecd258d 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -8,10 +8,11 @@ from matplotlib import patches from matplotlib.path import Path from matplotlib.patches import Polygon -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, check_figures_equal import matplotlib.pyplot as plt from matplotlib import transforms from matplotlib.backend_bases import MouseEvent +from matplotlib import rcParams def test_empty_closed_path(): @@ -241,13 +242,23 @@ def test_make_compound_path_stops(): assert np.sum(compound_path.codes == Path.STOP) == 0 +def test_path_sketch_seed(): + # the default value of path.sketch_seed should be 0 + assert rcParams['path.sketch_seed'] == 0 + + +def test_xkcd_seed_update(): + # when passing a seed to xkcd, the global rcParam should be updated + with plt.xkcd(seed=2000): + assert rcParams['path.sketch_seed'] == 2000 + + @image_comparison(['xkcd.png'], remove_text=True) def test_xkcd(): np.random.seed(0) x = np.linspace(0, 2 * np.pi, 100) y = np.sin(x) - with plt.xkcd(): fig, ax = plt.subplots() ax.plot(x, y) @@ -261,7 +272,6 @@ def test_xkcd_marker(): y1 = x y2 = 5 - x y3 = 2.5 * np.ones(8) - with plt.xkcd(): fig, ax = plt.subplots() ax.plot(x, y1, '+', ms=10) @@ -269,6 +279,77 @@ def test_xkcd_marker(): ax.plot(x, y3, '^', ms=10) +@check_figures_equal(extensions=['png']) +def test_xkcd_override(fig_test, fig_ref): + x = np.linspace(0.7, 1.42, 100) + y = x ** 2 + + ln = fig_ref.add_subplot().plot(x, y) + ln_test = fig_test.add_subplot().plot(x, y) + + with plt.xkcd(): + ln[0].set_sketch_params(3, 120, 40, 420) + + # set_sketch should override seed set by xkcd + with plt.xkcd(seed=5885): + ln_test[0].set_sketch_params(3, 120, 40, 420) + + +@check_figures_equal(extensions=['png']) +def test_artist_seed(fig_test, fig_ref): + x = np.linspace(0.7, 1.42, 100) + y = [0.7]*100 + + ax_ref = fig_ref.add_subplot() + ax_test = fig_test.add_subplot() + + ln = ax_ref.plot(x, y) + ln_test = ax_test.plot(x, y) + + ln[0].set_sketch_params(3, 120, 40, 19680801) + + # set_sketch_params seed should override seed set by rcParam + # when seed is passed in set_sketch, it should be used + # else rcParam should be used as seed + rcParams['path.sketch_seed'] = 59856 + ln_test[0].set_sketch_params(3, 120, 40, 19680801) + + +@check_figures_equal(extensions=['png']) +def test_path_seed(fig_test, fig_ref): + x = x = np.linspace(0, 5, 200) + y = 20*np.sin(x) + + ax_ref = fig_ref.add_subplot() + ax_test = fig_test.add_subplot() + + rcParams['path.sketch_seed'] = 645 + rcParams['path.sketch'] = 3, 120, 40 + + ln = ax_ref.plot(x, y) + + ln_test = ax_test.plot(x, y) + ln_test[0].set_sketch_params(3, 120, 40, 645) + + +@check_figures_equal(extensions=['png']) +def test_xkcd_seed(fig_test, fig_ref): + x = x = np.linspace(0, 5, 200) + y = 20*np.sin(x) + + ax_ref = fig_ref.add_subplot() + ax_test = fig_test.add_subplot() + + with plt.xkcd(seed=20): + ln = ax_ref.plot(x, y) + ln[0].set_sketch_params(3, 120, 40) + + with plt.xkcd(seed=40): + rcParams['path.sketch_seed'] = 20 + ln_test = ax_test.plot(x, y) + ln_test[0].set_sketch_params(3, 120, 40) + + @image_comparison(['marker_paths.pdf'], remove_text=True) def test_marker_paths_pdf(): N = 7 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 61c4ed07d0d2..0a86139135c0 100644 --- a/src/_path.h +++ b/src/_path.h @@ -1051,7 +1051,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); } } @@ -1216,7 +1216,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_converters.h b/src/path_converters.h index 8583d84855aa..bf5d5a51aca8 100644 --- a/src/path_converters.h +++ b/src/path_converters.h @@ -991,6 +991,7 @@ class PathSimplifier : protected EmbeddedQueue<9> } }; + template class Sketch { @@ -1004,8 +1005,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 +1018,9 @@ class Sketch m_last_y(0.0), m_has_last(false), m_p(0.0), - m_rand(0) + m_rand(0), + m_seed(seed) { - rewind(0); 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 +1084,7 @@ class Sketch m_has_last = false; m_p = 0.0; if (m_scale != 0.0) { - m_rand.seed(0); + m_rand.seed(m_seed); m_segmented.rewind(path_id); } else { m_source->rewind(path_id); @@ -1101,6 +1104,7 @@ class Sketch RandomNumberGenerator m_rand; double m_p_scale; double m_log_randomness; + int m_seed; }; #endif // MPL_PATH_CONVERTERS_H diff --git a/src/py_converters.cpp b/src/py_converters.cpp index 04382c5f94d0..8dff06939d9a 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; }