Delete venv/Lib/site-packages directory
This commit is contained in:
parent
2e8b5418e3
commit
9170224d23
|
@ -1,489 +0,0 @@
|
|||
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__
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
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
Binary file not shown.
Binary file not shown.
|
@ -1,7 +0,0 @@
|
|||
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']
|
|
@ -1,12 +0,0 @@
|
|||
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
|
@ -1,2 +0,0 @@
|
|||
"""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.
|
@ -1,511 +0,0 @@
|
|||
"""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))
|
||||
)
|
|
@ -1,72 +0,0 @@
|
|||
"""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"
|
|
@ -1,48 +0,0 @@
|
|||
"""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