Skip to content

Commit

Permalink
Added the alpha command to set the opacity of a layer (#447)
Browse files Browse the repository at this point in the history
There was no way to adjust a layer's opacity without providing the base colour, which may have been set already. This PR introduce the `alpha` command, which does exactly that. If no colour is defined for the layer, it is set to black.

Also:
- added `vpype_cli.FloatRangeType`
- fixed typo in `vpype_cli.IntRangeType` docstring
- fixed misnamed `vpype.Color.as_rgb_hex()` member name
- added test for `alpha`
- updated doc and readme
- Fixed error in `test_read_command_page_size()` on Windows introduced in #446
  • Loading branch information
abey79 authored Apr 2, 2022
1 parent 2d50a1d commit cd95e2d
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 8 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Release date: UNRELEASED

### New features and improvements

* Added the `alpha` command to set layer opacity without changing the base color (#447)
* Added HPGL configuration for the Calcomp Artisan plotter (thanks to Andee Collard and @ithinkido) (#418)
* Added the `--dont-set-date` option to the `write` command (#442)
* The `read` command now better handles SVGs with missing `width` or `height` attributes (#446)
Expand All @@ -23,7 +24,7 @@ Release date: UNRELEASED

### API changes

* Added `vpype_cli.FloatType()`, `vpype_cli.IntRangeType()`, and `vpype_cli.ChoiceType()` (#430)
* Added `vpype_cli.FloatType()`, `vpype_cli.IntRangeType()`, `vpype_cli.FloatRangeType()`, and `vpype_cli.ChoiceType()` (#430, #447)
* Changed `vpype.Document.add_to_sources()` to also modify the `vp_source` property (#431)
* Added a `set_date:bool = True` argument to `vpype.write_svg()` (#442)
* Changed the default value of `default_width` and `default_height` arguments of `vpype.read_svg()` (and friends) to `None` to allow `svgelement` better handle missing `width`/`height` attributes (#446)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ and much more.

#### Metadata

- Adjust layer **color**, **pen width** and **name** ([`color`](https://vpype.readthedocs.io/en/latest/reference.html#color), [`penwidth`](https://vpype.readthedocs.io/en/latest/reference.html#penwidth), [`name`](https://vpype.readthedocs.io/en/latest/reference.html#name)).
- Adjust layer **color**, **alpha**, **pen width** and **name** ([`color`](https://vpype.readthedocs.io/en/latest/reference.html#color), [`alpha`](https://vpype.readthedocs.io/en/latest/reference.html#alpha), [`penwidth`](https://vpype.readthedocs.io/en/latest/reference.html#penwidth), [`name`](https://vpype.readthedocs.io/en/latest/reference.html#name)).
- Apply provided or fully customisable **pen configurations** ([`pens`](https://vpype.readthedocs.io/en/latest/reference.html#pens)).
- Manipulate global and per-layer **properties** ([`propset`](https://vpype.readthedocs.io/en/latest/reference.html#propset), [`propget`](https://vpype.readthedocs.io/en/latest/reference.html#propget), [`proplist`](https://vpype.readthedocs.io/en/latest/reference.html#proplist), [`propdel`](https://vpype.readthedocs.io/en/latest/reference.html#propdel), [`propclear`](https://vpype.readthedocs.io/en/latest/reference.html#propclear)).

Expand Down
2 changes: 1 addition & 1 deletion docs/fundamentals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Although there is in general no constraint on the number, name, and type of prop
* ``vp_source`` (:class:`pathlib.Path`): the input file from which the geometries are created (global and/or layer property)
* ``vp_sources`` (:class:`set` of :class:`pathlib.Path`): list of all input files from which geometries are created (global property)

Many commands act on these properties. For example, the :ref:`cmd_read` command sets these properties according to the imported SVG file's content. The :ref:`cmd_color`, :ref:`cmd_penwidth`, :ref:`cmd_name`, and :ref:`cmd_pens` commands can set these properties to arbitrary values. In particular, the :ref:`cmd_pens` commands can apply a predefined set of values on multiple layers at once, for example to apply a CMYK color scheme (see :ref:`faq_custom_pen_config` for more information). The page size global property is set by the :ref:`cmd_pagesize` and :ref:`cmd_layout` commands, and used by the :ref:`cmd_write` command.
Many commands act on these properties. For example, the :ref:`cmd_read` command sets these properties according to the imported SVG file's content. The :ref:`cmd_color`, :ref:`cmd_alpha`, :ref:`cmd_penwidth`, :ref:`cmd_name`, and :ref:`cmd_pens` commands can set these properties to arbitrary values. In particular, the :ref:`cmd_pens` commands can apply a predefined set of values on multiple layers at once, for example to apply a CMYK color scheme (see :ref:`faq_custom_pen_config` for more information). The page size global property is set by the :ref:`cmd_pagesize` and :ref:`cmd_layout` commands, and used by the :ref:`cmd_write` command.

.. note::

Expand Down
4 changes: 4 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ CLI reference
.. click:: vpype_cli:cli
:prog: vpype

.. _cmd_alpha:
.. click:: vpype_cli:alpha
:prog: alpha

.. _cmd_arc:
.. click:: vpype_cli:arc
:prog: arc
Expand Down
19 changes: 19 additions & 0 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class Command:
Command("text 'hello wold'"),
Command("penwidth 0.15mm", preserves_metadata=False),
Command("color red", preserves_metadata=False),
Command("alpha 0.5", preserves_metadata=False),
Command("name my_name", preserves_metadata=False),
Command("propset -g prop:global hello", preserves_metadata=False),
Command("propset -l 1 prop:local hello", preserves_metadata=False),
Expand Down Expand Up @@ -727,3 +728,21 @@ def test_help(runner):
assert res.exit_code == 0
assert "Execute the sequence of commands passed in argument." in res.stdout
assert "multipass" in res.stdout


def test_alpha():
assert vpype_cli.execute("random alpha 0.5").layers[1].property(
vp.METADATA_FIELD_COLOR
) == vp.Color(0, 0, 0, 127)

assert vpype_cli.execute("random alpha 1").layers[1].property(
vp.METADATA_FIELD_COLOR
) == vp.Color(0, 0, 0, 255)

assert vpype_cli.execute("random alpha 0").layers[1].property(
vp.METADATA_FIELD_COLOR
) == vp.Color(0, 0, 0, 0)

assert vpype_cli.execute("random color red alpha 0.5").layers[1].property(
vp.METADATA_FIELD_COLOR
) == vp.Color(255, 0, 0, 127)
2 changes: 1 addition & 1 deletion tests/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ def test_read_command_page_size(
args += f"--display-size {default[0]:.3f}x{default[1]:.3f} "
if default[0] > default[1]:
args += f"--display-landscape"
doc = vpype_cli.execute(f"read {args} {path}")
doc = vpype_cli.execute(f"read {args} '{path}'")

assert doc.page_size == pytest.approx(target)

Expand Down
2 changes: 1 addition & 1 deletion vpype/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ def write_svg(
# affected by whether or not previous layer have their color defined
color_idx += 1

group.attribs["stroke"] = color.as_rgb_hes()
group.attribs["stroke"] = color.as_rgb_hex()
if color.alpha < 255:
group.attribs["stroke-opacity"] = f"{color.alpha/255:.3f}"
group.attribs["style"] = "display:inline"
Expand Down
2 changes: 1 addition & 1 deletion vpype/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def as_hex(self) -> str:
"""Return a standard, hexadecimal representation of the instance."""
return svgelements.Color(self.red, self.green, self.blue, self.alpha).hex

def as_rgb_hes(self) -> str:
def as_rgb_hex(self) -> str:
"""Return a standard, hexadecimal representation of the instance, ignoring alpha."""
return svgelements.Color(self.red, self.green, self.blue).hexrgb

Expand Down
38 changes: 37 additions & 1 deletion vpype_cli/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from .cli import cli
from .decorators import global_processor, layer_processor
from .types import LayerType, LengthType, TextType, multiple_to_layer_ids
from .types import FloatRangeType, LayerType, LengthType, TextType, multiple_to_layer_ids

__all__ = (
"propset",
Expand All @@ -19,6 +19,7 @@
"propclear",
"penwidth",
"color",
"alpha",
"name",
"pens",
)
Expand Down Expand Up @@ -298,6 +299,9 @@ def color(layer: vp.LineCollection, color: str) -> vp.LineCollection:
By default, this commands sets the color for all layers. Use the `--layer` option to set
the color of one (or more) specific layer(s).
Note: the opacity of a layer may be set without changing the base color with the `alpha`
command.
Examples:
Set the color for all layers:
Expand All @@ -313,6 +317,38 @@ def color(layer: vp.LineCollection, color: str) -> vp.LineCollection:
return layer


# noinspection PyShadowingNames
@cli.command(group="Metadata")
@click.argument("alpha", metavar="ALPHA", type=FloatRangeType(min=0.0, max=1.0, clamp=True))
@layer_processor
def alpha(layer: vp.LineCollection, alpha: float) -> vp.LineCollection:
"""Set the opacity of one or more layers.
Assign the opacity ALPHA to the target layer(s) without changing its based color. If the
base color is undefined, it is set to black.
By default, this commands operate on all layers. Use the `--layer` option to specify one or
more target layer(s)
Examples:
Set all layer to 50% red:
$ vpype [...] color red alpha .5 [...]
Set the opacity for a specific layer:
$ vpype [...] alpha --layer 2 .5 [...]
"""

if (color := layer.property(vp.METADATA_FIELD_COLOR)) is None:
color = vp.Color("black")
layer.set_property(
vp.METADATA_FIELD_COLOR, vp.Color(color.red, color.green, color.blue, int(alpha * 255))
)
return layer


# noinspection PyShadowingNames
@cli.command(group="Metadata")
@click.argument("name", type=TextType())
Expand Down
9 changes: 8 additions & 1 deletion vpype_cli/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,19 @@ class FileType(_DelegatedDeferredEvaluatorType):


class IntRangeType(_DelegatedDeferredEvaluatorType):
""":class:`click.File` clone which performs substitution on input."""
""":class:`click.IntRange` clone which performs substitution on input."""

name = "float"
_delegate_class = click.IntRange


class FloatRangeType(_DelegatedDeferredEvaluatorType):
""":class:`click.FloatRange` clone which performs substitution on input."""

name = "float"
_delegate_class = click.FloatRange


class ChoiceType(_DelegatedDeferredEvaluatorType):
name = "choice"
_delegate_class = click.Choice
Expand Down

0 comments on commit cd95e2d

Please sign in to comment.