diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 9f3efd283906..a7cdd288b1bb 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -32,22 +32,127 @@ matplotlib.ticker.LogitLocator.nonsingular matplotlib.backend_bases._Mode.__new__ matplotlib.units.Number.__hash__ +# Property read-write vs read-only weirdness, fix if possible +matplotlib.offsetbox.DraggableBase.canvas +matplotlib.offsetbox.DraggableBase.cids +matplotlib.transforms.BboxTransform.is_separable +matplotlib.transforms.BboxTransformFrom.is_separable +matplotlib.transforms.BboxTransformTo.is_separable +matplotlib.transforms.BlendedAffine2D.is_separable +matplotlib.transforms.CompositeGenericTransform.is_separable +matplotlib.transforms.TransformWrapper.input_dims +matplotlib.transforms.TransformWrapper.is_separable +matplotlib.transforms.TransformWrapper.output_dims + # 3.6 Pending deprecations matplotlib.figure.Figure.set_constrained_layout matplotlib.figure.Figure.set_constrained_layout_pads matplotlib.figure.Figure.set_tight_layout -# 3.7 deprecations -matplotlib.cm.register_cmap -matplotlib.cm.unregister_cmap +# 3.8 deprecations +matplotlib.cbook.get_sample_data +matplotlib.contour.ContourSet.allkinds +matplotlib.contour.ContourSet.allsegs +matplotlib.contour.ContourSet.tcolors +matplotlib.contour.ContourSet.tlinewidths +matplotlib.ticker.LogLocator.__init__ +matplotlib.ticker.LogLocator.set_params # positional-only argument name lacking leading underscores matplotlib.axes._base._AxesBase.axis +# Aliases (dynamically generated, not type hinted) +matplotlib.collections.Collection.get_aa +matplotlib.collections.Collection.get_antialiaseds +matplotlib.collections.Collection.get_dashes +matplotlib.collections.Collection.get_ec +matplotlib.collections.Collection.get_edgecolors +matplotlib.collections.Collection.get_facecolors +matplotlib.collections.Collection.get_fc +matplotlib.collections.Collection.get_linestyles +matplotlib.collections.Collection.get_linewidths +matplotlib.collections.Collection.get_ls +matplotlib.collections.Collection.get_lw +matplotlib.collections.Collection.get_transOffset +matplotlib.collections.Collection.set_aa +matplotlib.collections.Collection.set_antialiaseds +matplotlib.collections.Collection.set_dashes +matplotlib.collections.Collection.set_ec +matplotlib.collections.Collection.set_edgecolors +matplotlib.collections.Collection.set_facecolors +matplotlib.collections.Collection.set_fc +matplotlib.collections.Collection.set_linestyles +matplotlib.collections.Collection.set_linewidths +matplotlib.collections.Collection.set_ls +matplotlib.collections.Collection.set_lw +matplotlib.collections.Collection.set_transOffset +matplotlib.lines.Line2D.get_aa +matplotlib.lines.Line2D.get_c +matplotlib.lines.Line2D.get_ds +matplotlib.lines.Line2D.get_ls +matplotlib.lines.Line2D.get_lw +matplotlib.lines.Line2D.get_mec +matplotlib.lines.Line2D.get_mew +matplotlib.lines.Line2D.get_mfc +matplotlib.lines.Line2D.get_mfcalt +matplotlib.lines.Line2D.get_ms +matplotlib.lines.Line2D.set_aa +matplotlib.lines.Line2D.set_c +matplotlib.lines.Line2D.set_ds +matplotlib.lines.Line2D.set_ls +matplotlib.lines.Line2D.set_lw +matplotlib.lines.Line2D.set_mec +matplotlib.lines.Line2D.set_mew +matplotlib.lines.Line2D.set_mfc +matplotlib.lines.Line2D.set_mfcalt +matplotlib.lines.Line2D.set_ms +matplotlib.patches.Patch.get_aa +matplotlib.patches.Patch.get_ec +matplotlib.patches.Patch.get_fc +matplotlib.patches.Patch.get_ls +matplotlib.patches.Patch.get_lw +matplotlib.patches.Patch.set_aa +matplotlib.patches.Patch.set_ec +matplotlib.patches.Patch.set_fc +matplotlib.patches.Patch.set_ls +matplotlib.patches.Patch.set_lw +matplotlib.text.Text.get_c +matplotlib.text.Text.get_family +matplotlib.text.Text.get_font +matplotlib.text.Text.get_font_properties +matplotlib.text.Text.get_ha +matplotlib.text.Text.get_name +matplotlib.text.Text.get_size +matplotlib.text.Text.get_style +matplotlib.text.Text.get_va +matplotlib.text.Text.get_variant +matplotlib.text.Text.get_weight +matplotlib.text.Text.set_c +matplotlib.text.Text.set_family +matplotlib.text.Text.set_font +matplotlib.text.Text.set_font_properties +matplotlib.text.Text.set_ha +matplotlib.text.Text.set_ma +matplotlib.text.Text.set_name +matplotlib.text.Text.set_size +matplotlib.text.Text.set_stretch +matplotlib.text.Text.set_style +matplotlib.text.Text.set_va +matplotlib.text.Text.set_variant +matplotlib.text.Text.set_weight +matplotlib.axes._base._AxesBase.get_fc +matplotlib.axes._base._AxesBase.set_fc + +# Other dynamic python behaviors not type hinted +matplotlib.rcsetup.defaultParams + # Maybe should be abstractmethods, required for subclasses, stubs define once matplotlib.tri.*TriInterpolator.__call__ matplotlib.tri.*TriInterpolator.gradient +# Functionally a method call, but actually a class instance, type hinted as former +matplotlib.rcsetup.validate_fillstyle + # TypeVar used only in type hints matplotlib.backend_bases.FigureCanvasBase._T matplotlib.backend_managers.ToolManager._T diff --git a/doc/api/next_api_changes/removals/26965-ER.rst b/doc/api/next_api_changes/removals/26965-ER.rst new file mode 100644 index 000000000000..f815d0b2f45c --- /dev/null +++ b/doc/api/next_api_changes/removals/26965-ER.rst @@ -0,0 +1,27 @@ +As part of a `multi-step process +`_ we are refactoring +the global state for managing the registered colormaps. + +In Matplotlib 3.5 we added a `.ColormapRegistry` class and exposed an instance +at the top level as ``matplotlib.colormaps``. The existing top level functions +in `matplotlib.cm` (``get_cmap``, ``register_cmap``, ``unregister_cmap``) were +changed to be aliases around the same instance. + +In Matplotlib 3.7 those top level functions were deprecated. The following +functions have been removed: + +- ``matplotlib.cm.get_cmap``; use ``matplotlib.colormaps[name]`` instead if you + have a `str`. + + Use `matplotlib.cm.ColormapRegistry.get_cmap` if you + have a string, `None` or a `matplotlib.colors.Colormap` object that you want + to convert to a `matplotlib.colors.Colormap` instance. +- ``matplotlib.cm.register_cmap``; use `matplotlib.colormaps.register + <.ColormapRegistry.register>` instead +- ``matplotlib.cm.unregister_cmap``; use `matplotlib.colormaps.unregister + <.ColormapRegistry.unregister>` instead +- ``matplotlib.pyplot.register_cmap``; use `matplotlib.colormaps.register + <.ColormapRegistry.register>` instead + +The `matplotlib.pyplot.get_cmap` function will stay available for backward +compatibility. diff --git a/doc/api/prev_api_changes/api_changes_0.99.rst b/doc/api/prev_api_changes/api_changes_0.99.rst index 5d544eaec7f5..e03face0d075 100644 --- a/doc/api/prev_api_changes/api_changes_0.99.rst +++ b/doc/api/prev_api_changes/api_changes_0.99.rst @@ -7,7 +7,7 @@ Changes in 0.99 NumPy arrays. * User-generated colormaps can now be added to the set recognized - by :func:`matplotlib.cm.get_cmap`. Colormaps can be made the + by ``matplotlib.cm.get_cmap``. Colormaps can be made the default and applied to the current image using :func:`matplotlib.pyplot.set_cmap`. diff --git a/doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst b/doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst index 407494bdb53f..cb711b8373f9 100644 --- a/doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst +++ b/doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst @@ -294,7 +294,7 @@ Exception changes ~~~~~~~~~~~~~~~~~ Various APIs that raised a `ValueError` for incorrectly typed inputs now raise `TypeError` instead: `.backend_bases.GraphicsContextBase.set_clip_path`, -``blocking_input.BlockingInput.__call__``, `.cm.register_cmap`, `.dviread.DviFont`, +``blocking_input.BlockingInput.__call__``, ``matplotlib.cm.register_cmap``, `.dviread.DviFont`, `.rcsetup.validate_hatch`, ``.rcsetup.validate_animation_writer_path``, `.spines.Spine`, many classes in the :mod:`matplotlib.transforms` module and :mod:`matplotlib.tri` package, and Axes methods that take a ``norm`` parameter. diff --git a/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst b/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst index d9d79adcfbd5..256c33ed762f 100644 --- a/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst +++ b/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst @@ -55,7 +55,7 @@ Please pass capstyles ("miter", "round", "bevel") and joinstyles ("butt", Passing raw data to ``register_cmap()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Passing raw data via parameters *data* and *lut* to `.register_cmap()` is +Passing raw data via parameters *data* and *lut* to ``matplotlib.cm.register_cmap()`` is deprecated. Instead, explicitly create a `.LinearSegmentedColormap` and pass it via the *cmap* parameter: ``register_cmap(cmap=LinearSegmentedColormap(name, data, lut))``. diff --git a/doc/api/prev_api_changes/api_changes_3.4.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.4.0/behaviour.rst index df08097dba03..e35301c11986 100644 --- a/doc/api/prev_api_changes/api_changes_3.4.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.4.0/behaviour.rst @@ -203,7 +203,7 @@ time, not at draw time. Raise or warn on registering a colormap twice ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When using `matplotlib.cm.register_cmap` to register a user provided or +When using ``matplotlib.cm.register_cmap`` to register a user provided or third-party colormap it will now raise a `ValueError` if trying to over-write one of the built in colormaps and warn if trying to over write a user registered colormap. This may raise for user-registered colormaps in the diff --git a/doc/api/prev_api_changes/api_changes_3.6.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.6.0/behaviour.rst index a35584b04961..b54cf315f602 100644 --- a/doc/api/prev_api_changes/api_changes_3.6.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.6.0/behaviour.rst @@ -4,7 +4,7 @@ Behaviour changes ``plt.get_cmap`` and ``matplotlib.cm.get_cmap`` return a copy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Formerly, `~.pyplot.get_cmap` and `.cm.get_cmap` returned a global version of a +Formerly, `~.pyplot.get_cmap` and ``matplotlib.cm.get_cmap`` returned a global version of a `.Colormap`. This was prone to errors as modification of the colormap would propagate from one location to another without warning. Now, a new copy of the colormap is returned. diff --git a/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst index a4fd8b57e419..e8d7bee3ab41 100644 --- a/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst @@ -20,7 +20,7 @@ also be based on ``mpl_toolkits.axisartist``. This behavior is consistent with ``plt.get_cmap`` and ``matplotlib.cm.get_cmap`` return a copy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Formerly, `~.pyplot.get_cmap` and `.cm.get_cmap` returned a global version of a +Formerly, `~.pyplot.get_cmap` and ``matplotlib.cm.get_cmap`` returned a global version of a `.Colormap`. This was prone to errors as modification of the colormap would propagate from one location to another without warning. Now, a new copy of the colormap is returned. diff --git a/doc/users/prev_whats_new/whats_new_3.4.0.rst b/doc/users/prev_whats_new/whats_new_3.4.0.rst index e26a7c70d253..003cd85fa49d 100644 --- a/doc/users/prev_whats_new/whats_new_3.4.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.4.0.rst @@ -547,7 +547,7 @@ for out-of-range and masked values. New ``cm.unregister_cmap`` function ----------------------------------- -`.cm.unregister_cmap` allows users to remove a colormap that they have +``matplotlib.cm.unregister_cmap`` allows users to remove a colormap that they have previously registered. New ``CenteredNorm`` for symmetrical data around a center diff --git a/doc/users/prev_whats_new/whats_new_3.5.0.rst b/doc/users/prev_whats_new/whats_new_3.5.0.rst index 5a55cff17d02..e67573702218 100644 --- a/doc/users/prev_whats_new/whats_new_3.5.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.5.0.rst @@ -148,9 +148,9 @@ To register new colormaps use:: plt.colormaps.register(my_colormap) -We recommend to use the new API instead of the `~.cm.get_cmap` and -`~.cm.register_cmap` functions for new code. `matplotlib.cm.get_cmap` and -`matplotlib.cm.register_cmap` will eventually be deprecated and removed. +We recommend to use the new API instead of the ``matplotlib.cm.get_cmap`` and +``matplotlib.cm.register_cmap`` functions for new code. ``matplotlib.cm.get_cmap`` and +``matplotlib.cm.register_cmap`` will eventually be deprecated and removed. Within `.pyplot`, ``plt.get_cmap()`` and ``plt.register_cmap()`` will continue to be supported for backward compatibility. diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index 53c60c8a8883..399a61191353 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -83,8 +83,6 @@ class ColormapRegistry(Mapping): def __init__(self, cmaps): self._cmaps = cmaps self._builtin_cmaps = tuple(cmaps) - # A shim to allow register_cmap() to force an override - self._allow_override_builtin = False def __getitem__(self, item): try: @@ -146,10 +144,8 @@ def register(self, cmap, *, name=None, force=False): # unless explicitly asked to raise ValueError( f'A colormap named "{name}" is already registered.') - elif (name in self._builtin_cmaps - and not self._allow_override_builtin): - # We don't allow overriding a builtin unless privately - # coming from register_cmap() + elif name in self._builtin_cmaps: + # We don't allow overriding a builtin raise ValueError("Re-registering the builtin cmap " f"{name!r} is not allowed.") @@ -229,136 +225,6 @@ def get_cmap(self, cmap): ) -# public access to the colormaps should be via `matplotlib.colormaps`. For now, -# we still create the registry here, but that should stay an implementation -# detail. -_colormaps = ColormapRegistry(_gen_cmap_registry()) -globals().update(_colormaps) - - -@_api.deprecated("3.7", alternative="``matplotlib.colormaps.register(name)``") -def register_cmap(name=None, cmap=None, *, override_builtin=False): - """ - Add a colormap to the set recognized by :func:`get_cmap`. - - Register a new colormap to be accessed by name :: - - LinearSegmentedColormap('swirly', data, lut) - register_cmap(cmap=swirly_cmap) - - Parameters - ---------- - name : str, optional - The name that can be used in :func:`get_cmap` or :rc:`image.cmap` - - If absent, the name will be the :attr:`~matplotlib.colors.Colormap.name` - attribute of the *cmap*. - - cmap : matplotlib.colors.Colormap - Despite being the second argument and having a default value, this - is a required argument. - - override_builtin : bool - - Allow built-in colormaps to be overridden by a user-supplied - colormap. - - Please do not use this unless you are sure you need it. - """ - _api.check_isinstance((str, None), name=name) - if name is None: - try: - name = cmap.name - except AttributeError as err: - raise ValueError("Arguments must include a name or a " - "Colormap") from err - # override_builtin is allowed here for backward compatibility - # this is just a shim to enable that to work privately in - # the global ColormapRegistry - _colormaps._allow_override_builtin = override_builtin - _colormaps.register(cmap, name=name, force=override_builtin) - _colormaps._allow_override_builtin = False - - -def _get_cmap(name=None, lut=None): - """ - Get a colormap instance, defaulting to rc values if *name* is None. - - Parameters - ---------- - name : `~matplotlib.colors.Colormap` or str or None, default: None - If a `.Colormap` instance, it will be returned. Otherwise, the name of - a colormap known to Matplotlib, which will be resampled by *lut*. The - default, None, means :rc:`image.cmap`. - lut : int or None, default: None - If *name* is not already a Colormap instance and *lut* is not None, the - colormap will be resampled to have *lut* entries in the lookup table. - - Returns - ------- - Colormap - """ - if name is None: - name = mpl.rcParams['image.cmap'] - if isinstance(name, colors.Colormap): - return name - _api.check_in_list(sorted(_colormaps), name=name) - if lut is None: - return _colormaps[name] - else: - return _colormaps[name].resampled(lut) - -# do it in two steps like this so we can have an un-deprecated version in -# pyplot. -get_cmap = _api.deprecated( - '3.7', - name='get_cmap', - alternative=( - "``matplotlib.colormaps[name]`` " + - "or ``matplotlib.colormaps.get_cmap(obj)``" - ) -)(_get_cmap) - - -@_api.deprecated("3.7", - alternative="``matplotlib.colormaps.unregister(name)``") -def unregister_cmap(name): - """ - Remove a colormap recognized by :func:`get_cmap`. - - You may not remove built-in colormaps. - - If the named colormap is not registered, returns with no error, raises - if you try to de-register a default colormap. - - .. warning:: - - Colormap names are currently a shared namespace that may be used - by multiple packages. Use `unregister_cmap` only if you know you - have registered that name before. In particular, do not - unregister just in case to clean the name before registering a - new colormap. - - Parameters - ---------- - name : str - The name of the colormap to be un-registered - - Returns - ------- - ColorMap or None - If the colormap was registered, return it if not return `None` - - Raises - ------ - ValueError - If you try to de-register a default built-in colormap. - """ - cmap = _colormaps.get(name, None) - _colormaps.unregister(name) - return cmap - - def _auto_norm_from_scale(scale_cls): """ Automatically generate a norm class from *scale_cls*. diff --git a/lib/matplotlib/cm.pyi b/lib/matplotlib/cm.pyi index be8f10b39cb6..da78d940ba4a 100644 --- a/lib/matplotlib/cm.pyi +++ b/lib/matplotlib/cm.pyi @@ -19,8 +19,6 @@ class ColormapRegistry(Mapping[str, colors.Colormap]): _colormaps: ColormapRegistry = ... -def get_cmap(name: str | colors.Colormap | None = ..., lut: int | None = ...) -> colors.Colormap: ... - class ScalarMappable: cmap: colors.Colormap | None colorbar: Colorbar | None diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index dc59bc809638..53da581fb3bb 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -74,7 +74,6 @@ from matplotlib.scale import get_scale_names # noqa: F401 from matplotlib.cm import _colormaps -from matplotlib.cm import register_cmap # type: ignore # noqa: F401 from matplotlib.colors import _color_sequences import numpy as np @@ -143,6 +142,10 @@ _log = logging.getLogger(__name__) +_colormaps = ColormapRegistry(_gen_cmap_registry()) +globals().update(_colormaps) + + # Explicit rename instead of import-as for typing's sake. colormaps = _colormaps color_sequences = _color_sequences @@ -2368,8 +2371,32 @@ def get_cmap( name: Colormap | str | None = None, lut: int | None = None ) -> Colormap: - return cm._get_cmap(name=name, lut=lut) # type: ignore -get_cmap.__doc__ = cm._get_cmap.__doc__ # type: ignore + """ + Get a colormap instance, defaulting to rc values if *name* is None. + + Parameters + ---------- + name : `~matplotlib.colors.Colormap` or str or None, default: None + If a `.Colormap` instance, it will be returned. Otherwise, the name of + a colormap known to Matplotlib, which will be resampled by *lut*. The + default, None, means :rc:`image.cmap`. + lut : int or None, default: None + If *name* is not already a Colormap instance and *lut* is not None, the + colormap will be resampled to have *lut* entries in the lookup table. + + Returns + ------- + Colormap + """ + if name is None: + name = mpl.rcParams['image.cmap'] + if isinstance(name, colors.Colormap): + return name + _api.check_in_list(sorted(_colormaps), name=name) + if lut is None: + return _colormaps[name] + else: + return _colormaps[name].resampled(lut) def set_cmap(cmap: Colormap | str) -> None: diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 139efbe17407..312a324e400f 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -72,51 +72,6 @@ def test_resampled(): assert_array_almost_equal(lc(np.nan), lc3(np.nan)) -def test_register_cmap(): - new_cm = mpl.colormaps["viridis"] - target = "viridis2" - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.register\(name\)" - ): - cm.register_cmap(target, new_cm) - assert mpl.colormaps[target] == new_cm - - with pytest.raises(ValueError, - match="Arguments must include a name or a Colormap"): - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.register\(name\)" - ): - cm.register_cmap() - - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.unregister\(name\)" - ): - cm.unregister_cmap(target) - with pytest.raises(ValueError, - match=f'{target!r} is not a valid value for name;'): - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\[name\]" - ): - cm.get_cmap(target) - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.unregister\(name\)" - ): - # test that second time is error free - cm.unregister_cmap(target) - - with pytest.raises(TypeError, match="'cmap' must be"): - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.register\(name\)" - ): - cm.register_cmap('nome', cmap='not a cmap') - - def test_colormaps_get_cmap(): cr = mpl.colormaps @@ -144,20 +99,6 @@ def test_double_register_builtin_cmap(): matplotlib.colormaps.register( mpl.colormaps[name], name=name, force=True ) - with pytest.raises(ValueError, match='A colormap named "viridis"'): - with pytest.warns(mpl.MatplotlibDeprecationWarning): - cm.register_cmap(name, mpl.colormaps[name]) - with pytest.warns(UserWarning): - # TODO is warning more than once! - cm.register_cmap(name, mpl.colormaps[name], override_builtin=True) - - -def test_unregister_builtin_cmap(): - name = "viridis" - match = f'cannot unregister {name!r} which is a builtin colormap.' - with pytest.raises(ValueError, match=match): - with pytest.warns(mpl.MatplotlibDeprecationWarning): - cm.unregister_cmap(name) def test_colormap_copy(): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy