Add files via upload

This commit is contained in:
ThereforeGames 2022-12-11 17:59:59 -05:00 committed by GitHub
parent 685f9631b5
commit 00ca6a6db4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 15666 additions and 0 deletions

View File

@ -0,0 +1,489 @@
3.1.8
=====
recalculate bits used after all flags created (sometimes needed when a custom
`__new__` is in place.
3.1.7
=====
update flag creation to (possibly) add bitwise operator methods to newly
created flags
update extend_enum() to work with 3.11 flags
3.1.6
=====
Update `dir()` on mixed enums to include mixed data type methods and
attributes.
Rename `enum_property` to `property` to match stdlib. Recommended usage is
`aenum.property` (prefix with module name).
Remove quadritic creation behavior.
BREAKING CHANGE BUG FIX that won't affect most people
Enums with a custom `__new__` that:
- use the enum machinery to generate the values; AND
- have keyword arguments set to a default (like `None`)
will fail to generate a missing value. To fix: remove the default value and
instead specify it on the member creation line.
BREAKING CHANGE
In Python 3.11 the `str()` of mixed enums will now match its `format()` which
will be the normal `str()` of the data type -- so for an IntEnum you'll see
`5` instead of `Perm.R|X`. This affects IntEnum, StrEnum, and IntFlag.
3.1.5
=====
fix support of `auto()` kwds
3.1.3
=====
rename `aenum.property` to `aenum.enum_property`
fix `enum_property` to work with `_init_` attributes
3.1.2
=====
fix `extend_enum()` for unhashable values
3.1.1
=====
fix `extend_enum()` for most cases
3.1.0
=====
AddValue is similar to the old AutoNumber: it will always activate, but
uses _generate_next_value_ to get the next value (so the user has some
control over the return data type instead of always getting an int).
BREAKING CHANGES
AutoValue is gone. It was superflous and its removal simplified the code.
Simply put the fields needed in an `_init_` and `_generate_next_value_`
will be called to supply the missing values (this is probably already what
is happening).
3.0.0
=====
standard Enum usage is unchanged
BREAKING CHANGES
Enum
- the more esoteric method of creating Enums have been modified or removed
- AutoNumber setting is gone, inherit from AutoNumberEnum instead
- creating members without specifying anything is removed (if you don't
know what this means, you weren't doing it)
Flag
- unique flags are canonical (i.e. flags with powers of two values such as
1, 2, 4, 8, 16, etc.)
- non-unique flags are aliases (i.e. values such as 3 or 7)
- iteration of Flag and flag members only uses canonical flags
ENHANCEMENTS
Member creation has been redone to match Python 3.10's methods. This also
allows all supported Pythons (2.7, 3.3+) to use the __set_name__ and
__init_subclass__ protocols (more robustly than in aenum 2.2.5)
CHANGES
enum_property() has been renamed to property() (old name still available, but
deprecated).
bin() replacement shows negative integers in twos-complement
2.2.5
=====
call __init_subclass__ after members have been added, and in Pythons < 3.6
call __set_name__ in Pythons < 3.6
do not convert/disallow private names
add iteration/len support to NamedConstant
2.2.4
=====
add support to Constant to retrieve members by value
--> class K(Constant):
... one = 1
... two = 2
--> K.one
<K.one: 1>
--> K(1)
<K.one: 1>
add pickle/deepcopy support to Constant
add support for Constant to use other Constant values
(resulting members /are not/ the same)
--> class C(Constant)
... one = K.one
... three = 3
--> C.one == K.one
True
--> C.one is K.one
False
AutoNumber and auto() now work together
Enum members are now added to the class as enum_property, which supports
unshadowing of parent class attributes when called on an Enum member:
--> class StrEnum(str, Enum):
... lower = 'lower'
... upper = 'upper'
... mixed = 'mixed'
--> StrEnum.lower
<StrEnum.lower: 'lower'>
--> StrEnum.lower.upper()
'LOWER'
--> StrEnum.upper
<StrEnum.upper: 'upper'>
--> StrEnum.upper.upper()
'UPPER'
2.2.3
=====
use members' type's methods __str__, __repr__, __format__, and
__reduce_ex__ if directly assigned in Enum class body; i.e.:
--> class Color(str, Enum):
... red = 'red'
... green = 'green'
... blue = 'blue'
... __str__ = str.__str__
--> print(repr(Color.green))
<Color.green: 'green'>
--> print(Color.green)
green
2.2.2
=====
replace _RouteClassAttributeToGetattr with enum_property (it is still
available as an alias)
support constant() and auto() being used together:
--> class Fruit(Flag):
... _order_ = 'apple banana lemon orange'
... apple = auto()
... banana = auto()
... lemon = auto()
... orange = auto()
... CitrusTypes = constant(lemon | orange)
--> list(Fruit)
[Fruit.apple, Fruit.banana, Fruit.lemon, Fruit.orange]
--> list(Fruit.CitrusTypes)
[Fruit.orange, Fruit.lemon]
--> Fruit.orange in Fruit.CitrusTypes
True
2.2.1
=====
allow Enums to be called without a value
class Color(Enum):
black = 0
red = 1
green = 2
blue = 3
#
@classmethod
def _missing_value_(cls, value):
if value is no_arg:
return cls.black
>>> Color()
<Color.black: 0>
allow Enum name use while constructing Enum (Python 3.4+ only)
--> class Color(Enum):
... _order_ = 'BLACK WHITE'
... BLACK = Color('black', '#000')
... WHITE = Color('white', '#fff')
... #
... def __init__(self, label, hex):
... self.label = label
... self.hex = hex
2.2.0
=====
BREAKING CHANGE
---------------
In Python 3+ classes defined inside an Enum no longer become members by
default; in Python 2 they still become members, but see below.
For cross-compatibility and full control two decorators are provided:
- @member --> forces item to become a member
- @nonmember --> excludes item from becoming a member
So to have an Enum that behaves the same in Python 2 and 3, use the
decorators (and other compatibility shims):
class Color(Enum):
_order_ = 'red green blue'
red = 1
green = 2
blue = 3
@nonmember
class Shades(Enum):
_order_ = 'light medium dark'
light = 1
medium = 2
dark = 3
2.1.4
=====
EnumMeta:
- change __member_new__ to __new_member__ (as the stdlib enum does)
- assign member name to enum() instances (an Enum helper for defining members)
- handle empty iterables when using functional API
- make auto() work with previous enum members
- keep searching mixins until base class is found
Enum:
- fix bug in Flag checks (ensure it is a Flag before checking the name)
- add multiple mixin support
- do not allow blank names (functional API)
- raise TypeError if _missing_* returns wrong type
- fix __format__ to honor custom __str__
extend_enum:
- support stdlib Enums
- use _generate_next_value_ if value not provided
general:
- standardize exception formatting
- use getfullargspec() in Python 3 (avoids deprecation warnings)
2.1.2
=====
when order is callable, save it for subclass use
2.1.1
=====
correctly raise TypeError for non-Enum containment checks
support combining names with | for Flag key access
support _order_ being a callable
2.1.0
=====
support Flags being combined with other data types:
- add _create_pseudo_member_values_
- add default __new__ and temporary _init_
2.0.10
======
ensure _ignore_ is set when _settings_ specified in body which includes
AutoValue
make Flag members iterable
2.0.9
=====
fix missing comma in __all__
fix extend_enum with custom __new__ methods
fix MultiValue with AutoNumber without _init_
2.0.8
=====
extend_enum now handles aliases and multivalues correctly
2.0.7
=====
support mixin types with extend_enum
init and AutoNumber can now work together
add test for new Enum using EnumMeta
add tests for variations of multivalue and init
prevent deletion of NamedConstant.constant
2.0.6
=====
constants cannot be deleted (they already couldn't be changed)
constants can be used to define other constants
2.0.5
=====
_init_ and MultiValue can now work together
2.0.4
=====
_init_ and AutoValue (and _generate_next_value_) can now work together to
supply missing values even when some of the required values per member are
absent
2.0.3
=====
add _missing_value_ and _missing_name_ methods, deprecate _missing_
make enum instances comparable
2.0.2
=====
both EnumMeta.__getattr__ and Enum.__new__ fall back to _missing_
2.0.1
=====
auto() now works with other data types
AutoNumber supports legacy Enums (fixed regression)
2.0.0
=====
Flag and IntFlag added.
1.4.7
=====
fix %-interpolation bug
defined SqlLiteEnum only if sqlite exists
support pyflakes
1.4.6
=====
version numbering error
1.4.5
=====
revert AutoNumberEnum to custom __new__ instead of AutoNumber
use _ignore_ to shield against AutoNumber magic
inherit start and init settings from base Enums
1.4.4
=====
enabled export as a decorator
enabled _order_ to replace __order__
enabled python2 support for settings, init, and start
1.4.3
=====
support _ignore_ for dynamically creating class bodies
1.4.2
=====
MultiValue, NoAlias, Unique, and init now work with Python 2
1.4.1
=====
Py3: added Enum creation flags: Auto, MultiValue, NoAlias, Unique
fixed extend_enum to honor Enum flags
1.4.0
=====
When possible aenum inherits from Python's own enum.
Breaking change: enum members now default to evaluating as True to maintain
compatibility with the stdlib.
Add your own __bool__ (__nonzero__ in Python 2) if need this behavior:
def __bool__(self):
return bool(self.value)
__nonzero__ = __bool__

View File

@ -0,0 +1,32 @@
Copyright (c) 2015, 2016, 2017, 2018 Ethan Furman.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
Neither the name Ethan Furman nor the names of any
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
from operator import div as _div_
from inspect import getargspec
def raise_with_traceback(exc, tb):
raise exc, None, tb
__all__ = ['_div_', 'getargspec', 'raise_with_traceback']

View File

@ -0,0 +1,12 @@
from inspect import getfullargspec as _getfullargspec
def getargspec(method):
args, varargs, keywords, defaults, _, _, _ = _getfullargspec(method)
return args, varargs, keywords, defaults
def raise_with_traceback(exc, tb):
raise exc.with_traceback(tb)
def raise_from_none(exc):
raise exc from None

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
"""Use this module to apply a number of blending modes to a background and foreground image
"""

View File

@ -0,0 +1,511 @@
"""Provide blending functions and types.
Adapted from https://github.com/addisonElliott/pypdn/blob/master/pypdn/reader.py
and https://gitlab.com/inklabapp/pyora/-/blob/master/pyora/BlendNonSep.py
MIT License Copyright (c) 2020 FredHappyface
Credits to:
MIT License Copyright (c) 2019 Paul Jewell
For implementing blending from the Open Raster Image Spec
MIT License Copyright (c) 2018 Addison Elliott
For implementing blending from Paint.NET
MIT License Copyright (c) 2017 pashango
For implementing a number of blending functions used by other popular image
editors
"""
from __future__ import annotations
import warnings
import numpy as np
from PIL import Image
from .blendtype import BlendType
def normal(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.NORMAL."""
del background # we don't care about this
return foreground
def multiply(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.MULTIPLY."""
return np.clip(foreground * background, 0.0, 1.0)
def additive(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.ADDITIVE."""
return np.minimum(background + foreground, 1.0)
def colourburn(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.COLOURBURN."""
with np.errstate(divide="ignore"):
return np.where(
foreground != 0.0, np.maximum(1.0 - ((1.0 - background) / foreground), 0.0), 0.0
)
def colourdodge(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.COLOURDODGE."""
with np.errstate(divide="ignore"):
return np.where(foreground != 1.0, np.minimum(background / (1.0 - foreground), 1.0), 1.0)
def reflect(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.REFLECT."""
with np.errstate(divide="ignore"):
return np.where(
foreground != 1.0, np.minimum((background ** 2) / (1.0 - foreground), 1.0), 1.0
)
def glow(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.GLOW."""
with np.errstate(divide="ignore"):
return np.where(
background != 1.0, np.minimum((foreground ** 2) / (1.0 - background), 1.0), 1.0
)
def overlay(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.OVERLAY."""
return np.where(
background < 0.5,
2 * background * foreground,
1.0 - (2 * (1.0 - background) * (1.0 - foreground)),
)
def difference(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.DIFFERENCE."""
return np.abs(background - foreground)
def negation(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.NEGATION."""
return np.maximum(background - foreground, 0.0)
def lighten(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.LIGHTEN."""
return np.maximum(background, foreground)
def darken(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.DARKEN."""
return np.minimum(background, foreground)
def screen(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.SCREEN."""
return background + foreground - background * foreground
def xor(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.XOR."""
# XOR requires int values so convert to uint8
with warnings.catch_warnings():
warnings.simplefilter("ignore")
return imageIntToFloat(imageFloatToInt(background) ^ imageFloatToInt(foreground))
def softlight(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.SOFTLIGHT."""
return (1.0 - background) * background * foreground + background * (
1.0 - (1.0 - background) * (1.0 - foreground)
)
def hardlight(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.HARDLIGHT."""
return np.where(
foreground < 0.5,
np.minimum(background * 2 * foreground, 1.0),
np.minimum(1.0 - ((1.0 - background) * (1.0 - (foreground - 0.5) * 2.0)), 1.0),
)
def grainextract(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.GRAINEXTRACT."""
return np.clip(background - foreground + 0.5, 0.0, 1.0)
def grainmerge(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.GRAINMERGE."""
return np.clip(background + foreground - 0.5, 0.0, 1.0)
def divide(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.DIVIDE."""
return np.minimum((256.0 / 255.0 * background) / (1.0 / 255.0 + foreground), 1.0)
def pinlight(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.PINLIGHT."""
return np.minimum(background, 2 * foreground) * (foreground < 0.5) + np.maximum(
background, 2 * (foreground - 0.5)
) * (foreground >= 0.5)
def vividlight(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.VIVIDLIGHT."""
return colourburn(background, foreground * 2) * (foreground < 0.5) + colourdodge(
background, 2 * (foreground - 0.5)
) * (foreground >= 0.5)
def exclusion(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.EXCLUSION."""
return background + foreground - (2.0 * background * foreground)
def _lum(colours: np.ndarray) -> np.ndarray:
"""Luminosity.
:param colours: x by x by 3 matrix of rgb color components of pixels
:return: x by x by 3 matrix of luminosity of pixels
"""
return (colours[:, :, 0] * 0.299) + (colours[:, :, 1] * 0.587) + (colours[:, :, 2] * 0.114)
def _setLum(originalColours: np.ndarray, newLuminosity: np.ndarray) -> np.ndarray:
"""Set a new luminosity value for the matrix of color."""
_colours = originalColours.copy()
_luminosity = _lum(_colours)
deltaLum = newLuminosity - _luminosity
_colours[:, :, 0] += deltaLum
_colours[:, :, 1] += deltaLum
_colours[:, :, 2] += deltaLum
_luminosity = _lum(_colours)
_minColours = np.min(_colours, axis=2)
_MaxColours = np.max(_colours, axis=2)
for i in range(_colours.shape[0]):
for j in range(_colours.shape[1]):
_colour = _colours[i][j]
newLuminosity = _luminosity[i, j]
minColour = _minColours[i, j]
maxColour = _MaxColours[i, j]
if minColour < 0:
_colours[i][j] = newLuminosity + (
((_colour - newLuminosity) * newLuminosity) / (newLuminosity - minColour)
)
if maxColour > 1:
_colours[i][j] = newLuminosity + (
((_colour - newLuminosity) * (1 - newLuminosity)) / (maxColour - newLuminosity)
)
return _colours
def _sat(colours: np.ndarray) -> np.ndarray:
"""Saturation.
:param colours: x by x by 3 matrix of rgb color components of pixels
:return: int of saturation of pixels
"""
return np.max(colours, axis=2) - np.min(colours, axis=2)
def _setSat(originalColours: np.ndarray, newSaturation: np.ndarray) -> np.ndarray:
"""Set a new saturation value for the matrix of color.
The current implementation cannot be vectorized in an efficient manner,
so it is very slow,
O(m*n) at least. This might be able to be improved with openCL if that is
the direction that the lib takes.
:param c: x by x by 3 matrix of rgb color components of pixels
:param s: int of the new saturation value for the matrix
:return: x by x by 3 matrix of luminosity of pixels
"""
_colours = originalColours.copy()
for i in range(_colours.shape[0]):
for j in range(_colours.shape[1]):
_colour = _colours[i][j]
minI = 0
midI = 1
maxI = 2
if _colour[midI] < _colour[minI]:
minI, midI = midI, minI
if _colour[maxI] < _colour[midI]:
midI, maxI = maxI, midI
if _colour[midI] < _colour[minI]:
minI, midI = midI, minI
if _colour[maxI] - _colour[minI] > 0.0:
_colours[i][j][midI] = ((_colour[midI] - _colour[minI]) * newSaturation[i, j]) / (
_colour[maxI] - _colour[minI]
)
_colours[i][j][maxI] = newSaturation[i, j]
else:
_colours[i][j][midI] = 0
_colours[i][j][maxI] = 0
_colours[i][j][minI] = 0
return _colours
def hue(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.HUE."""
return _setLum(_setSat(foreground, _sat(background)), _lum(background))
def saturation(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.SATURATION."""
return _setLum(_setSat(background, _sat(foreground)), _lum(background))
def colour(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.COLOUR."""
return _setLum(foreground, _lum(background))
def luminosity(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.LUMINOSITY."""
return _setLum(background, _lum(foreground))
def destin(
backgroundAlpha: np.ndarray,
foregroundAlpha: np.ndarray,
backgroundColour: np.ndarray,
foregroundColour: np.ndarray,
):
"""'clip' composite mode.
All parts of 'layer above' which are alpha in 'layer below' will be made
also alpha in 'layer above'
(to whatever degree of alpha they were)
Destination which overlaps the source, replaces the source.
Fa = 0; Fb = αs
co = αb x Cb x αs
αo = αb x αs
"""
del foregroundColour # Not used by function
outAlpha = backgroundAlpha * foregroundAlpha
with np.errstate(divide="ignore", invalid="ignore"):
outRGB = np.divide(
np.multiply((backgroundAlpha * foregroundAlpha)[:, :, None], backgroundColour),
outAlpha[:, :, None],
)
return outRGB, outAlpha
def destout(
backgroundAlpha: np.ndarray,
foregroundAlpha: np.ndarray,
backgroundColour: np.ndarray,
foregroundColour: np.ndarray,
):
"""Reverse 'Clip' composite mode.
All parts of 'layer below' which are alpha in 'layer above' will be made
also alpha in 'layer below'
(to whatever degree of alpha they were)
"""
del foregroundColour # Not used by function
outAlpha = backgroundAlpha * (1 - foregroundAlpha)
with np.errstate(divide="ignore", invalid="ignore"):
outRGB = np.divide(
np.multiply((backgroundAlpha * (1 - foregroundAlpha))[:, :, None], backgroundColour),
outAlpha[:, :, None],
)
return outRGB, outAlpha
def destatop(
backgroundAlpha: np.ndarray,
foregroundAlpha: np.ndarray,
backgroundColour: np.ndarray,
foregroundColour: np.ndarray,
):
"""Place the layer below above the 'layer above' in places where the 'layer above' exists...
where 'layer below' does not exist, but 'layer above' does, place 'layer-above'
"""
outAlpha = (foregroundAlpha * (1 - backgroundAlpha)) + (backgroundAlpha * foregroundAlpha)
with np.errstate(divide="ignore", invalid="ignore"):
outRGB = np.divide(
np.multiply((foregroundAlpha * (1 - backgroundAlpha))[:, :, None], foregroundColour)
+ np.multiply((backgroundAlpha * foregroundAlpha)[:, :, None], backgroundColour),
outAlpha[:, :, None],
)
return outRGB, outAlpha
def srcatop(
backgroundAlpha: np.ndarray,
foregroundAlpha: np.ndarray,
backgroundColour: np.ndarray,
foregroundColour: np.ndarray,
):
"""Place the layer below above the 'layer above' in places where the 'layer above' exists."""
outAlpha = (foregroundAlpha * backgroundAlpha) + (backgroundAlpha * (1 - foregroundAlpha))
with np.errstate(divide="ignore", invalid="ignore"):
outRGB = np.divide(
np.multiply((foregroundAlpha * backgroundAlpha)[:, :, None], foregroundColour)
+ np.multiply((backgroundAlpha * (1 - foregroundAlpha))[:, :, None], backgroundColour),
outAlpha[:, :, None],
)
return outRGB, outAlpha
def imageIntToFloat(image: np.ndarray) -> np.ndarray:
"""Convert a numpy array representing an image to an array of floats.
Args:
image (np.ndarray): numpy array of ints
Returns:
np.ndarray: numpy array of floats
"""
return image / 255
def imageFloatToInt(image: np.ndarray) -> np.ndarray:
"""Convert a numpy array representing an image to an array of ints.
Args:
image (np.ndarray): numpy array of floats
Returns:
np.ndarray: numpy array of ints
"""
return (image * 255).astype(np.uint8)
def blend(background: np.ndarray, foreground: np.ndarray, blendType: BlendType) -> np.ndarray:
"""Blend pixels.
Args:
background (np.ndarray): background
foreground (np.ndarray): foreground
blendType (BlendType): the blend type
Returns:
np.ndarray: new array representing the image
background: np.ndarray,
foreground: np.ndarray and the return are in the form
[[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]
...
[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
...
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]
...
[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]]
"""
blendLookup = {
BlendType.NORMAL: normal,
BlendType.MULTIPLY: multiply,
BlendType.COLOURBURN: colourburn,
BlendType.COLOURDODGE: colourdodge,
BlendType.REFLECT: reflect,
BlendType.OVERLAY: overlay,
BlendType.DIFFERENCE: difference,
BlendType.LIGHTEN: lighten,
BlendType.DARKEN: darken,
BlendType.SCREEN: screen,
BlendType.SOFTLIGHT: softlight,
BlendType.HARDLIGHT: hardlight,
BlendType.GRAINEXTRACT: grainextract,
BlendType.GRAINMERGE: grainmerge,
BlendType.DIVIDE: divide,
BlendType.HUE: hue,
BlendType.SATURATION: saturation,
BlendType.COLOUR: colour,
BlendType.LUMINOSITY: luminosity,
BlendType.XOR: xor,
BlendType.NEGATION: negation,
BlendType.PINLIGHT: pinlight,
BlendType.VIVIDLIGHT: vividlight,
BlendType.EXCLUSION: exclusion,
}
if blendType not in blendLookup:
return normal(background, foreground)
return blendLookup[blendType](background, foreground)
def blendLayers(
background: Image.Image,
foreground: Image.Image,
blendType: BlendType | tuple[str, ...],
opacity: float = 1.0,
) -> Image.Image:
"""Blend layers using numpy array.
Args:
background (Image.Image): background layer
foreground (Image.Image): foreground layer (must be same size as background)
blendType (BlendType): The blendtype
opacity (float): The opacity of the foreground image
Returns:
Image.Image: combined image
"""
# Convert the Image.Image to a numpy array
npForeground: np.ndarray = imageIntToFloat(np.array(foreground.convert("RGBA")))
npBackground: np.ndarray = imageIntToFloat(np.array(background.convert("RGBA")))
# Get the alpha from the layers
backgroundAlpha = npBackground[:, :, 3]
foregroundAlpha = npForeground[:, :, 3] * opacity
combinedAlpha = backgroundAlpha * foregroundAlpha
# Get the colour from the layers
backgroundColor = npBackground[:, :, 0:3]
foregroundColor = npForeground[:, :, 0:3]
# Some effects require alpha
alphaFunc = {
BlendType.DESTIN: destin,
BlendType.DESTOUT: destout,
BlendType.SRCATOP: srcatop,
BlendType.DESTATOP: destatop,
}
if blendType in alphaFunc:
return Image.fromarray(
imageFloatToInt(
np.clip(
np.dstack(
alphaFunc[blendType](
backgroundAlpha, foregroundAlpha, backgroundColor, foregroundColor
)
),
a_min=0,
a_max=1,
)
)
)
# Get the colours and the alpha for the new image
colorComponents = (
(backgroundAlpha - combinedAlpha)[:, :, None] * backgroundColor
+ (foregroundAlpha - combinedAlpha)[:, :, None] * foregroundColor
+ combinedAlpha[:, :, None] * blend(backgroundColor, foregroundColor, blendType)
)
alphaComponent = backgroundAlpha + foregroundAlpha - combinedAlpha
return Image.fromarray(
imageFloatToInt(np.clip(np.dstack((colorComponents, alphaComponent)), a_min=0, a_max=1))
)

View File

@ -0,0 +1,72 @@
"""Specify supported blend types."""
from __future__ import annotations
from aenum import MultiValueEnum
class BlendType(str, MultiValueEnum):
"""Specify supported blend types.
NORMAL = "bm:normal", "normal"
MULTIPLY = "bm:multiply", "multiply"
ADDITIVE = "bm:additive", "additive"
COLOURBURN = "bm:colourburn", "colourburn"
COLOURDODGE = "bm:colourdodge", "colourdodge"
REFLECT = "bm:reflect", "reflect"
GLOW = "bm:glow", "glow"
OVERLAY = "bm:overlay", "overlay"
DIFFERENCE = "bm:difference", "difference"
NEGATION = "bm:negation", "negation"
LIGHTEN = "bm:lighten", "lighten"
DARKEN = "bm:darken", "darken"
SCREEN = "bm:screen", "screen"
XOR = "bm:xor", "xor"
SOFTLIGHT = "bm:softlight", "softlight"
HARDLIGHT = "bm:hardlight", "hardlight"
GRAINEXTRACT = "bm:grainextract", "grainextract"
GRAINMERGE = "bm:grainmerge", "grainmerge"
DIVIDE = "bm:divide", "divide"
HUE = "bm:hue", "hue"
SATURATION = "bm:saturation", "saturation"
COLOUR = "bm:colour", "colour"
LUMINOSITY = "bm:luminosity", "luminosity"
PINLIGHT = "bm:pinlight", "pinlight"
VIVIDLIGHT = "bm:vividlight", "vividlight"
EXCLUSION = "bm:exclusion", "exclusion"
DESTIN = "bm:destin", "destin"
DESTOUT = "bm:destout", "destout"
SRCATOP = "bm:srcatop", "srcatop"
DESTATOP = "bm:destatop", "destatop"
"""
NORMAL = "bm:normal", "normal"
MULTIPLY = "bm:multiply", "multiply"
ADDITIVE = "bm:additive", "additive"
COLOURBURN = "bm:colourburn", "colourburn"
COLOURDODGE = "bm:colourdodge", "colourdodge"
REFLECT = "bm:reflect", "reflect"
GLOW = "bm:glow", "glow"
OVERLAY = "bm:overlay", "overlay"
DIFFERENCE = "bm:difference", "difference"
NEGATION = "bm:negation", "negation"
LIGHTEN = "bm:lighten", "lighten"
DARKEN = "bm:darken", "darken"
SCREEN = "bm:screen", "screen"
XOR = "bm:xor", "xor"
SOFTLIGHT = "bm:softlight", "softlight"
HARDLIGHT = "bm:hardlight", "hardlight"
GRAINEXTRACT = "bm:grainextract", "grainextract"
GRAINMERGE = "bm:grainmerge", "grainmerge"
DIVIDE = "bm:divide", "divide"
HUE = "bm:hue", "hue"
SATURATION = "bm:saturation", "saturation"
COLOUR = "bm:colour", "colour"
LUMINOSITY = "bm:luminosity", "luminosity"
PINLIGHT = "bm:pinlight", "pinlight"
VIVIDLIGHT = "bm:vividlight", "vividlight"
EXCLUSION = "bm:exclusion", "exclusion"
DESTIN = "bm:destin", "destin"
DESTOUT = "bm:destout", "destout"
SRCATOP = "bm:srcatop", "srcatop"
DESTATOP = "bm:destatop", "destatop"

View File

@ -0,0 +1,48 @@
"""Do stuff to images to prepare them.
"""
from __future__ import annotations
import warnings
from deprecation import deprecated
from PIL import Image
@deprecated(deprecated_in="2021.1", removed_in="", details="use renderWAlphaOffset")
def rasterImageOA( # pylint:disable=missing-function-docstring
image: Image.Image, size: tuple[int, int], alpha: float = 1.0, offsets: tuple[int, int] = (0, 0)
) -> Image.Image:
warnings.warn(
"Call to deprecated function rasterImageOA.", category=DeprecationWarning, stacklevel=2
)
return renderWAlphaOffset(image, size, alpha, offsets)
@deprecated(deprecated_in="2021.1", removed_in="", details="use renderWAlphaOffset")
def rasterImageOffset( # pylint:disable=missing-function-docstring
image: Image.Image, size: tuple[int, int], offsets: tuple[int, int] = (0, 0)
) -> Image.Image:
warnings.warn(
"Call to deprecated function rasterImageOffset.", category=DeprecationWarning, stacklevel=2
)
return renderWAlphaOffset(image, size, 1, offsets)
def renderWAlphaOffset(
image: Image.Image, size: tuple[int, int], alpha: float = 1.0, offsets: tuple[int, int] = (0, 0)
) -> Image.Image:
"""Render an image with offset and alpha to a given size.
Args:
image (Image.Image): pil image to draw
size (tuple[int, int]): width, height as a tuple
alpha (float, optional): alpha transparency. Defaults to 1.0.
offsets (tuple[int, int], optional): x, y offsets as a tuple.
Defaults to (0, 0).
Returns:
Image.Image: new image
"""
imageOffset = Image.new("RGBA", size)
imageOffset.paste(image.convert("RGBA"), offsets, image.convert("RGBA"))
return Image.blend(Image.new("RGBA", size), imageOffset, alpha)