Module uim.model.inkdata.strokes

Expand source code
# -*- coding: utf-8 -*-
# Copyright © 2021 Wacom Authors. All Rights Reserved.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
import ctypes
import uuid
from abc import ABC
from enum import Enum
from typing import Tuple, List, Optional

import numpy as np

from uim.codec.context.scheme import PrecisionScheme
from uim.model.base import UUIDIdentifier, HashIdentifier
from uim.model.inkdata.brush import BlendModeURIs
from uim.model.semantics.structures import BoundingBox


class LayoutMask(Enum):
    """
    Specifies the various geometric and appearance attributes of a path point as bit flags.
    """
    X = 0x1
    Y = 0x2
    Z = 0x4
    SIZE = 0x8
    ROTATION = 0x10
    RED = 0x20
    GREEN = 0x40
    BLUE = 0x80
    ALPHA = 0x100
    SCALE_X = 0x200
    SCALE_Y = 0x400
    SCALE_Z = 0x800
    OFFSET_X = 0x1000
    OFFSET_Y = 0x2000
    OFFSET_Z = 0x4000
    TANGENT_X = 0x8000
    TANGENT_Y = 0x10000


class PathPointProperties(HashIdentifier):
    """
    PathPointProperties
    ===================
    A simple data model, which may hold size, color components and matrix transformational components.
    
    Parameters
    ----------
    size: float
        Size of the brush.
    red: float
        Color value [0, 1] for red channel.
    green: float
        Color value [0, 1] for green channel.
    blue: float
        Color value [0, 1] for blue channel.
    alpha: float
        Color value [0, 1] for alpha channel.
    rotation: float
        Brush rotation z value.
    scale_x: float
        Brush scale x value.
    scale_y: float
        Brush scale y value.
    scale_z: float
        Brush scale z [for 3D rendering].
    offset_x: float
        Brush offset x value.
    offset_y: float
        Brush offset y value.
    offset_z: float
        Brush offset z [for 3D rendering].

    References
    ----------
    [1] WILL SDK for ink - Rendering pipeline URL: https://developer-docs.wacom.com/sdk-for-ink/docs/pipeline
    [2] Ink Designer to configure rendering pipeline: http://ink-designer.trafficmanager.net/
    """

    def __init__(self, size: float = 0., red: float = 0., green: float = 0., blue: float = 0., alpha: float = 0.,
                 rotation: float = 0.,
                 scale_x: float = 0., scale_y: float = 0., scale_z: float = 0.,
                 offset_x: float = 0., offset_y: float = 0., offset_z: float = 0.):
        super().__init__()
        self.__size = size
        self.__red = red
        self.__green = green
        self.__blue = blue
        self.__alpha = alpha
        self.__rotation = rotation
        self.__scale_x = scale_x
        self.__scale_y = scale_y
        self.__scale_z = scale_z
        self.__offset_x = offset_x
        self.__offset_y = offset_y
        self.__offset_z = offset_z

    @staticmethod
    def color(rgba: int) -> Tuple[float, float, float, float]:
        """
        Decode integer encoded RBGA value into float.

        Parameters
        ----------
        rgba: int
            Color encoded in a single integer value

        Returns
        -------
        red - `float`
            Red value
        green - `float`
            Green value
        blue - `float`
            Blue value
        alpha - `float`
            Alpha value
        """
        red: float = ((rgba >> 24) & 0xFF) / 255.0
        green: float = ((rgba >> 16) & 0xFF) / 255.0
        blue: float = ((rgba >> 8) & 0xFF) / 255.0
        alpha: float = (rgba & 0xFF) / 255.0
        return red, green, blue, alpha

    @staticmethod
    def rgba(red: float, green: float, blue: float, alpha: float) -> int:
        """
        Encode RGBA values to a single integer value.

        Parameters
        ----------
        red: int -
            Red value
        green: int -
            Green value
        blue: int -
            Blue value
        alpha: int -
            Alpha value

        Returns
        -------
        rgba: int
            Color values encode as one single integer value
        """
        r: int = int(red * 255)
        g: int = int(green * 255)
        b: int = int(blue * 255)
        a: int = int(alpha * 255)
        return ctypes.c_int32((r << 24) | (g << 16) | (b << 8) | a).value

    @property
    def size(self) -> float:
        """Size of the brush; value between [0. - 1.]. (`float`)"""
        return self.__size

    @property
    def red(self) -> float:
        """Color value for red channel; value between [0. - 1.]. (`float`)"""
        return self.__red

    @property
    def green(self) -> float:
        """Color value for green channel; value between [0. - 1.]. (`float`)"""
        return self.__green

    @property
    def blue(self) -> float:
        """Color value for blue channel; value between [0. - 1.]. (`float`)"""
        return self.__blue

    @property
    def alpha(self) -> float:
        """Alpha value for channel; value between [0. - 1.]. (`float`)"""
        return self.__alpha

    @property
    def rotation(self) -> float:
        """Brush rotation. (`float`)"""
        return self.__rotation

    @property
    def scale_x(self) -> float:
        """Brush scale x value. (`float`)"""
        return self.__scale_x

    @property
    def scale_y(self) -> float:
        """Brush scale y value. (`float`)"""
        return self.__scale_y

    @property
    def scale_z(self) -> float:
        """Brush scale z value. (`float`)"""
        return self.__scale_z

    @property
    def offset_x(self) -> float:
        """Offset x value. (`float`)"""
        return self.__offset_x

    @property
    def offset_y(self) -> float:
        """Offset y value. (`float`)"""
        return self.__offset_y

    @property
    def offset_z(self) -> float:
        """Offset z value. (`float`)"""
        return self.__offset_z

    @size.setter
    def size(self, size: float):
        self.__size = size

    @red.setter
    def red(self, red: float):
        self.__red = red

    @green.setter
    def green(self, green: float):
        self.__green = green

    @blue.setter
    def blue(self, blue: float):
        self.__blue = blue

    @alpha.setter
    def alpha(self, alpha: float):
        self.__alpha = alpha

    @rotation.setter
    def rotation(self, rotation: float):
        self.__rotation = rotation

    @scale_x.setter
    def scale_x(self, scale_x: float):
        self.__scale_x = scale_x

    @scale_y.setter
    def scale_y(self, scale_y: float):
        self.__scale_y = scale_y

    @scale_z.setter
    def scale_z(self, scale_z: float):
        self.__scale_z = scale_z

    @offset_x.setter
    def offset_x(self, offset_x: float):
        self.__offset_x = offset_x

    @offset_y.setter
    def offset_y(self, offset_y: float):
        self.__offset_y = offset_y

    @offset_z.setter
    def offset_z(self, offset_z: float):
        self.__offset_z = offset_z

    def __tokenize__(self) -> list:
        return [self.size, self.red, self.green, self.blue, self.alpha, self.rotation, self.scale_x, self.scale_y,
                self.scale_z, self.offset_x, self.offset_y, self.offset_z]

    def __repr__(self):
        return '<PathPointProperties: [size:={}, red:={}, green:={}, blue:={}, alpha:={}, rotation:={},' \
               'scale x:={}, scale y:={}, scale z:={}, offset x:={}, offset y:={}, offset z:={}]>' \
            .format(self.size,
                    self.red, self.green, self.blue, self.alpha,
                    self.rotation,
                    self.scale_x, self.scale_y, self.scale_z,
                    self.offset_x, self.offset_y, self.offset_z)


class Style(ABC):
    """
    Style
    =====
    The `Style` is defined as a combination of a `PathPointProperties` configuration, reference to a Brush,
    a random number generator seed value and rendering method type. Setting the Style property allows overriding of
    specific path point properties, color components and/or matrix transformational components.
    A `Style` with `PathPointProperties` configuration should be normally used to define constant path components.

    Parameters
    ----------
    properties: `PathPointProperties`
        Static values of properties which do not exist per per path point
    brush_uri: str
        Reference to Brush used for stroke rasterization
    particles_random_seed: int
        Particles random seed, required for particle strokes
    render_mode_uri: str
        Render mode URI
    """

    def __init__(self, properties: PathPointProperties = None, brush_uri: str = None, particles_random_seed: int = 0,
                 render_mode_uri: str = BlendModeURIs.SOURCE_OVER):
        self.__properties = properties if properties is not None else PathPointProperties()
        self.__brush_uri = brush_uri
        self.__particles_random_seed = particles_random_seed
        self.__render_mode_URI = render_mode_uri if render_mode_uri is not None and render_mode_uri != '' \
            else BlendModeURIs.SOURCE_OVER

    @property
    def path_point_properties(self) -> PathPointProperties:
        """Static values of properties which do not exist per per path point. (`PathPointProperties`, read-only)"""
        return self.__properties

    @property
    def brush_uri(self) -> str:
        """Reference to Brush used for stroke rasterization using the URI. (`str`, read-only)"""
        return self.__brush_uri

    @property
    def particles_random_seed(self):
        """Particles random seed, required for particle strokes.  (`int`, read-only) """
        return self.__particles_random_seed

    @property
    def render_mode_uri(self):
        """Defines additional information about stroke visualisation, such as ERASER. (`str`)"""
        return self.__render_mode_URI

    @render_mode_uri.setter
    def render_mode_uri(self, uri: str):
        self.__render_mode_URI = uri

    def __repr__(self):
        return '<Style : [id:={}, particles_random_seed:={}, render mode:={}>' \
            .format(self.__brush_uri,
                    self.__particles_random_seed,
                    self.__render_mode_URI)


class Spline(ABC):
    """
    Spline
    ======
    The Catmull-Rom spline is defined in the scope of the Stroke using the following properties:

    - **ts, tf** - Spline start and end parameters
    - **spline** - a sequence of spline data points
    - **color** - a sequence of color values (per spline data point; if provided, the length of this sequence must be
                  equal to the spline points count)

    Parameters
    ----------
    layout_mask: int
        Configured layout mask
    data: List[float]
        List of spline values
    ts: float
        Start parameter
    tf: float
        Final parameter
    """

    def __init__(self, layout_mask: int, data: List[float], ts: float = 0., tf: float = 1.):
        self.__layout_mask: int = layout_mask
        self.__data: List[float] = data
        self.__ts: float = ts
        self.__tf: float = tf

    @property
    def data(self) -> List[float]:
        """Gets or sets a list of spline values. (`List[float]`, read-only)"""
        return self.__data

    @property
    def layout_mask(self) -> int:
        """Gets a bitmask representation of the spline's data layout. (`int`, read-only)"""
        return self.__layout_mask

    @property
    def ts(self) -> float:
        """Start parameter for the first Catmull-Rom segment. (`float`, read-only)"""
        return self.__ts

    @property
    def tf(self) -> float:
        """Final parameter for the last Catmull-Rom segment. (`float`, read-only)"""
        return self.__tf

    def __repr__(self):
        return f'<Spline : [mask:={self.layout_mask}]>'


class Stroke(UUIDIdentifier):
    """
    Stroke Geometry
    ---------------
    The geometry of an ink stroke is represented by its Stroke.
    A Stroke is defined as a combination of:

        - A Catmull-Rom spline in the form of a sequence of data points (mandatory), including per-point
          transformational data (optional)
        - Rendering configuration about how the spline should be visualized (optional)
        - Reference to raw input data (SensorData instance), which the path originates from (optional)

    Parameters
    ----------
     sid: `UUID`
        Stroke unique identifier
    sensor_data_offset: `int`
        Index of points mapping between raw and processed paths.
    sensor_data_id: `UUID`
        Reference UUID of sensor data.
    sensor_data_mapping: list
        Explicit mapping between indices of Path and SensorData, used when input rate is very high and
        provides unwanted points.
    style: `Style`
        The Style object associated with this stroke.
    random_seed: `int`
        A random seed used for randomly generated attributes of a stroke.
    spline: `Spline`
        The stroke's Catmull-Rom spline.

    Examples
    --------
    >>> from uim.model.inkdata.brush import BrushPolygon, BrushPolygonUri, RasterBrush, RotationMode, BlendMode
    >>> from uim.model.inkdata.strokes import Spline, Style, Stroke, LayoutMask
    >>>
    >>> raster_brush_0: RasterBrush = RasterBrush(
    >>> name="app://qa-test-app/raster-brush/MyRasterBrush",
    >>> spacing=10., scattering=5., rotation=RotationMode.TRAJECTORY, shape_textures=[bytes([10, 20]),
    >>>                                                                               bytes([30, 20])],
    >>> fill_width=2.0, fill_height=0.3,
    >>> fill_texture=bytes([10, 10, 20, 15, 17, 20, 25, 16, 34, 255, 23, 0, 34, 255, 23, 255]),
    >>> randomize_fill=False, blend_mode=BlendMode.SOURCE_OVER)
    >>> # Create a spline object - 9 data points, each consisting of X, Y, Size, Red, Green, Blue, Alpha
    >>> spline_1: Spline = Spline(
    >>>     LayoutMask.X.value | LayoutMask.Y.value | LayoutMask.SIZE.value | LayoutMask.RED.value | LayoutMask.GREEN.value
    >>>     | LayoutMask.BLUE.value | LayoutMask.ALPHA.value,
    >>>     [10.0, 10.7, 1.0, 0.5, 0.0, 0.1, 1.0,
    >>>      21.0, 20.2, 2.0, 0.9, 0.4, 0.2, 0.8,
    >>>      30.0, 12.4, 2.1, 0.7, 0.1, 0.1, 0.7,
    >>>      40.0, 15.0, 1.5, 0.3, 0.5, 0.4, 1.0,
    >>>      50.0, 45.0, 1.0, 0.3, 0.5, 0.4, 1.0,
    >>>      41.0, 53.0, 1.1, 0.2, 0.3, 0.5, 0.9,
    >>>      33.0, 73.0, 1.2, 0.6, 0.7, 0.4, 0.8,
    >>>      20.0, 84.0, 1.3, 0.7, 0.8, 0.3, 0.7,
    >>>      10.0, 91.0, 1.1, 0.7, 0.9, 0.2, 0.6]
    >>> )
    >>>
    >>> # Create a style
    >>> style_1: Style = Style(brush_uri=raster_brush_0.name)
    >>> style_1.path_point_properties.rotation = 0.35
    >>>
    >>> # The render mode URI can also be app specific like app://blabla
    >>> # The URI will://rasterization/3.0/blend-mode/SourceOver is assumed and must not be set.
    >>> style_1.render_mode_uri = "will://rasterization/3.0/blend-mode/DestinationOver"
    >>>
    >>> # Create a stroke object. Note that it just exists, but is not in the model yet.
    >>> stroke_1: Stroke = Stroke(UUIDIdentifier.id_generator(), spline=spline_1, style=style_1)
    """

    def __init__(self, sid: uuid.UUID = None, sensor_data_offset: int = None, sensor_data_id: uuid.UUID = None,
                 sensor_data_mapping: list = None, style: Style = None, random_seed: int = 0, property_index: int = 0,
                 spline: Spline = None):
        super().__init__(sid)
        self.__start_parameter: float = 0.
        self.__end_parameter: float = 0.
        self.__spline_x: List[float] = []
        self.__spline_y: List[float] = []
        self.__spline_z: List[float] = []
        self.__size: List[float] = []
        self.__rotation: List[float] = []
        self.__scale_x: List[float] = []
        self.__scale_y: List[float] = []
        self.__scale_z: List[float] = []
        self.__offset_x: List[float] = []
        self.__offset_y: List[float] = []
        self.__offset_z: List[float] = []
        self.__red: List[int] = []
        self.__green: List[int] = []
        self.__blue: List[int] = []
        self.__alpha: List[int] = []
        self.__tangent_x: List[float] = []
        self.__tangent_y: List[float] = []
        self.__sensor_data_id: uuid.UUID = sensor_data_id
        self.__sensor_data_offset: int = sensor_data_offset or 0
        self._sensor_data_mapping: list = sensor_data_mapping or []
        self.__style: Style = style
        self.__random_seed: int = random_seed
        self.__properties_index: int = property_index
        self.__timestamp_cache = None
        self.__pressure_cache = None
        self.__precision_scheme: Optional[PrecisionScheme] = None
        if spline is not None:
            self.__import__(spline)

    def set_timestamp_values(self, timestamp_values):
        self.__timestamp_cache = timestamp_values

    def get_timestamp_values(self):
        return self.__timestamp_cache

    def set_pressure_values(self, pressure_values):
        self.__pressure_cache = pressure_values

    def get_pressure_values(self):
        return self.__pressure_cache

    @property
    def layout_mask(self) -> int:
        """Layout mask for the stroke. (`int`)"""
        mask: int = 0
        if len(self.__spline_x) > 0:
            mask |= LayoutMask.X.value
        if len(self.__spline_y) > 0:
            mask |= LayoutMask.Y.value
        if len(self.__spline_z) > 0:
            mask |= LayoutMask.Z.value
        if len(self.__size) > 0:
            mask |= LayoutMask.SIZE.value
        if len(self.__rotation) > 0:
            mask |= LayoutMask.ROTATION.value
        if len(self.__red) > 0:
            mask |= LayoutMask.RED.value
        if len(self.__green) > 0:
            mask |= LayoutMask.GREEN.value
        if len(self.__blue) > 0:
            mask |= LayoutMask.BLUE.value
        if len(self.__alpha) > 0:
            mask |= LayoutMask.ALPHA.value
        if len(self.__scale_x) > 0:
            mask |= LayoutMask.SCALE_X.value
        if len(self.__scale_y) > 0:
            mask |= LayoutMask.SCALE_Y.value
        if len(self.__scale_z) > 0:
            mask |= LayoutMask.SCALE_Z.value
        if len(self.__offset_x) > 0:
            mask |= LayoutMask.OFFSET_X.value
        if len(self.__offset_y) > 0:
            mask |= LayoutMask.OFFSET_Y.value
        if len(self.__offset_z) > 0:
            mask |= LayoutMask.OFFSET_Z.value
        if len(self.__tangent_x) > 0:
            mask |= LayoutMask.TANGENT_X.value
        if len(self.__tangent_y) > 0:
            mask |= LayoutMask.TANGENT_Y.value
        return mask

    @property
    def properties_index(self) -> int:
        """Setting the properties index. (`int`)"""
        return self.__properties_index

    @properties_index.setter
    def properties_index(self, properties_index: int):
        self.__properties_index = properties_index

    @property
    def start_parameter(self) -> float:
        """Start parameter of the stroke. (`float`)"""
        return self.__start_parameter

    @start_parameter.setter
    def start_parameter(self, s: float):
        self.__start_parameter = s

    @property
    def end_parameter(self) -> float:
        """End parameter of the stroke. (`float`)"""
        return self.__end_parameter

    @end_parameter.setter
    def end_parameter(self, e: float):
        self.__end_parameter = e

    @property
    def precision_scheme(self) -> PrecisionScheme:
        """An object that defines the precisions used for storing the stroke data. If set to None, stroke data is stored
        using floating point values. (`PrecisionScheme`)"""
        return self.__precision_scheme

    @precision_scheme.setter
    def precision_scheme(self, precision_scheme: PrecisionScheme):
        self.__precision_scheme = precision_scheme

    @property
    def sizes(self) -> List[float]:
        """List of size values. (`List[float]`)"""
        return self.__size

    @sizes.setter
    def sizes(self, size: list):
        self.__size = size

    @property
    def red(self) -> List[int]:
        """List of color values [0, 255] for red channel. (`List[int]`)"""
        return self.__red

    @red.setter
    def red(self, values: List[int]):
        self.__red = values

    @property
    def green(self) -> List[int]:
        """Color values [0, 255] for green channel. (`List[int]`)"""
        return self.__green

    @green.setter
    def green(self, values: List[int]):
        self.__green = values

    @property
    def blue(self) -> List[int]:
        """Color values [0, 255] for blue channel. (`List[int]`)"""
        return self.__blue

    @blue.setter
    def blue(self, values: List[int]):
        self.__blue = values

    @property
    def alpha(self) -> List[int]:
        """Color values [0, 255] for alpha channel. (`List[int]`)"""
        return self.__alpha

    @alpha.setter
    def alpha(self, values: List[int]):
        self.__alpha = values

    @property
    def rotations(self) -> List[float]:
        """List of rotations. (`List[float]`)"""
        return self.__rotation

    @rotations.setter
    def rotations(self, values: List[float]):
        self.__rotation = values

    @property
    def splines_x(self) -> List[float]:
        """List of splines x. (`List[float]`)"""
        return self.__spline_x

    @splines_x.setter
    def splines_x(self, spline_x: List[float]):
        self.__spline_x = spline_x

    @property
    def splines_y(self) -> List[float]:
        """List of splines y. (`List[float]`)"""
        return self.__spline_y

    @splines_y.setter
    def splines_y(self, spline_y: List[float]):
        self.__spline_y = spline_y

    @property
    def splines_z(self) -> List[float]:
        """List of splines z. (`List[float]`)"""
        return self.__spline_z

    @splines_z.setter
    def splines_z(self, spline_z: List[float]):
        self.__spline_z = spline_z

    @property
    def scales_x(self) -> List[float]:
        """List of x scales. (`List[float]`)"""
        return self.__scale_x

    @scales_x.setter
    def scales_x(self, scale: List[float]):
        self.__scale_x = scale

    @property
    def scales_y(self) -> List[float]:
        """List of y scales. (`List[float]`)"""
        return self.__scale_y

    @scales_y.setter
    def scales_y(self, scale: List[float]):
        self.__scale_y = scale

    @property
    def scales_z(self) -> List[float]:
        """List of z scales. (`List[float]`)"""
        return self.__scale_z

    @scales_z.setter
    def scales_z(self, scale: list):
        self.__scale_z = scale

    @property
    def offsets_x(self) -> List[float]:
        """List of x offsets."""
        return self.__offset_x

    @offsets_x.setter
    def offsets_x(self, offset: List[float]):
        self.__offset_x = offset

    @property
    def offsets_y(self) -> List[float]:
        """List of y offsets. (`List[float]`)"""
        return self.__offset_y

    @offsets_y.setter
    def offsets_y(self, offset: List[float]):
        self.__offset_y = offset

    @property
    def offsets_z(self) -> List[float]:
        """List of z offsets. (`List[float]`)"""
        return self.__offset_z

    @offsets_z.setter
    def offsets_z(self, offset: List[float]):
        self.__offset_z = offset

    @property
    def sensor_data_offset(self) -> int:
        """Index of points mapping between raw and processed paths. (`int`)"""
        return self.__sensor_data_offset

    @property
    def sensor_data_id(self) -> uuid.UUID:
        """Reference id sensor data. (`UUID`)"""
        return self.__sensor_data_id

    @property
    def sensor_data_mapping(self) -> List[int]:
        """Explicit mapping between indices of Path and SensorData, used when input rate is very high and
        provides unwanted points. (`List[int]`)"""
        return self._sensor_data_mapping

    @property
    def style(self) -> Style:
        """Style that is applied to the path of the stroke. (`Style`)"""
        return self.__style

    @style.setter
    def style(self, style: Style):
        self.__style = style

    @property
    def spline_min_x(self) -> float:
        """Minimum value of x spline. (`float`)"""
        return np.min(self.__spline_x)

    @property
    def spline_min_y(self) -> float:
        """Minimum value of y spline. (`float`)"""
        return np.min(self.__spline_y)

    @property
    def spline_max_x(self) -> float:
        """Maximum value of x spline. (`float`)"""
        return np.max(self.__spline_x)

    @property
    def spline_max_y(self) -> float:
        """Maximum value of y spline. (`float`)"""
        return np.max(self.__spline_y)

    @property
    def points_count(self) -> int:
        """Number of points of sample points. (`int`)"""
        return len(self.__spline_x)

    @property
    def bounding_box(self) -> BoundingBox:
        """Bounding box for path stroke. (`BoundingBox`)"""
        x_min = self.spline_min_x
        x_max = self.spline_max_x
        y_min = self.spline_min_y
        y_max = self.spline_max_y
        return BoundingBox(x=x_min, y=y_min, width=x_max - x_min, height=y_max - y_min)

    def __import__(self, spline: Spline):
        # The content from spline is imported with the appropriate Layout mask being set.
        self.__start_parameter = spline.ts
        self.__end_parameter = spline.tf
        idx: int = 0
        while idx < len(spline.data):
            if spline.layout_mask & LayoutMask.X.value:
                self.__spline_x.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.Y.value:
                self.__spline_y.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.Z.value:
                self.__spline_z.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.SIZE.value:
                self.__size.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.ROTATION.value:
                self.__rotation.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.RED.value:
                self.__red.append(int(spline.data[idx] * 255))
                idx += 1
            if spline.layout_mask & LayoutMask.GREEN.value:
                self.__green.append(int(spline.data[idx] * 255))
                idx += 1
            if spline.layout_mask & LayoutMask.BLUE.value:
                self.__blue.append(int(spline.data[idx] * 255))
                idx += 1
            if spline.layout_mask & LayoutMask.ALPHA.value:
                self.__alpha.append(int(spline.data[idx] * 255))
                idx += 1
            if spline.layout_mask & LayoutMask.SCALE_X.value:
                self.__scale_x.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.SCALE_Y.value:
                self.__scale_y.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.SCALE_Z.value:
                self.__scale_z.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.OFFSET_X.value:
                self.__offset_x.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.OFFSET_Y.value:
                self.__offset_y.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.OFFSET_Z.value:
                self.__offset_z.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.TANGENT_X.value:
                self.__tangent_x.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.TANGENT_Y.value:
                self.__tangent_y.append(spline.data[idx])
                idx += 1

    def __repr__(self):
        return f'<Stroke : [id:={self.id_h_form}], [num points:={self.points_count}, layout mask:={self.layout_mask}]>'

Classes

class LayoutMask (value, names=None, *, module=None, qualname=None, type=None, start=1)

Specifies the various geometric and appearance attributes of a path point as bit flags.

Expand source code
class LayoutMask(Enum):
    """
    Specifies the various geometric and appearance attributes of a path point as bit flags.
    """
    X = 0x1
    Y = 0x2
    Z = 0x4
    SIZE = 0x8
    ROTATION = 0x10
    RED = 0x20
    GREEN = 0x40
    BLUE = 0x80
    ALPHA = 0x100
    SCALE_X = 0x200
    SCALE_Y = 0x400
    SCALE_Z = 0x800
    OFFSET_X = 0x1000
    OFFSET_Y = 0x2000
    OFFSET_Z = 0x4000
    TANGENT_X = 0x8000
    TANGENT_Y = 0x10000

Ancestors

  • enum.Enum

Class variables

var ALPHA
var BLUE
var GREEN
var OFFSET_X
var OFFSET_Y
var OFFSET_Z
var RED
var ROTATION
var SCALE_X
var SCALE_Y
var SCALE_Z
var SIZE
var TANGENT_X
var TANGENT_Y
var X
var Y
var Z
class PathPointProperties (size: float = 0.0, red: float = 0.0, green: float = 0.0, blue: float = 0.0, alpha: float = 0.0, rotation: float = 0.0, scale_x: float = 0.0, scale_y: float = 0.0, scale_z: float = 0.0, offset_x: float = 0.0, offset_y: float = 0.0, offset_z: float = 0.0)

PathPointProperties

A simple data model, which may hold size, color components and matrix transformational components.

Parameters

size : float
Size of the brush.
red : float
Color value [0, 1] for red channel.
green : float
Color value [0, 1] for green channel.
blue : float
Color value [0, 1] for blue channel.
alpha : float
Color value [0, 1] for alpha channel.
rotation : float
Brush rotation z value.
scale_x : float
Brush scale x value.
scale_y : float
Brush scale y value.
scale_z : float
Brush scale z [for 3D rendering].
offset_x : float
Brush offset x value.
offset_y : float
Brush offset y value.
offset_z : float
Brush offset z [for 3D rendering].

References

[1] WILL SDK for ink - Rendering pipeline URL: https://developer-docs.wacom.com/sdk-for-ink/docs/pipeline [2] Ink Designer to configure rendering pipeline: http://ink-designer.trafficmanager.net/

Expand source code
class PathPointProperties(HashIdentifier):
    """
    PathPointProperties
    ===================
    A simple data model, which may hold size, color components and matrix transformational components.
    
    Parameters
    ----------
    size: float
        Size of the brush.
    red: float
        Color value [0, 1] for red channel.
    green: float
        Color value [0, 1] for green channel.
    blue: float
        Color value [0, 1] for blue channel.
    alpha: float
        Color value [0, 1] for alpha channel.
    rotation: float
        Brush rotation z value.
    scale_x: float
        Brush scale x value.
    scale_y: float
        Brush scale y value.
    scale_z: float
        Brush scale z [for 3D rendering].
    offset_x: float
        Brush offset x value.
    offset_y: float
        Brush offset y value.
    offset_z: float
        Brush offset z [for 3D rendering].

    References
    ----------
    [1] WILL SDK for ink - Rendering pipeline URL: https://developer-docs.wacom.com/sdk-for-ink/docs/pipeline
    [2] Ink Designer to configure rendering pipeline: http://ink-designer.trafficmanager.net/
    """

    def __init__(self, size: float = 0., red: float = 0., green: float = 0., blue: float = 0., alpha: float = 0.,
                 rotation: float = 0.,
                 scale_x: float = 0., scale_y: float = 0., scale_z: float = 0.,
                 offset_x: float = 0., offset_y: float = 0., offset_z: float = 0.):
        super().__init__()
        self.__size = size
        self.__red = red
        self.__green = green
        self.__blue = blue
        self.__alpha = alpha
        self.__rotation = rotation
        self.__scale_x = scale_x
        self.__scale_y = scale_y
        self.__scale_z = scale_z
        self.__offset_x = offset_x
        self.__offset_y = offset_y
        self.__offset_z = offset_z

    @staticmethod
    def color(rgba: int) -> Tuple[float, float, float, float]:
        """
        Decode integer encoded RBGA value into float.

        Parameters
        ----------
        rgba: int
            Color encoded in a single integer value

        Returns
        -------
        red - `float`
            Red value
        green - `float`
            Green value
        blue - `float`
            Blue value
        alpha - `float`
            Alpha value
        """
        red: float = ((rgba >> 24) & 0xFF) / 255.0
        green: float = ((rgba >> 16) & 0xFF) / 255.0
        blue: float = ((rgba >> 8) & 0xFF) / 255.0
        alpha: float = (rgba & 0xFF) / 255.0
        return red, green, blue, alpha

    @staticmethod
    def rgba(red: float, green: float, blue: float, alpha: float) -> int:
        """
        Encode RGBA values to a single integer value.

        Parameters
        ----------
        red: int -
            Red value
        green: int -
            Green value
        blue: int -
            Blue value
        alpha: int -
            Alpha value

        Returns
        -------
        rgba: int
            Color values encode as one single integer value
        """
        r: int = int(red * 255)
        g: int = int(green * 255)
        b: int = int(blue * 255)
        a: int = int(alpha * 255)
        return ctypes.c_int32((r << 24) | (g << 16) | (b << 8) | a).value

    @property
    def size(self) -> float:
        """Size of the brush; value between [0. - 1.]. (`float`)"""
        return self.__size

    @property
    def red(self) -> float:
        """Color value for red channel; value between [0. - 1.]. (`float`)"""
        return self.__red

    @property
    def green(self) -> float:
        """Color value for green channel; value between [0. - 1.]. (`float`)"""
        return self.__green

    @property
    def blue(self) -> float:
        """Color value for blue channel; value between [0. - 1.]. (`float`)"""
        return self.__blue

    @property
    def alpha(self) -> float:
        """Alpha value for channel; value between [0. - 1.]. (`float`)"""
        return self.__alpha

    @property
    def rotation(self) -> float:
        """Brush rotation. (`float`)"""
        return self.__rotation

    @property
    def scale_x(self) -> float:
        """Brush scale x value. (`float`)"""
        return self.__scale_x

    @property
    def scale_y(self) -> float:
        """Brush scale y value. (`float`)"""
        return self.__scale_y

    @property
    def scale_z(self) -> float:
        """Brush scale z value. (`float`)"""
        return self.__scale_z

    @property
    def offset_x(self) -> float:
        """Offset x value. (`float`)"""
        return self.__offset_x

    @property
    def offset_y(self) -> float:
        """Offset y value. (`float`)"""
        return self.__offset_y

    @property
    def offset_z(self) -> float:
        """Offset z value. (`float`)"""
        return self.__offset_z

    @size.setter
    def size(self, size: float):
        self.__size = size

    @red.setter
    def red(self, red: float):
        self.__red = red

    @green.setter
    def green(self, green: float):
        self.__green = green

    @blue.setter
    def blue(self, blue: float):
        self.__blue = blue

    @alpha.setter
    def alpha(self, alpha: float):
        self.__alpha = alpha

    @rotation.setter
    def rotation(self, rotation: float):
        self.__rotation = rotation

    @scale_x.setter
    def scale_x(self, scale_x: float):
        self.__scale_x = scale_x

    @scale_y.setter
    def scale_y(self, scale_y: float):
        self.__scale_y = scale_y

    @scale_z.setter
    def scale_z(self, scale_z: float):
        self.__scale_z = scale_z

    @offset_x.setter
    def offset_x(self, offset_x: float):
        self.__offset_x = offset_x

    @offset_y.setter
    def offset_y(self, offset_y: float):
        self.__offset_y = offset_y

    @offset_z.setter
    def offset_z(self, offset_z: float):
        self.__offset_z = offset_z

    def __tokenize__(self) -> list:
        return [self.size, self.red, self.green, self.blue, self.alpha, self.rotation, self.scale_x, self.scale_y,
                self.scale_z, self.offset_x, self.offset_y, self.offset_z]

    def __repr__(self):
        return '<PathPointProperties: [size:={}, red:={}, green:={}, blue:={}, alpha:={}, rotation:={},' \
               'scale x:={}, scale y:={}, scale z:={}, offset x:={}, offset y:={}, offset z:={}]>' \
            .format(self.size,
                    self.red, self.green, self.blue, self.alpha,
                    self.rotation,
                    self.scale_x, self.scale_y, self.scale_z,
                    self.offset_x, self.offset_y, self.offset_z)

Ancestors

Class variables

var SEPARATOR : str

Static methods

def color(rgba: int) ‑> Tuple[float, float, float, float]

Decode integer encoded RBGA value into float.

Parameters

rgba : int
Color encoded in a single integer value

Returns

red - float Red value green - float Green value blue - float Blue value alpha - float Alpha value

Expand source code
@staticmethod
def color(rgba: int) -> Tuple[float, float, float, float]:
    """
    Decode integer encoded RBGA value into float.

    Parameters
    ----------
    rgba: int
        Color encoded in a single integer value

    Returns
    -------
    red - `float`
        Red value
    green - `float`
        Green value
    blue - `float`
        Blue value
    alpha - `float`
        Alpha value
    """
    red: float = ((rgba >> 24) & 0xFF) / 255.0
    green: float = ((rgba >> 16) & 0xFF) / 255.0
    blue: float = ((rgba >> 8) & 0xFF) / 255.0
    alpha: float = (rgba & 0xFF) / 255.0
    return red, green, blue, alpha
def rgba(red: float, green: float, blue: float, alpha: float) ‑> int

Encode RGBA values to a single integer value.

Parameters

red : int -
Red value
green : int -
Green value
blue : int -
Blue value
alpha : int -
Alpha value

Returns

rgba : int
Color values encode as one single integer value
Expand source code
@staticmethod
def rgba(red: float, green: float, blue: float, alpha: float) -> int:
    """
    Encode RGBA values to a single integer value.

    Parameters
    ----------
    red: int -
        Red value
    green: int -
        Green value
    blue: int -
        Blue value
    alpha: int -
        Alpha value

    Returns
    -------
    rgba: int
        Color values encode as one single integer value
    """
    r: int = int(red * 255)
    g: int = int(green * 255)
    b: int = int(blue * 255)
    a: int = int(alpha * 255)
    return ctypes.c_int32((r << 24) | (g << 16) | (b << 8) | a).value

Instance variables

var alpha : float

Alpha value for channel; value between [0. - 1.]. (float)

Expand source code
@property
def alpha(self) -> float:
    """Alpha value for channel; value between [0. - 1.]. (`float`)"""
    return self.__alpha
var blue : float

Color value for blue channel; value between [0. - 1.]. (float)

Expand source code
@property
def blue(self) -> float:
    """Color value for blue channel; value between [0. - 1.]. (`float`)"""
    return self.__blue
var green : float

Color value for green channel; value between [0. - 1.]. (float)

Expand source code
@property
def green(self) -> float:
    """Color value for green channel; value between [0. - 1.]. (`float`)"""
    return self.__green
var offset_x : float

Offset x value. (float)

Expand source code
@property
def offset_x(self) -> float:
    """Offset x value. (`float`)"""
    return self.__offset_x
var offset_y : float

Offset y value. (float)

Expand source code
@property
def offset_y(self) -> float:
    """Offset y value. (`float`)"""
    return self.__offset_y
var offset_z : float

Offset z value. (float)

Expand source code
@property
def offset_z(self) -> float:
    """Offset z value. (`float`)"""
    return self.__offset_z
var red : float

Color value for red channel; value between [0. - 1.]. (float)

Expand source code
@property
def red(self) -> float:
    """Color value for red channel; value between [0. - 1.]. (`float`)"""
    return self.__red
var rotation : float

Brush rotation. (float)

Expand source code
@property
def rotation(self) -> float:
    """Brush rotation. (`float`)"""
    return self.__rotation
var scale_x : float

Brush scale x value. (float)

Expand source code
@property
def scale_x(self) -> float:
    """Brush scale x value. (`float`)"""
    return self.__scale_x
var scale_y : float

Brush scale y value. (float)

Expand source code
@property
def scale_y(self) -> float:
    """Brush scale y value. (`float`)"""
    return self.__scale_y
var scale_z : float

Brush scale z value. (float)

Expand source code
@property
def scale_z(self) -> float:
    """Brush scale z value. (`float`)"""
    return self.__scale_z
var size : float

Size of the brush; value between [0. - 1.]. (float)

Expand source code
@property
def size(self) -> float:
    """Size of the brush; value between [0. - 1.]. (`float`)"""
    return self.__size

Inherited members

class Spline (layout_mask: int, data: List[float], ts: float = 0.0, tf: float = 1.0)

Spline

The Catmull-Rom spline is defined in the scope of the Stroke using the following properties:

  • ts, tf - Spline start and end parameters
  • spline - a sequence of spline data points
  • color - a sequence of color values (per spline data point; if provided, the length of this sequence must be equal to the spline points count)

Parameters

layout_mask : int
Configured layout mask
data : List[float]
List of spline values
ts : float
Start parameter
tf : float
Final parameter
Expand source code
class Spline(ABC):
    """
    Spline
    ======
    The Catmull-Rom spline is defined in the scope of the Stroke using the following properties:

    - **ts, tf** - Spline start and end parameters
    - **spline** - a sequence of spline data points
    - **color** - a sequence of color values (per spline data point; if provided, the length of this sequence must be
                  equal to the spline points count)

    Parameters
    ----------
    layout_mask: int
        Configured layout mask
    data: List[float]
        List of spline values
    ts: float
        Start parameter
    tf: float
        Final parameter
    """

    def __init__(self, layout_mask: int, data: List[float], ts: float = 0., tf: float = 1.):
        self.__layout_mask: int = layout_mask
        self.__data: List[float] = data
        self.__ts: float = ts
        self.__tf: float = tf

    @property
    def data(self) -> List[float]:
        """Gets or sets a list of spline values. (`List[float]`, read-only)"""
        return self.__data

    @property
    def layout_mask(self) -> int:
        """Gets a bitmask representation of the spline's data layout. (`int`, read-only)"""
        return self.__layout_mask

    @property
    def ts(self) -> float:
        """Start parameter for the first Catmull-Rom segment. (`float`, read-only)"""
        return self.__ts

    @property
    def tf(self) -> float:
        """Final parameter for the last Catmull-Rom segment. (`float`, read-only)"""
        return self.__tf

    def __repr__(self):
        return f'<Spline : [mask:={self.layout_mask}]>'

Ancestors

  • abc.ABC

Instance variables

var data : List[float]

Gets or sets a list of spline values. (List[float], read-only)

Expand source code
@property
def data(self) -> List[float]:
    """Gets or sets a list of spline values. (`List[float]`, read-only)"""
    return self.__data
var layout_mask : int

Gets a bitmask representation of the spline's data layout. (int, read-only)

Expand source code
@property
def layout_mask(self) -> int:
    """Gets a bitmask representation of the spline's data layout. (`int`, read-only)"""
    return self.__layout_mask
var tf : float

Final parameter for the last Catmull-Rom segment. (float, read-only)

Expand source code
@property
def tf(self) -> float:
    """Final parameter for the last Catmull-Rom segment. (`float`, read-only)"""
    return self.__tf
var ts : float

Start parameter for the first Catmull-Rom segment. (float, read-only)

Expand source code
@property
def ts(self) -> float:
    """Start parameter for the first Catmull-Rom segment. (`float`, read-only)"""
    return self.__ts
class Stroke (sid: uuid.UUID = None, sensor_data_offset: int = None, sensor_data_id: uuid.UUID = None, sensor_data_mapping: list = None, style: Style = None, random_seed: int = 0, property_index: int = 0, spline: Spline = None)

Stroke Geometry

The geometry of an ink stroke is represented by its Stroke. A Stroke is defined as a combination of:

- A Catmull-Rom spline in the form of a sequence of data points (mandatory), including per-point
  transformational data (optional)
- Rendering configuration about how the spline should be visualized (optional)
- Reference to raw input data (SensorData instance), which the path originates from (optional)

Parameters

sid: UUID
Stroke unique identifier
sensor_data_offset : int
Index of points mapping between raw and processed paths.
sensor_data_id : UUID
Reference UUID of sensor data.
sensor_data_mapping : list
Explicit mapping between indices of Path and SensorData, used when input rate is very high and provides unwanted points.
style : Style
The Style object associated with this stroke.
random_seed : int
A random seed used for randomly generated attributes of a stroke.
spline : Spline
The stroke's Catmull-Rom spline.

Examples

>>> from uim.model.inkdata.brush import BrushPolygon, BrushPolygonUri, RasterBrush, RotationMode, BlendMode
>>> from uim.model.inkdata.strokes import Spline, Style, Stroke, LayoutMask
>>>
>>> raster_brush_0: RasterBrush = RasterBrush(
>>> name="app://qa-test-app/raster-brush/MyRasterBrush",
>>> spacing=10., scattering=5., rotation=RotationMode.TRAJECTORY, shape_textures=[bytes([10, 20]),
>>>                                                                               bytes([30, 20])],
>>> fill_width=2.0, fill_height=0.3,
>>> fill_texture=bytes([10, 10, 20, 15, 17, 20, 25, 16, 34, 255, 23, 0, 34, 255, 23, 255]),
>>> randomize_fill=False, blend_mode=BlendMode.SOURCE_OVER)
>>> # Create a spline object - 9 data points, each consisting of X, Y, Size, Red, Green, Blue, Alpha
>>> spline_1: Spline = Spline(
>>>     LayoutMask.X.value | LayoutMask.Y.value | LayoutMask.SIZE.value | LayoutMask.RED.value | LayoutMask.GREEN.value
>>>     | LayoutMask.BLUE.value | LayoutMask.ALPHA.value,
>>>     [10.0, 10.7, 1.0, 0.5, 0.0, 0.1, 1.0,
>>>      21.0, 20.2, 2.0, 0.9, 0.4, 0.2, 0.8,
>>>      30.0, 12.4, 2.1, 0.7, 0.1, 0.1, 0.7,
>>>      40.0, 15.0, 1.5, 0.3, 0.5, 0.4, 1.0,
>>>      50.0, 45.0, 1.0, 0.3, 0.5, 0.4, 1.0,
>>>      41.0, 53.0, 1.1, 0.2, 0.3, 0.5, 0.9,
>>>      33.0, 73.0, 1.2, 0.6, 0.7, 0.4, 0.8,
>>>      20.0, 84.0, 1.3, 0.7, 0.8, 0.3, 0.7,
>>>      10.0, 91.0, 1.1, 0.7, 0.9, 0.2, 0.6]
>>> )
>>>
>>> # Create a style
>>> style_1: Style = Style(brush_uri=raster_brush_0.name)
>>> style_1.path_point_properties.rotation = 0.35
>>>
>>> # The render mode URI can also be app specific like app://blabla
>>> # The URI will://rasterization/3.0/blend-mode/SourceOver is assumed and must not be set.
>>> style_1.render_mode_uri = "will://rasterization/3.0/blend-mode/DestinationOver"
>>>
>>> # Create a stroke object. Note that it just exists, but is not in the model yet.
>>> stroke_1: Stroke = Stroke(UUIDIdentifier.id_generator(), spline=spline_1, style=style_1)
Expand source code
class Stroke(UUIDIdentifier):
    """
    Stroke Geometry
    ---------------
    The geometry of an ink stroke is represented by its Stroke.
    A Stroke is defined as a combination of:

        - A Catmull-Rom spline in the form of a sequence of data points (mandatory), including per-point
          transformational data (optional)
        - Rendering configuration about how the spline should be visualized (optional)
        - Reference to raw input data (SensorData instance), which the path originates from (optional)

    Parameters
    ----------
     sid: `UUID`
        Stroke unique identifier
    sensor_data_offset: `int`
        Index of points mapping between raw and processed paths.
    sensor_data_id: `UUID`
        Reference UUID of sensor data.
    sensor_data_mapping: list
        Explicit mapping between indices of Path and SensorData, used when input rate is very high and
        provides unwanted points.
    style: `Style`
        The Style object associated with this stroke.
    random_seed: `int`
        A random seed used for randomly generated attributes of a stroke.
    spline: `Spline`
        The stroke's Catmull-Rom spline.

    Examples
    --------
    >>> from uim.model.inkdata.brush import BrushPolygon, BrushPolygonUri, RasterBrush, RotationMode, BlendMode
    >>> from uim.model.inkdata.strokes import Spline, Style, Stroke, LayoutMask
    >>>
    >>> raster_brush_0: RasterBrush = RasterBrush(
    >>> name="app://qa-test-app/raster-brush/MyRasterBrush",
    >>> spacing=10., scattering=5., rotation=RotationMode.TRAJECTORY, shape_textures=[bytes([10, 20]),
    >>>                                                                               bytes([30, 20])],
    >>> fill_width=2.0, fill_height=0.3,
    >>> fill_texture=bytes([10, 10, 20, 15, 17, 20, 25, 16, 34, 255, 23, 0, 34, 255, 23, 255]),
    >>> randomize_fill=False, blend_mode=BlendMode.SOURCE_OVER)
    >>> # Create a spline object - 9 data points, each consisting of X, Y, Size, Red, Green, Blue, Alpha
    >>> spline_1: Spline = Spline(
    >>>     LayoutMask.X.value | LayoutMask.Y.value | LayoutMask.SIZE.value | LayoutMask.RED.value | LayoutMask.GREEN.value
    >>>     | LayoutMask.BLUE.value | LayoutMask.ALPHA.value,
    >>>     [10.0, 10.7, 1.0, 0.5, 0.0, 0.1, 1.0,
    >>>      21.0, 20.2, 2.0, 0.9, 0.4, 0.2, 0.8,
    >>>      30.0, 12.4, 2.1, 0.7, 0.1, 0.1, 0.7,
    >>>      40.0, 15.0, 1.5, 0.3, 0.5, 0.4, 1.0,
    >>>      50.0, 45.0, 1.0, 0.3, 0.5, 0.4, 1.0,
    >>>      41.0, 53.0, 1.1, 0.2, 0.3, 0.5, 0.9,
    >>>      33.0, 73.0, 1.2, 0.6, 0.7, 0.4, 0.8,
    >>>      20.0, 84.0, 1.3, 0.7, 0.8, 0.3, 0.7,
    >>>      10.0, 91.0, 1.1, 0.7, 0.9, 0.2, 0.6]
    >>> )
    >>>
    >>> # Create a style
    >>> style_1: Style = Style(brush_uri=raster_brush_0.name)
    >>> style_1.path_point_properties.rotation = 0.35
    >>>
    >>> # The render mode URI can also be app specific like app://blabla
    >>> # The URI will://rasterization/3.0/blend-mode/SourceOver is assumed and must not be set.
    >>> style_1.render_mode_uri = "will://rasterization/3.0/blend-mode/DestinationOver"
    >>>
    >>> # Create a stroke object. Note that it just exists, but is not in the model yet.
    >>> stroke_1: Stroke = Stroke(UUIDIdentifier.id_generator(), spline=spline_1, style=style_1)
    """

    def __init__(self, sid: uuid.UUID = None, sensor_data_offset: int = None, sensor_data_id: uuid.UUID = None,
                 sensor_data_mapping: list = None, style: Style = None, random_seed: int = 0, property_index: int = 0,
                 spline: Spline = None):
        super().__init__(sid)
        self.__start_parameter: float = 0.
        self.__end_parameter: float = 0.
        self.__spline_x: List[float] = []
        self.__spline_y: List[float] = []
        self.__spline_z: List[float] = []
        self.__size: List[float] = []
        self.__rotation: List[float] = []
        self.__scale_x: List[float] = []
        self.__scale_y: List[float] = []
        self.__scale_z: List[float] = []
        self.__offset_x: List[float] = []
        self.__offset_y: List[float] = []
        self.__offset_z: List[float] = []
        self.__red: List[int] = []
        self.__green: List[int] = []
        self.__blue: List[int] = []
        self.__alpha: List[int] = []
        self.__tangent_x: List[float] = []
        self.__tangent_y: List[float] = []
        self.__sensor_data_id: uuid.UUID = sensor_data_id
        self.__sensor_data_offset: int = sensor_data_offset or 0
        self._sensor_data_mapping: list = sensor_data_mapping or []
        self.__style: Style = style
        self.__random_seed: int = random_seed
        self.__properties_index: int = property_index
        self.__timestamp_cache = None
        self.__pressure_cache = None
        self.__precision_scheme: Optional[PrecisionScheme] = None
        if spline is not None:
            self.__import__(spline)

    def set_timestamp_values(self, timestamp_values):
        self.__timestamp_cache = timestamp_values

    def get_timestamp_values(self):
        return self.__timestamp_cache

    def set_pressure_values(self, pressure_values):
        self.__pressure_cache = pressure_values

    def get_pressure_values(self):
        return self.__pressure_cache

    @property
    def layout_mask(self) -> int:
        """Layout mask for the stroke. (`int`)"""
        mask: int = 0
        if len(self.__spline_x) > 0:
            mask |= LayoutMask.X.value
        if len(self.__spline_y) > 0:
            mask |= LayoutMask.Y.value
        if len(self.__spline_z) > 0:
            mask |= LayoutMask.Z.value
        if len(self.__size) > 0:
            mask |= LayoutMask.SIZE.value
        if len(self.__rotation) > 0:
            mask |= LayoutMask.ROTATION.value
        if len(self.__red) > 0:
            mask |= LayoutMask.RED.value
        if len(self.__green) > 0:
            mask |= LayoutMask.GREEN.value
        if len(self.__blue) > 0:
            mask |= LayoutMask.BLUE.value
        if len(self.__alpha) > 0:
            mask |= LayoutMask.ALPHA.value
        if len(self.__scale_x) > 0:
            mask |= LayoutMask.SCALE_X.value
        if len(self.__scale_y) > 0:
            mask |= LayoutMask.SCALE_Y.value
        if len(self.__scale_z) > 0:
            mask |= LayoutMask.SCALE_Z.value
        if len(self.__offset_x) > 0:
            mask |= LayoutMask.OFFSET_X.value
        if len(self.__offset_y) > 0:
            mask |= LayoutMask.OFFSET_Y.value
        if len(self.__offset_z) > 0:
            mask |= LayoutMask.OFFSET_Z.value
        if len(self.__tangent_x) > 0:
            mask |= LayoutMask.TANGENT_X.value
        if len(self.__tangent_y) > 0:
            mask |= LayoutMask.TANGENT_Y.value
        return mask

    @property
    def properties_index(self) -> int:
        """Setting the properties index. (`int`)"""
        return self.__properties_index

    @properties_index.setter
    def properties_index(self, properties_index: int):
        self.__properties_index = properties_index

    @property
    def start_parameter(self) -> float:
        """Start parameter of the stroke. (`float`)"""
        return self.__start_parameter

    @start_parameter.setter
    def start_parameter(self, s: float):
        self.__start_parameter = s

    @property
    def end_parameter(self) -> float:
        """End parameter of the stroke. (`float`)"""
        return self.__end_parameter

    @end_parameter.setter
    def end_parameter(self, e: float):
        self.__end_parameter = e

    @property
    def precision_scheme(self) -> PrecisionScheme:
        """An object that defines the precisions used for storing the stroke data. If set to None, stroke data is stored
        using floating point values. (`PrecisionScheme`)"""
        return self.__precision_scheme

    @precision_scheme.setter
    def precision_scheme(self, precision_scheme: PrecisionScheme):
        self.__precision_scheme = precision_scheme

    @property
    def sizes(self) -> List[float]:
        """List of size values. (`List[float]`)"""
        return self.__size

    @sizes.setter
    def sizes(self, size: list):
        self.__size = size

    @property
    def red(self) -> List[int]:
        """List of color values [0, 255] for red channel. (`List[int]`)"""
        return self.__red

    @red.setter
    def red(self, values: List[int]):
        self.__red = values

    @property
    def green(self) -> List[int]:
        """Color values [0, 255] for green channel. (`List[int]`)"""
        return self.__green

    @green.setter
    def green(self, values: List[int]):
        self.__green = values

    @property
    def blue(self) -> List[int]:
        """Color values [0, 255] for blue channel. (`List[int]`)"""
        return self.__blue

    @blue.setter
    def blue(self, values: List[int]):
        self.__blue = values

    @property
    def alpha(self) -> List[int]:
        """Color values [0, 255] for alpha channel. (`List[int]`)"""
        return self.__alpha

    @alpha.setter
    def alpha(self, values: List[int]):
        self.__alpha = values

    @property
    def rotations(self) -> List[float]:
        """List of rotations. (`List[float]`)"""
        return self.__rotation

    @rotations.setter
    def rotations(self, values: List[float]):
        self.__rotation = values

    @property
    def splines_x(self) -> List[float]:
        """List of splines x. (`List[float]`)"""
        return self.__spline_x

    @splines_x.setter
    def splines_x(self, spline_x: List[float]):
        self.__spline_x = spline_x

    @property
    def splines_y(self) -> List[float]:
        """List of splines y. (`List[float]`)"""
        return self.__spline_y

    @splines_y.setter
    def splines_y(self, spline_y: List[float]):
        self.__spline_y = spline_y

    @property
    def splines_z(self) -> List[float]:
        """List of splines z. (`List[float]`)"""
        return self.__spline_z

    @splines_z.setter
    def splines_z(self, spline_z: List[float]):
        self.__spline_z = spline_z

    @property
    def scales_x(self) -> List[float]:
        """List of x scales. (`List[float]`)"""
        return self.__scale_x

    @scales_x.setter
    def scales_x(self, scale: List[float]):
        self.__scale_x = scale

    @property
    def scales_y(self) -> List[float]:
        """List of y scales. (`List[float]`)"""
        return self.__scale_y

    @scales_y.setter
    def scales_y(self, scale: List[float]):
        self.__scale_y = scale

    @property
    def scales_z(self) -> List[float]:
        """List of z scales. (`List[float]`)"""
        return self.__scale_z

    @scales_z.setter
    def scales_z(self, scale: list):
        self.__scale_z = scale

    @property
    def offsets_x(self) -> List[float]:
        """List of x offsets."""
        return self.__offset_x

    @offsets_x.setter
    def offsets_x(self, offset: List[float]):
        self.__offset_x = offset

    @property
    def offsets_y(self) -> List[float]:
        """List of y offsets. (`List[float]`)"""
        return self.__offset_y

    @offsets_y.setter
    def offsets_y(self, offset: List[float]):
        self.__offset_y = offset

    @property
    def offsets_z(self) -> List[float]:
        """List of z offsets. (`List[float]`)"""
        return self.__offset_z

    @offsets_z.setter
    def offsets_z(self, offset: List[float]):
        self.__offset_z = offset

    @property
    def sensor_data_offset(self) -> int:
        """Index of points mapping between raw and processed paths. (`int`)"""
        return self.__sensor_data_offset

    @property
    def sensor_data_id(self) -> uuid.UUID:
        """Reference id sensor data. (`UUID`)"""
        return self.__sensor_data_id

    @property
    def sensor_data_mapping(self) -> List[int]:
        """Explicit mapping between indices of Path and SensorData, used when input rate is very high and
        provides unwanted points. (`List[int]`)"""
        return self._sensor_data_mapping

    @property
    def style(self) -> Style:
        """Style that is applied to the path of the stroke. (`Style`)"""
        return self.__style

    @style.setter
    def style(self, style: Style):
        self.__style = style

    @property
    def spline_min_x(self) -> float:
        """Minimum value of x spline. (`float`)"""
        return np.min(self.__spline_x)

    @property
    def spline_min_y(self) -> float:
        """Minimum value of y spline. (`float`)"""
        return np.min(self.__spline_y)

    @property
    def spline_max_x(self) -> float:
        """Maximum value of x spline. (`float`)"""
        return np.max(self.__spline_x)

    @property
    def spline_max_y(self) -> float:
        """Maximum value of y spline. (`float`)"""
        return np.max(self.__spline_y)

    @property
    def points_count(self) -> int:
        """Number of points of sample points. (`int`)"""
        return len(self.__spline_x)

    @property
    def bounding_box(self) -> BoundingBox:
        """Bounding box for path stroke. (`BoundingBox`)"""
        x_min = self.spline_min_x
        x_max = self.spline_max_x
        y_min = self.spline_min_y
        y_max = self.spline_max_y
        return BoundingBox(x=x_min, y=y_min, width=x_max - x_min, height=y_max - y_min)

    def __import__(self, spline: Spline):
        # The content from spline is imported with the appropriate Layout mask being set.
        self.__start_parameter = spline.ts
        self.__end_parameter = spline.tf
        idx: int = 0
        while idx < len(spline.data):
            if spline.layout_mask & LayoutMask.X.value:
                self.__spline_x.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.Y.value:
                self.__spline_y.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.Z.value:
                self.__spline_z.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.SIZE.value:
                self.__size.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.ROTATION.value:
                self.__rotation.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.RED.value:
                self.__red.append(int(spline.data[idx] * 255))
                idx += 1
            if spline.layout_mask & LayoutMask.GREEN.value:
                self.__green.append(int(spline.data[idx] * 255))
                idx += 1
            if spline.layout_mask & LayoutMask.BLUE.value:
                self.__blue.append(int(spline.data[idx] * 255))
                idx += 1
            if spline.layout_mask & LayoutMask.ALPHA.value:
                self.__alpha.append(int(spline.data[idx] * 255))
                idx += 1
            if spline.layout_mask & LayoutMask.SCALE_X.value:
                self.__scale_x.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.SCALE_Y.value:
                self.__scale_y.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.SCALE_Z.value:
                self.__scale_z.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.OFFSET_X.value:
                self.__offset_x.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.OFFSET_Y.value:
                self.__offset_y.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.OFFSET_Z.value:
                self.__offset_z.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.TANGENT_X.value:
                self.__tangent_x.append(spline.data[idx])
                idx += 1
            if spline.layout_mask & LayoutMask.TANGENT_Y.value:
                self.__tangent_y.append(spline.data[idx])
                idx += 1

    def __repr__(self):
        return f'<Stroke : [id:={self.id_h_form}], [num points:={self.points_count}, layout mask:={self.layout_mask}]>'

Ancestors

Class variables

var SEPARATOR : str

Instance variables

var alpha : List[int]

Color values [0, 255] for alpha channel. (List[int])

Expand source code
@property
def alpha(self) -> List[int]:
    """Color values [0, 255] for alpha channel. (`List[int]`)"""
    return self.__alpha
var blue : List[int]

Color values [0, 255] for blue channel. (List[int])

Expand source code
@property
def blue(self) -> List[int]:
    """Color values [0, 255] for blue channel. (`List[int]`)"""
    return self.__blue
var bounding_boxBoundingBox

Bounding box for path stroke. (BoundingBox)

Expand source code
@property
def bounding_box(self) -> BoundingBox:
    """Bounding box for path stroke. (`BoundingBox`)"""
    x_min = self.spline_min_x
    x_max = self.spline_max_x
    y_min = self.spline_min_y
    y_max = self.spline_max_y
    return BoundingBox(x=x_min, y=y_min, width=x_max - x_min, height=y_max - y_min)
var end_parameter : float

End parameter of the stroke. (float)

Expand source code
@property
def end_parameter(self) -> float:
    """End parameter of the stroke. (`float`)"""
    return self.__end_parameter
var green : List[int]

Color values [0, 255] for green channel. (List[int])

Expand source code
@property
def green(self) -> List[int]:
    """Color values [0, 255] for green channel. (`List[int]`)"""
    return self.__green
var layout_mask : int

Layout mask for the stroke. (int)

Expand source code
@property
def layout_mask(self) -> int:
    """Layout mask for the stroke. (`int`)"""
    mask: int = 0
    if len(self.__spline_x) > 0:
        mask |= LayoutMask.X.value
    if len(self.__spline_y) > 0:
        mask |= LayoutMask.Y.value
    if len(self.__spline_z) > 0:
        mask |= LayoutMask.Z.value
    if len(self.__size) > 0:
        mask |= LayoutMask.SIZE.value
    if len(self.__rotation) > 0:
        mask |= LayoutMask.ROTATION.value
    if len(self.__red) > 0:
        mask |= LayoutMask.RED.value
    if len(self.__green) > 0:
        mask |= LayoutMask.GREEN.value
    if len(self.__blue) > 0:
        mask |= LayoutMask.BLUE.value
    if len(self.__alpha) > 0:
        mask |= LayoutMask.ALPHA.value
    if len(self.__scale_x) > 0:
        mask |= LayoutMask.SCALE_X.value
    if len(self.__scale_y) > 0:
        mask |= LayoutMask.SCALE_Y.value
    if len(self.__scale_z) > 0:
        mask |= LayoutMask.SCALE_Z.value
    if len(self.__offset_x) > 0:
        mask |= LayoutMask.OFFSET_X.value
    if len(self.__offset_y) > 0:
        mask |= LayoutMask.OFFSET_Y.value
    if len(self.__offset_z) > 0:
        mask |= LayoutMask.OFFSET_Z.value
    if len(self.__tangent_x) > 0:
        mask |= LayoutMask.TANGENT_X.value
    if len(self.__tangent_y) > 0:
        mask |= LayoutMask.TANGENT_Y.value
    return mask
var offsets_x : List[float]

List of x offsets.

Expand source code
@property
def offsets_x(self) -> List[float]:
    """List of x offsets."""
    return self.__offset_x
var offsets_y : List[float]

List of y offsets. (List[float])

Expand source code
@property
def offsets_y(self) -> List[float]:
    """List of y offsets. (`List[float]`)"""
    return self.__offset_y
var offsets_z : List[float]

List of z offsets. (List[float])

Expand source code
@property
def offsets_z(self) -> List[float]:
    """List of z offsets. (`List[float]`)"""
    return self.__offset_z
var points_count : int

Number of points of sample points. (int)

Expand source code
@property
def points_count(self) -> int:
    """Number of points of sample points. (`int`)"""
    return len(self.__spline_x)
var precision_schemePrecisionScheme

An object that defines the precisions used for storing the stroke data. If set to None, stroke data is stored using floating point values. (PrecisionScheme)

Expand source code
@property
def precision_scheme(self) -> PrecisionScheme:
    """An object that defines the precisions used for storing the stroke data. If set to None, stroke data is stored
    using floating point values. (`PrecisionScheme`)"""
    return self.__precision_scheme
var properties_index : int

Setting the properties index. (int)

Expand source code
@property
def properties_index(self) -> int:
    """Setting the properties index. (`int`)"""
    return self.__properties_index
var red : List[int]

List of color values [0, 255] for red channel. (List[int])

Expand source code
@property
def red(self) -> List[int]:
    """List of color values [0, 255] for red channel. (`List[int]`)"""
    return self.__red
var rotations : List[float]

List of rotations. (List[float])

Expand source code
@property
def rotations(self) -> List[float]:
    """List of rotations. (`List[float]`)"""
    return self.__rotation
var scales_x : List[float]

List of x scales. (List[float])

Expand source code
@property
def scales_x(self) -> List[float]:
    """List of x scales. (`List[float]`)"""
    return self.__scale_x
var scales_y : List[float]

List of y scales. (List[float])

Expand source code
@property
def scales_y(self) -> List[float]:
    """List of y scales. (`List[float]`)"""
    return self.__scale_y
var scales_z : List[float]

List of z scales. (List[float])

Expand source code
@property
def scales_z(self) -> List[float]:
    """List of z scales. (`List[float]`)"""
    return self.__scale_z
var sensor_data_id : uuid.UUID

Reference id sensor data. (UUID)

Expand source code
@property
def sensor_data_id(self) -> uuid.UUID:
    """Reference id sensor data. (`UUID`)"""
    return self.__sensor_data_id
var sensor_data_mapping : List[int]

Explicit mapping between indices of Path and SensorData, used when input rate is very high and provides unwanted points. (List[int])

Expand source code
@property
def sensor_data_mapping(self) -> List[int]:
    """Explicit mapping between indices of Path and SensorData, used when input rate is very high and
    provides unwanted points. (`List[int]`)"""
    return self._sensor_data_mapping
var sensor_data_offset : int

Index of points mapping between raw and processed paths. (int)

Expand source code
@property
def sensor_data_offset(self) -> int:
    """Index of points mapping between raw and processed paths. (`int`)"""
    return self.__sensor_data_offset
var sizes : List[float]

List of size values. (List[float])

Expand source code
@property
def sizes(self) -> List[float]:
    """List of size values. (`List[float]`)"""
    return self.__size
var spline_max_x : float

Maximum value of x spline. (float)

Expand source code
@property
def spline_max_x(self) -> float:
    """Maximum value of x spline. (`float`)"""
    return np.max(self.__spline_x)
var spline_max_y : float

Maximum value of y spline. (float)

Expand source code
@property
def spline_max_y(self) -> float:
    """Maximum value of y spline. (`float`)"""
    return np.max(self.__spline_y)
var spline_min_x : float

Minimum value of x spline. (float)

Expand source code
@property
def spline_min_x(self) -> float:
    """Minimum value of x spline. (`float`)"""
    return np.min(self.__spline_x)
var spline_min_y : float

Minimum value of y spline. (float)

Expand source code
@property
def spline_min_y(self) -> float:
    """Minimum value of y spline. (`float`)"""
    return np.min(self.__spline_y)
var splines_x : List[float]

List of splines x. (List[float])

Expand source code
@property
def splines_x(self) -> List[float]:
    """List of splines x. (`List[float]`)"""
    return self.__spline_x
var splines_y : List[float]

List of splines y. (List[float])

Expand source code
@property
def splines_y(self) -> List[float]:
    """List of splines y. (`List[float]`)"""
    return self.__spline_y
var splines_z : List[float]

List of splines z. (List[float])

Expand source code
@property
def splines_z(self) -> List[float]:
    """List of splines z. (`List[float]`)"""
    return self.__spline_z
var start_parameter : float

Start parameter of the stroke. (float)

Expand source code
@property
def start_parameter(self) -> float:
    """Start parameter of the stroke. (`float`)"""
    return self.__start_parameter
var styleStyle

Style that is applied to the path of the stroke. (Style)

Expand source code
@property
def style(self) -> Style:
    """Style that is applied to the path of the stroke. (`Style`)"""
    return self.__style

Methods

def get_pressure_values(self)
Expand source code
def get_pressure_values(self):
    return self.__pressure_cache
def get_timestamp_values(self)
Expand source code
def get_timestamp_values(self):
    return self.__timestamp_cache
def set_pressure_values(self, pressure_values)
Expand source code
def set_pressure_values(self, pressure_values):
    self.__pressure_cache = pressure_values
def set_timestamp_values(self, timestamp_values)
Expand source code
def set_timestamp_values(self, timestamp_values):
    self.__timestamp_cache = timestamp_values

Inherited members

class Style (properties: PathPointProperties = None, brush_uri: str = None, particles_random_seed: int = 0, render_mode_uri: str = 'will://rasterization/3.0/blend-mode/SourceOver')

Style

The Style is defined as a combination of a PathPointProperties configuration, reference to a Brush, a random number generator seed value and rendering method type. Setting the Style property allows overriding of specific path point properties, color components and/or matrix transformational components. A Style with PathPointProperties configuration should be normally used to define constant path components.

Parameters

properties : PathPointProperties
Static values of properties which do not exist per per path point
brush_uri : str
Reference to Brush used for stroke rasterization
particles_random_seed : int
Particles random seed, required for particle strokes
render_mode_uri : str
Render mode URI
Expand source code
class Style(ABC):
    """
    Style
    =====
    The `Style` is defined as a combination of a `PathPointProperties` configuration, reference to a Brush,
    a random number generator seed value and rendering method type. Setting the Style property allows overriding of
    specific path point properties, color components and/or matrix transformational components.
    A `Style` with `PathPointProperties` configuration should be normally used to define constant path components.

    Parameters
    ----------
    properties: `PathPointProperties`
        Static values of properties which do not exist per per path point
    brush_uri: str
        Reference to Brush used for stroke rasterization
    particles_random_seed: int
        Particles random seed, required for particle strokes
    render_mode_uri: str
        Render mode URI
    """

    def __init__(self, properties: PathPointProperties = None, brush_uri: str = None, particles_random_seed: int = 0,
                 render_mode_uri: str = BlendModeURIs.SOURCE_OVER):
        self.__properties = properties if properties is not None else PathPointProperties()
        self.__brush_uri = brush_uri
        self.__particles_random_seed = particles_random_seed
        self.__render_mode_URI = render_mode_uri if render_mode_uri is not None and render_mode_uri != '' \
            else BlendModeURIs.SOURCE_OVER

    @property
    def path_point_properties(self) -> PathPointProperties:
        """Static values of properties which do not exist per per path point. (`PathPointProperties`, read-only)"""
        return self.__properties

    @property
    def brush_uri(self) -> str:
        """Reference to Brush used for stroke rasterization using the URI. (`str`, read-only)"""
        return self.__brush_uri

    @property
    def particles_random_seed(self):
        """Particles random seed, required for particle strokes.  (`int`, read-only) """
        return self.__particles_random_seed

    @property
    def render_mode_uri(self):
        """Defines additional information about stroke visualisation, such as ERASER. (`str`)"""
        return self.__render_mode_URI

    @render_mode_uri.setter
    def render_mode_uri(self, uri: str):
        self.__render_mode_URI = uri

    def __repr__(self):
        return '<Style : [id:={}, particles_random_seed:={}, render mode:={}>' \
            .format(self.__brush_uri,
                    self.__particles_random_seed,
                    self.__render_mode_URI)

Ancestors

  • abc.ABC

Instance variables

var brush_uri : str

Reference to Brush used for stroke rasterization using the URI. (str, read-only)

Expand source code
@property
def brush_uri(self) -> str:
    """Reference to Brush used for stroke rasterization using the URI. (`str`, read-only)"""
    return self.__brush_uri
var particles_random_seed

Particles random seed, required for particle strokes. (int, read-only)

Expand source code
@property
def particles_random_seed(self):
    """Particles random seed, required for particle strokes.  (`int`, read-only) """
    return self.__particles_random_seed
var path_point_propertiesPathPointProperties

Static values of properties which do not exist per per path point. (PathPointProperties, read-only)

Expand source code
@property
def path_point_properties(self) -> PathPointProperties:
    """Static values of properties which do not exist per per path point. (`PathPointProperties`, read-only)"""
    return self.__properties
var render_mode_uri

Defines additional information about stroke visualisation, such as ERASER. (str)

Expand source code
@property
def render_mode_uri(self):
    """Defines additional information about stroke visualisation, such as ERASER. (`str`)"""
    return self.__render_mode_URI