Add files via upload
This commit is contained in:
parent
685f9631b5
commit
00ca6a6db4
489
venv/Lib/site-packages/aenum/CHANGES
Normal file
489
venv/Lib/site-packages/aenum/CHANGES
Normal 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__
|
||||||
|
|
32
venv/Lib/site-packages/aenum/LICENSE
Normal file
32
venv/Lib/site-packages/aenum/LICENSE
Normal 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.
|
4111
venv/Lib/site-packages/aenum/__init__.py
Normal file
4111
venv/Lib/site-packages/aenum/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
venv/Lib/site-packages/aenum/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/aenum/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/aenum/__pycache__/_py3.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/aenum/__pycache__/_py3.cpython-39.pyc
Normal file
Binary file not shown.
7
venv/Lib/site-packages/aenum/_py2.py
Normal file
7
venv/Lib/site-packages/aenum/_py2.py
Normal 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']
|
12
venv/Lib/site-packages/aenum/_py3.py
Normal file
12
venv/Lib/site-packages/aenum/_py3.py
Normal 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
|
||||||
|
|
1568
venv/Lib/site-packages/aenum/doc/aenum.rst
Normal file
1568
venv/Lib/site-packages/aenum/doc/aenum.rst
Normal file
File diff suppressed because it is too large
Load Diff
6832
venv/Lib/site-packages/aenum/test.py
Normal file
6832
venv/Lib/site-packages/aenum/test.py
Normal file
File diff suppressed because it is too large
Load Diff
1982
venv/Lib/site-packages/aenum/test_v3.py
Normal file
1982
venv/Lib/site-packages/aenum/test_v3.py
Normal file
File diff suppressed because it is too large
Load Diff
2
venv/Lib/site-packages/blendmodes/__init__.py
Normal file
2
venv/Lib/site-packages/blendmodes/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
"""Use this module to apply a number of blending modes to a background and foreground image
|
||||||
|
"""
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
511
venv/Lib/site-packages/blendmodes/blend.py
Normal file
511
venv/Lib/site-packages/blendmodes/blend.py
Normal 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))
|
||||||
|
)
|
72
venv/Lib/site-packages/blendmodes/blendtype.py
Normal file
72
venv/Lib/site-packages/blendmodes/blendtype.py
Normal 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"
|
48
venv/Lib/site-packages/blendmodes/imagetools.py
Normal file
48
venv/Lib/site-packages/blendmodes/imagetools.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user