From 59d79d29c011733dd1d234266f034ed1b0e1d173 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 28 Aug 2026 15:09:24 +0800 Subject: [PATCH 1/3] Figure.image: Add parameter fgcolor/bgcolor/transparent_color and deprecate bitcolor [Will be removed in 0.24.0] --- pygmt/helpers/caching.py | 1 + pygmt/src/image.py | 77 ++++++++++++++--- .../baseline/test_image_bgcolor_fgcolor.png | 3 + pygmt/tests/test_image.py | 85 ++++++++++++++++++- 4 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 pygmt/tests/baseline/test_image_bgcolor_fgcolor.png diff --git a/pygmt/helpers/caching.py b/pygmt/helpers/caching.py index e315a0eddcc..5012f91b967 100644 --- a/pygmt/helpers/caching.py +++ b/pygmt/helpers/caching.py @@ -134,5 +134,6 @@ def cache_data() -> None: "@tut_quakes.ngdc", "@tut_ship.xyz", "@usgs_quakes_22.txt", + "@vader1.png", ] which(fname=datasets, download="auto") diff --git a/pygmt/src/image.py b/pygmt/src/image.py index 1c0ceb9b7f2..87c5b9c0241 100644 --- a/pygmt/src/image.py +++ b/pygmt/src/image.py @@ -2,19 +2,20 @@ image - Plot raster or EPS images. """ +import warnings from collections.abc import Sequence from typing import Literal from pygmt._typing import AnchorCode, PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.helpers import build_arg_list, fmt_docstring, use_alias +from pygmt.exceptions import GMTParameterError, GMTValueError +from pygmt.helpers import build_arg_list, fmt_docstring from pygmt.params import Axis, Box, Frame, Position from pygmt.src._common import _parse_position @fmt_docstring -@use_alias(G="bitcolor") def image( self, imagefile: PathLike, @@ -26,6 +27,10 @@ def image( box: Box | bool = False, monochrome: bool = False, invert: bool = False, + bgcolor: str | None = None, + fgcolor: str | None = None, + transparent_color: str | None = None, + bitcolor: str | Sequence[str] | None = None, projection: str | None = None, region: Sequence[float | str] | str | None = None, frame: Frame | Axis | Literal["none"] | str | Sequence[str] | bool = False, @@ -54,10 +59,15 @@ def image( Full GMT docs at :gmt-docs:`image.html`. - $aliases + **Aliases:** + + .. hlist:: + :columns: 3 + - B = frame - D = position, **+w**: width/height, **+r**: dpi, **+n**: replicate - F = box + - G = bgcolor, fgcolor, transparent_color, bitcolor - I = invert - J = projection - M = monochrome @@ -74,7 +84,7 @@ def image( contain an appropriate BoundingBox. A raster file can have a depth of 1, 8, 24, or 32 bits and is read via GDAL. position - Position of the GMT logo on the plot. It can be specified in multiple ways: + Position of the image on the plot. It can be specified in multiple ways: - A :class:`pygmt.params.Position` object to fully control the reference point, anchor point, and offset. @@ -106,14 +116,18 @@ def image( box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box appearance, pass a :class:`pygmt.params.Box` object to control style, fill, pen, and other box properties. - bitcolor : str or list - [*color*][**+b**\|\ **f**\|\ **t**]. - Change certain pixel values to another color or make them transparent. - For 1-bit images you can specify an alternate *color* for the - background (**+b**) or the foreground (**+f**) pixels, or give no color - to make those pixels transparent. Can be repeated with different - settings. Alternatively, for color images you can select a single - *color* that should be made transparent instead (**+t**). + bgcolor + fgcolor + For 1-bit images, set the background and foreground colors [Default is black and + white, respectively]. Setting either to an empty string makes those pixels + transparent. Cannot be both empty. + transparent_color + For color images, set a single color that should be made transparent. + bitcolor + .. deprecated:: 0.20.0 + + Use ``bgcolor``, ``fgcolor``, or ``transparent_color`` instead. Will be + removed in 0.24.0. monochrome Convert color image to monochrome grayshades using the (television) YIQ-transformation. @@ -142,6 +156,38 @@ def image( if width is None and height is not None: width = 0 + # TODO(PyGMT>=0.24.0): Remove the deprecated "bitcolor" parameter. + if bitcolor is not None: + msg = ( + "The 'bitcolor' parameter has been deprecated since v0.20.0 and will be " + "removed in v0.24.0. Use 'bgcolor', 'fgcolor' or 'transparent_color' " + "instead." + ) + warnings.warn(msg, category=FutureWarning, stacklevel=2) + if any(v is not None for v in [bgcolor, fgcolor, transparent_color]): + raise GMTParameterError( + conflicts_with=( + "bitcolor", + ["bgcolor", "fgcolor", "transparent_color"], + ), + ) + + # 'bgcolor' and 'fgcolor' cannot both be empty. + if bgcolor == "" and fgcolor == "": + _value = f"{bgcolor=}, {fgcolor=}" + raise GMTValueError( + _value, + description="bgcolor and fgcolor", + reason="'bgcolor' and 'fgcolor' cannot both be empty.", + ) + # GMT requires a color for the "+t" modifier. + if transparent_color == "": + raise GMTValueError( + transparent_color, + description="value for 'transparent_color'", + reason="'transparent_color' cannot be empty.", + ) + aliasdict = AliasSystem( D=[ Alias(position, name="position"), @@ -151,6 +197,13 @@ def image( Alias(dpi, name="dpi", prefix="+r"), ], F=Alias(box, name="box"), + G=[ + Alias(bgcolor, name="bgcolor", suffix="+b"), + Alias(fgcolor, name="fgcolor", suffix="+f"), + Alias(transparent_color, name="transparent_color", suffix="+t"), + ] + if bitcolor is None + else Alias(bitcolor, name="bitcolor"), M=Alias(monochrome, name="monochrome"), I=Alias(invert, name="invert"), ).add_common( diff --git a/pygmt/tests/baseline/test_image_bgcolor_fgcolor.png b/pygmt/tests/baseline/test_image_bgcolor_fgcolor.png new file mode 100644 index 00000000000..2a8ee77d946 --- /dev/null +++ b/pygmt/tests/baseline/test_image_bgcolor_fgcolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91a95f6c2df43150189839ce85716cf84d680ae7dc4f57342971b98f93ebd72b +size 12315 diff --git a/pygmt/tests/test_image.py b/pygmt/tests/test_image.py index 69ab14585ec..ad60e4cc993 100644 --- a/pygmt/tests/test_image.py +++ b/pygmt/tests/test_image.py @@ -4,8 +4,8 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTParameterError -from pygmt.params import Box, Position +from pygmt.exceptions import GMTParameterError, GMTValueError +from pygmt.params import Box, Frame, Position @pytest.mark.mpl_image_compare @@ -74,3 +74,84 @@ def test_image_position_mixed_syntax(): fig.image(imagefile="@circuit.png", position="x0/0", dpi="300") with pytest.raises(GMTParameterError): fig.image(imagefile="@circuit.png", position="x0/0", replicate=(2, 1)) + + +@pytest.mark.mpl_image_compare +def test_image_bgcolor_fgcolor(): + """ + Test setting the background and foreground colors of a 1-bit image. + """ + fig = Figure() + fig.basemap(region=[-1, 8, 0, 5], projection="X10c/5c", frame=Frame(fill="gray")) + fig.image(imagefile="@vader1.png", position=(0, 0), width="2c") + fig.image(imagefile="@vader1.png", position=(2, 0), width="2c", bgcolor="") + fig.image(imagefile="@vader1.png", position=(4, 0), width="2c", fgcolor="") + fig.image(imagefile="@vader1.png", position=(6, 0), width="2c", bgcolor="red") + fig.image(imagefile="@vader1.png", position=(0, 2), width="2c", fgcolor="blue") + fig.image( + imagefile="@vader1.png", + position=(2, 2), + width="2c", + bgcolor="red", + fgcolor="blue", + ) + fig.image( + imagefile="@vader1.png", + position=(4, 2), + width="2c", + bgcolor="red", + fgcolor="", + ) + fig.image( + imagefile="@vader1.png", + position=(6, 2), + width="2c", + bgcolor="", + fgcolor="blue", + ) + return fig + + +def test_image_bitcolor_invalid(): + """ + Test that invalid 'bgcolor'/'fgcolor'/'transparent_color' values raise an error. + """ + fig = Figure() + # Making both the background and the foreground transparent leaves nothing to paint. + with pytest.raises(GMTValueError): + fig.image(imagefile="@circuit.png", bgcolor="", fgcolor="") + # GMT requires a color for the "+t" modifier. + with pytest.raises(GMTValueError): + fig.image(imagefile="@circuit.png", transparent_color="") + + +# TODO(PyGMT>=0.24.0): Remove the test for the deprecated "bitcolor" parameter. +def test_image_bitcolor_deprecated(): + """ + Test that the deprecated 'bitcolor' parameter still works but warns. + """ + fig = Figure() + with pytest.warns(FutureWarning): + fig.image(imagefile="@circuit.png", bitcolor="red+b") + with pytest.warns(FutureWarning): + fig.image(imagefile="@circuit.png", bitcolor=["red+b", "blue+f"]) + + +# TODO(PyGMT>=0.24.0): Remove the test for the deprecated "bitcolor" parameter. +def test_image_bitcolor_conflict(): + """ + Test that the deprecated 'bitcolor' parameter raises an error when used with + 'bgcolor', 'fgcolor', or 'transparent_color'. + """ + fig = Figure() + with pytest.raises(GMTParameterError): + with pytest.warns(FutureWarning): + fig.image(imagefile="@circuit.png", bitcolor="red+b", bgcolor="blue") + with pytest.raises(GMTParameterError): + with pytest.warns(FutureWarning): + fig.image(imagefile="@circuit.png", bitcolor="red+b", fgcolor="blue") + with pytest.raises(GMTParameterError): + with pytest.warns(FutureWarning): + fig.image( + imagefile="@circuit.png", bitcolor="red+b", transparent_color="blue" + ) From b914f0b045a8d33239c19642dd5417a54b0c64cf Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 30 Aug 2026 15:12:41 +0800 Subject: [PATCH 2/3] Improve tests --- pygmt/tests/baseline/test_image_bgcolor_fgcolor.png | 4 ++-- pygmt/tests/test_image.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/tests/baseline/test_image_bgcolor_fgcolor.png b/pygmt/tests/baseline/test_image_bgcolor_fgcolor.png index 2a8ee77d946..d67fdc32063 100644 --- a/pygmt/tests/baseline/test_image_bgcolor_fgcolor.png +++ b/pygmt/tests/baseline/test_image_bgcolor_fgcolor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91a95f6c2df43150189839ce85716cf84d680ae7dc4f57342971b98f93ebd72b -size 12315 +oid sha256:3e1b507a66f028b254969b848d1db63fd7ae0755bd50c281f19747bc398c9a91 +size 10821 diff --git a/pygmt/tests/test_image.py b/pygmt/tests/test_image.py index ad60e4cc993..37f3c8b6ebe 100644 --- a/pygmt/tests/test_image.py +++ b/pygmt/tests/test_image.py @@ -82,7 +82,7 @@ def test_image_bgcolor_fgcolor(): Test setting the background and foreground colors of a 1-bit image. """ fig = Figure() - fig.basemap(region=[-1, 8, 0, 5], projection="X10c/5c", frame=Frame(fill="gray")) + fig.basemap(region=[0, 8, 0, 4], projection="x1c/1c", frame=Frame(fill="gray")) fig.image(imagefile="@vader1.png", position=(0, 0), width="2c") fig.image(imagefile="@vader1.png", position=(2, 0), width="2c", bgcolor="") fig.image(imagefile="@vader1.png", position=(4, 0), width="2c", fgcolor="") From 4fdda48e8f598bcc7adfabffa109a41efbb8e33d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 30 Aug 2026 15:18:24 +0800 Subject: [PATCH 3/3] Add the original docstrings of bitcolor back --- pygmt/src/image.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pygmt/src/image.py b/pygmt/src/image.py index 87c5b9c0241..2f15a75f07f 100644 --- a/pygmt/src/image.py +++ b/pygmt/src/image.py @@ -124,6 +124,13 @@ def image( transparent_color For color images, set a single color that should be made transparent. bitcolor + [*color*][**+b**\|\ **f**\|\ **t**]. + Change certain pixel values to another color or make them transparent. For 1-bit + images you can specify an alternate *color* for the background (**+b**) or the + foreground (**+f**) pixels, or give no color to make those pixels transparent. + Alternatively, for color images you can select a single *color* that should be + made transparent instead (**+t**). Pass a list of to specify multiple settings. + .. deprecated:: 0.20.0 Use ``bgcolor``, ``fgcolor``, or ``transparent_color`` instead. Will be