Source code for colour_visuals.rgb_colourspace

# !/usr/bin/env python
"""
RGB Colourspace Visuals
=======================

Defines the *RGB colourspace* visuals:

-   :class:`colour_visuals.VisualRGBColourspace2D`
-   :class:`colour_visuals.VisualRGBColourspace3D`
"""

from __future__ import annotations

import numpy as np
import pygfx as gfx
from colour.constants import EPSILON
from colour.geometry import primitive_cube
from colour.hints import (
    ArrayLike,
    Literal,
    LiteralColourspaceModel,
    LiteralRGBColourspace,
    Sequence,
    Type,
    cast,
)
from colour.models import RGB_Colourspace, RGB_to_XYZ, XYZ_to_RGB, xy_to_XYZ
from colour.plotting import (
    CONSTANTS_COLOUR_STYLE,
    METHODS_CHROMATICITY_DIAGRAM,
    colourspace_model_axis_reorder,
    filter_RGB_colourspaces,
)
from colour.utilities import first_item, full

from colour_visuals.common import (
    XYZ_to_colourspace_model,
    append_channel,
    as_contiguous_array,
    conform_primitive_dtype,
)

__author__ = "Colour Developers"
__copyright__ = "Copyright 2023 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "VisualRGBColourspace2D",
    "VisualRGBColourspace3D",
]


[docs] class VisualRGBColourspace2D(gfx.Group): """ Create a 2D *RGB* colourspace gamut visual. Parameters ---------- colourspace *RGB* colourspace to plot the gamut of. ``colourspaces`` elements can be of any type or form supported by the :func:`colour.plotting.common.filter_RGB_colourspaces` definition. method *Chromaticity Diagram* method. colours Colours of the visual, if *None*, the colours are computed from the visual geometry. opacity Opacity of the visual. thickness Thickness of the visual lines. Examples -------- >>> import os >>> from colour.utilities import suppress_stdout >>> from wgpu.gui.auto import WgpuCanvas >>> with suppress_stdout(): ... canvas = WgpuCanvas(size=(960, 540)) ... scene = gfx.Scene() ... scene.add( ... gfx.Background( ... None, gfx.BackgroundMaterial(np.array([0.18, 0.18, 0.18])) ... ) ... ) ... visual = VisualRGBColourspace2D() ... camera = gfx.PerspectiveCamera(50, 16 / 9) ... camera.show_object(visual, up=np.array([0, 0, 1]), scale=1.25) ... scene.add(visual) ... if os.environ.get("CI") is None: ... gfx.show(scene, camera=camera, canvas=canvas) ... .. image:: ../_static/Plotting_VisualRGBColourspace2D.png :align: center :alt: visual-rgbcolourspace-2-d """
[docs] def __init__( self, colourspace: RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] = "sRGB", method: Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str = "CIE 1931", colours: ArrayLike | None = None, opacity: float = 1, thickness: float = 1, ): super().__init__() colourspace = cast( RGB_Colourspace, first_item(filter_RGB_colourspaces(colourspace).values()), ) plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"] ij = XYZ_to_ij( xy_to_XYZ(colourspace.primaries), plotting_colourspace.whitepoint ) ij[np.isnan(ij)] = 0 positions = append_channel( np.array([ij[0], ij[1], ij[1], ij[2], ij[2], ij[0]]), 0 ) if colours is None: RGB = XYZ_to_RGB( xy_to_XYZ(colourspace.primaries), plotting_colourspace ) colours_g = np.array( [RGB[0], RGB[1], RGB[1], RGB[2], RGB[2], RGB[0]] ) else: colours_g = np.tile(colours, (positions.shape[0], 1)) self._gamut = gfx.Line( gfx.Geometry( positions=as_contiguous_array(positions), colors=as_contiguous_array(append_channel(colours_g, opacity)), ), gfx.LineSegmentMaterial(thickness=thickness, color_mode="vertex"), ) self.add(self._gamut) ij = XYZ_to_ij( xy_to_XYZ(colourspace.whitepoint), plotting_colourspace.whitepoint ) positions = append_channel(ij, 0).reshape([-1, 3]) if colours is None: colours_w = XYZ_to_RGB( xy_to_XYZ(colourspace.whitepoint), plotting_colourspace ).reshape([-1, 3]) else: colours_w = np.tile(colours, (positions.shape[0], 1)) self._whitepoint = gfx.Points( gfx.Geometry( positions=as_contiguous_array(positions), sizes=as_contiguous_array( full(positions.shape[0], thickness * 3) ), colors=as_contiguous_array(append_channel(colours_w, opacity)), ), gfx.PointsMaterial(color_mode="vertex", vertex_sizes=True), ) self.add(self._whitepoint)
[docs] class VisualRGBColourspace3D(gfx.Mesh): """ Create a 3D *RGB* colourspace volume visual. Parameters ---------- colourspace *RGB* colourspace to plot the gamut of. ``colourspaces`` elements can be of any type or form supported by the :func:`colour.plotting.common.filter_RGB_colourspaces` definition. model Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for the list of supported colourspace models. colours Colours of the visual, if *None*, the colours are computed from the visual geometry. opacity Opacity of the visual. thickness Thickness of the visual lines. material Material used to surface the visual geomeetry. wireframe Whether to render the visual as a wireframe, i.e., only render edges. segments Edge segments count for the *RGB* colourspace cube. Examples -------- >>> import os >>> import pylinalg as la >>> from colour.utilities import suppress_stdout >>> from wgpu.gui.auto import WgpuCanvas >>> with suppress_stdout(): ... canvas = WgpuCanvas(size=(960, 540)) ... scene = gfx.Scene() ... scene.add( ... gfx.Background( ... None, gfx.BackgroundMaterial(np.array([0.18, 0.18, 0.18])) ... ) ... ) ... visual = VisualRGBColourspace3D(wireframe=True) ... visual.local.rotation = la.quat_from_euler( ... (-np.pi / 4, 0), order="XY" ... ) ... camera = gfx.PerspectiveCamera(50, 16 / 9) ... camera.show_object(visual, up=np.array([0, 0, 1]), scale=1.25) ... scene.add(visual) ... if os.environ.get("CI") is None: ... gfx.show(scene, camera=camera, canvas=canvas) ... .. image:: ../_static/Plotting_VisualRGBColourspace3D.png :align: center :alt: visual-rgbcolourspace-3-d """
[docs] def __init__( self, colourspace: RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] = "sRGB", model: LiteralColourspaceModel | str = "CIE xyY", colours: ArrayLike | None = None, opacity: float = 1, material: Type[gfx.MeshAbstractMaterial] = gfx.MeshBasicMaterial, wireframe: bool = False, segments: int = 16, ): colourspace = cast( RGB_Colourspace, first_item(filter_RGB_colourspaces(colourspace).values()), ) vertices, faces, outline = conform_primitive_dtype( primitive_cube( width_segments=segments, height_segments=segments, depth_segments=segments, ) ) positions = vertices["position"] + 0.5 positions[positions == 0] = EPSILON if colours is None: colours = positions else: colours = np.tile(colours, (positions.shape[0], 1)) positions = colourspace_model_axis_reorder( XYZ_to_colourspace_model( RGB_to_XYZ(positions, colourspace), colourspace.whitepoint, model, ), model, ) super().__init__( gfx.Geometry( positions=as_contiguous_array(positions), normals=vertices["normal"], indices=outline[..., 1].reshape([-1, 4]), colors=as_contiguous_array(append_channel(colours, opacity)), ), material(color_mode="vertex", wireframe=wireframe) if wireframe else material(color_mode="vertex"), )
if __name__ == "__main__": scene = gfx.Scene() scene.add( gfx.Background( None, gfx.BackgroundMaterial(np.array([0.18, 0.18, 0.18])) ) ) light_1 = gfx.AmbientLight() scene.add(light_1) light_2 = gfx.DirectionalLight() light_2.local.position = np.array([1, 1, 0]) scene.add(light_2) visual_1 = VisualRGBColourspace3D() scene.add(visual_1) visual_2 = VisualRGBColourspace3D(wireframe=True) visual_2.local.position = np.array([0.5, 0, 0]) scene.add(visual_2) visual_3 = VisualRGBColourspace3D(material=gfx.MeshNormalMaterial) visual_3.local.position = np.array([1, 0, 0]) scene.add(visual_3) visual_4 = VisualRGBColourspace3D( model="CIE Lab", colours=np.array([0.5, 0.5, 0.5]), opacity=1, material=gfx.MeshStandardMaterial, ) visual_4.local.position = np.array([2.5, 0, 0]) scene.add(visual_4) visual_5 = VisualRGBColourspace2D() visual_5.local.position = np.array([3.5, 0, 0]) scene.add(visual_5) visual_6 = VisualRGBColourspace2D( method="CIE 1976 UCS", colours=np.array([0.5, 0.5, 0.5]), opacity=1, ) visual_6.local.position = np.array([4.5, 0, 0]) scene.add(visual_6) gfx.show(scene, up=np.array([0, 0, 1]))