pyimagecuda 0.1.0__cp311-cp311-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyimagecuda/__init__.py +67 -0
- pyimagecuda/adjust.py +115 -0
- pyimagecuda/blend.py +294 -0
- pyimagecuda/effect.py +216 -0
- pyimagecuda/fill.py +263 -0
- pyimagecuda/filter.py +199 -0
- pyimagecuda/gl_interop.py +84 -0
- pyimagecuda/image.py +121 -0
- pyimagecuda/io.py +259 -0
- pyimagecuda/pyimagecuda_internal.cp311-win_amd64.pyd +0 -0
- pyimagecuda/resize.py +108 -0
- pyimagecuda/text.py +106 -0
- pyimagecuda/transform.py +275 -0
- pyimagecuda-0.1.0.dist-info/METADATA +150 -0
- pyimagecuda-0.1.0.dist-info/RECORD +17 -0
- pyimagecuda-0.1.0.dist-info/WHEEL +5 -0
- pyimagecuda-0.1.0.dist-info/licenses/LICENSE +21 -0
pyimagecuda/io.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import pyvips
|
|
2
|
+
|
|
3
|
+
from .pyimagecuda_internal import upload_to_buffer, convert_f32_to_u8, convert_u8_to_f32, download_from_buffer, copy_buffer #type: ignore
|
|
4
|
+
from .image import Image, ImageU8, ImageBase
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import numpy as np
|
|
8
|
+
except ImportError:
|
|
9
|
+
np = None
|
|
10
|
+
|
|
11
|
+
def upload(image: ImageBase, data: bytes | bytearray | memoryview) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Uploads the image data from a bytes-like object to the GPU.
|
|
14
|
+
|
|
15
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#direct-uploaddownload
|
|
16
|
+
"""
|
|
17
|
+
bytes_per_pixel = 4 if isinstance(image, ImageU8) else 16
|
|
18
|
+
expected = image.width * image.height * bytes_per_pixel
|
|
19
|
+
actual = data.nbytes if isinstance(data, memoryview) else len(data)
|
|
20
|
+
|
|
21
|
+
if actual != expected:
|
|
22
|
+
raise ValueError(f"Expected {expected} bytes, got {actual}")
|
|
23
|
+
|
|
24
|
+
upload_to_buffer(image._buffer._handle, data, image.width, image.height)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def download(image: ImageBase) -> bytes:
|
|
28
|
+
"""
|
|
29
|
+
Downloads the image data from the GPU to a bytes object.
|
|
30
|
+
|
|
31
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#direct-uploaddownload
|
|
32
|
+
"""
|
|
33
|
+
return download_from_buffer(image._buffer._handle, image.width, image.height)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def copy(dst: ImageBase, src: ImageBase) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Copies image data from the source image to the destination image.
|
|
39
|
+
|
|
40
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#copy-between-buffers
|
|
41
|
+
"""
|
|
42
|
+
dst.resize(src.width, src.height)
|
|
43
|
+
copy_buffer(dst._buffer._handle, src._buffer._handle, src.width, src.height)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def convert_float_to_u8(dst: ImageU8, src: Image) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Converts a floating-point image to an 8-bit unsigned integer image.
|
|
49
|
+
|
|
50
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#manual-conversions
|
|
51
|
+
"""
|
|
52
|
+
dst.resize(src.width, src.height)
|
|
53
|
+
convert_f32_to_u8(dst._buffer._handle, src._buffer._handle, src.width, src.height)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def convert_u8_to_float(dst: Image, src: ImageU8) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Converts an 8-bit unsigned integer image to a floating-point image.
|
|
59
|
+
|
|
60
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#manual-conversions
|
|
61
|
+
"""
|
|
62
|
+
dst.resize(src.width, src.height)
|
|
63
|
+
convert_u8_to_f32(dst._buffer._handle, src._buffer._handle, src.width, src.height)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load(
|
|
67
|
+
filepath: str,
|
|
68
|
+
f32_buffer: Image | None = None,
|
|
69
|
+
u8_buffer: ImageU8 | None = None
|
|
70
|
+
) -> Image | None:
|
|
71
|
+
"""
|
|
72
|
+
Loads an image from a file (returns new image or writes to buffer).
|
|
73
|
+
|
|
74
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#loading-images
|
|
75
|
+
"""
|
|
76
|
+
vips_img = pyvips.Image.new_from_file(filepath, access='sequential')
|
|
77
|
+
|
|
78
|
+
if vips_img.bands == 1:
|
|
79
|
+
vips_img = vips_img.bandjoin([vips_img, vips_img, vips_img])
|
|
80
|
+
vips_img = vips_img.bandjoin(255)
|
|
81
|
+
elif vips_img.bands == 3:
|
|
82
|
+
vips_img = vips_img.bandjoin(255)
|
|
83
|
+
elif vips_img.bands == 4:
|
|
84
|
+
pass
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Unsupported image format: {vips_img.bands} channels. "
|
|
88
|
+
f"Only grayscale (1), RGB (3), and RGBA (4) are supported."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
width = vips_img.width
|
|
92
|
+
height = vips_img.height
|
|
93
|
+
|
|
94
|
+
should_return = False
|
|
95
|
+
|
|
96
|
+
if f32_buffer is None:
|
|
97
|
+
f32_buffer = Image(width, height)
|
|
98
|
+
should_return = True
|
|
99
|
+
else:
|
|
100
|
+
f32_buffer.resize(width, height)
|
|
101
|
+
should_return = False
|
|
102
|
+
|
|
103
|
+
if u8_buffer is None:
|
|
104
|
+
u8_buffer = ImageU8(width, height)
|
|
105
|
+
owns_u8 = True
|
|
106
|
+
else:
|
|
107
|
+
u8_buffer.resize(width, height)
|
|
108
|
+
owns_u8 = False
|
|
109
|
+
|
|
110
|
+
vips_img = vips_img.cast('uchar')
|
|
111
|
+
pixel_data = vips_img.write_to_memory()
|
|
112
|
+
|
|
113
|
+
upload(u8_buffer, pixel_data)
|
|
114
|
+
|
|
115
|
+
convert_u8_to_float(f32_buffer, u8_buffer)
|
|
116
|
+
|
|
117
|
+
if owns_u8:
|
|
118
|
+
u8_buffer.free()
|
|
119
|
+
|
|
120
|
+
return f32_buffer if should_return else None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _save_internal(u8_image: ImageU8, filepath: str, quality: int | None = None) -> None:
|
|
124
|
+
pixel_data = download(u8_image)
|
|
125
|
+
|
|
126
|
+
vips_img = pyvips.Image.new_from_memory(
|
|
127
|
+
pixel_data,
|
|
128
|
+
u8_image.width,
|
|
129
|
+
u8_image.height,
|
|
130
|
+
bands=4,
|
|
131
|
+
format='uchar'
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
vips_img = vips_img.copy(interpretation='srgb')
|
|
135
|
+
|
|
136
|
+
save_kwargs = {}
|
|
137
|
+
if quality is not None:
|
|
138
|
+
if filepath.lower().endswith(('.jpg', '.jpeg')):
|
|
139
|
+
save_kwargs['Q'] = quality
|
|
140
|
+
elif filepath.lower().endswith('.webp'):
|
|
141
|
+
save_kwargs['Q'] = quality
|
|
142
|
+
elif filepath.lower().endswith(('.heic', '.heif')):
|
|
143
|
+
save_kwargs['Q'] = quality
|
|
144
|
+
|
|
145
|
+
vips_img.write_to_file(filepath, **save_kwargs)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def save(image: Image, filepath: str, u8_buffer: ImageU8 | None = None, quality: int | None = None) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Saves the floating-point image to a file (using an 8-bit buffer for conversion).
|
|
151
|
+
|
|
152
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#saving-images
|
|
153
|
+
"""
|
|
154
|
+
if u8_buffer is None:
|
|
155
|
+
u8_buffer = ImageU8(image.width, image.height)
|
|
156
|
+
owns_buffer = True
|
|
157
|
+
else:
|
|
158
|
+
u8_buffer.resize(image.width, image.height)
|
|
159
|
+
owns_buffer = False
|
|
160
|
+
|
|
161
|
+
convert_float_to_u8(u8_buffer, image)
|
|
162
|
+
_save_internal(u8_buffer, filepath, quality)
|
|
163
|
+
|
|
164
|
+
if owns_buffer:
|
|
165
|
+
u8_buffer.free()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def save_u8(image: ImageU8, filepath: str, quality: int | None = None) -> None:
|
|
169
|
+
"""
|
|
170
|
+
Saves an 8-bit unsigned integer image directly to a file.
|
|
171
|
+
|
|
172
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#saving-images
|
|
173
|
+
"""
|
|
174
|
+
_save_internal(image, filepath, quality)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def from_numpy(array, f32_buffer: Image | None = None, u8_buffer: ImageU8 | None = None) -> Image:
|
|
178
|
+
"""
|
|
179
|
+
Creates a PyImageCUDA Image from a NumPy array (e.g. from OpenCV, Pillow, Matplotlib).
|
|
180
|
+
|
|
181
|
+
- Handles uint8 (0-255) -> float32 (0.0-1.0) conversion automatically on GPU.
|
|
182
|
+
- Handles Grayscale/RGB -> RGBA expansion automatically.
|
|
183
|
+
- Optimized: Uploads uint8 data (4x smaller) if possible, then converts on GPU.
|
|
184
|
+
|
|
185
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#numpy-integration
|
|
186
|
+
"""
|
|
187
|
+
if np is None:
|
|
188
|
+
raise ImportError("NumPy is not installed. Run `pip install numpy` to use this feature.")
|
|
189
|
+
|
|
190
|
+
if not isinstance(array, np.ndarray):
|
|
191
|
+
raise TypeError(f"Expected numpy.ndarray, got {type(array)}")
|
|
192
|
+
|
|
193
|
+
target_dtype = array.dtype
|
|
194
|
+
|
|
195
|
+
if array.ndim == 2:
|
|
196
|
+
h, w = array.shape
|
|
197
|
+
alpha_val = 255 if target_dtype == np.uint8 else 1.0
|
|
198
|
+
alpha_channel = np.full((h, w), alpha_val, dtype=target_dtype)
|
|
199
|
+
array = np.dstack((array, array, array, alpha_channel))
|
|
200
|
+
|
|
201
|
+
elif array.ndim == 3:
|
|
202
|
+
h, w, c = array.shape
|
|
203
|
+
if c == 3:
|
|
204
|
+
alpha_val = 255 if target_dtype == np.uint8 else 1.0
|
|
205
|
+
alpha_channel = np.full((h, w), alpha_val, dtype=target_dtype)
|
|
206
|
+
array = np.dstack((array, alpha_channel))
|
|
207
|
+
elif c != 4:
|
|
208
|
+
raise ValueError(f"Unsupported channel count: {c}. PyImageCUDA requires 1, 3, or 4 channels.")
|
|
209
|
+
else:
|
|
210
|
+
raise ValueError(f"Unsupported array shape: {array.shape}. Expected (H, W), (H, W, 3) or (H, W, 4).")
|
|
211
|
+
|
|
212
|
+
if not array.flags['C_CONTIGUOUS']:
|
|
213
|
+
array = np.ascontiguousarray(array)
|
|
214
|
+
|
|
215
|
+
height, width = array.shape[:2]
|
|
216
|
+
|
|
217
|
+
should_return = False
|
|
218
|
+
if f32_buffer is None:
|
|
219
|
+
f32_buffer = Image(width, height)
|
|
220
|
+
should_return = True
|
|
221
|
+
else:
|
|
222
|
+
f32_buffer.resize(width, height)
|
|
223
|
+
|
|
224
|
+
if array.dtype == np.uint8:
|
|
225
|
+
owns_u8 = False
|
|
226
|
+
if u8_buffer is None:
|
|
227
|
+
u8_buffer = ImageU8(width, height)
|
|
228
|
+
owns_u8 = True
|
|
229
|
+
else:
|
|
230
|
+
u8_buffer.resize(width, height)
|
|
231
|
+
|
|
232
|
+
upload(u8_buffer, array.tobytes())
|
|
233
|
+
convert_u8_to_float(f32_buffer, u8_buffer)
|
|
234
|
+
|
|
235
|
+
if owns_u8:
|
|
236
|
+
u8_buffer.free()
|
|
237
|
+
|
|
238
|
+
elif array.dtype == np.float32:
|
|
239
|
+
upload(f32_buffer, array.tobytes())
|
|
240
|
+
else:
|
|
241
|
+
array = array.astype(np.float32)
|
|
242
|
+
upload(f32_buffer, array.tobytes())
|
|
243
|
+
|
|
244
|
+
return f32_buffer if should_return else None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def to_numpy(image: Image) -> 'np.ndarray': # type: ignore
|
|
248
|
+
"""
|
|
249
|
+
Downloads a PyImageCUDA Image to a NumPy array.
|
|
250
|
+
|
|
251
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/io/#numpy-integration
|
|
252
|
+
"""
|
|
253
|
+
if np is None:
|
|
254
|
+
raise ImportError("NumPy is not installed. Run `pip install numpy` to use this feature.")
|
|
255
|
+
|
|
256
|
+
raw_bytes = download(image)
|
|
257
|
+
array = np.frombuffer(raw_bytes, dtype=np.float32)
|
|
258
|
+
|
|
259
|
+
return array.reshape((image.height, image.width, 4))
|
|
Binary file
|
pyimagecuda/resize.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from .image import Image
|
|
2
|
+
from .pyimagecuda_internal import resize_f32 #type: ignore
|
|
3
|
+
from .io import copy
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _resize_internal(
|
|
7
|
+
src: Image,
|
|
8
|
+
width: int | None,
|
|
9
|
+
height: int | None,
|
|
10
|
+
method: int,
|
|
11
|
+
dst_buffer: Image | None = None
|
|
12
|
+
) -> Image | None:
|
|
13
|
+
|
|
14
|
+
if width is None and height is None:
|
|
15
|
+
raise ValueError("At least one of width or height must be specified")
|
|
16
|
+
elif width is None:
|
|
17
|
+
width = int(src.width * (height / src.height))
|
|
18
|
+
elif height is None:
|
|
19
|
+
height = int(src.height * (width / src.width))
|
|
20
|
+
|
|
21
|
+
# No resize needed, just copy
|
|
22
|
+
if width == src.width and height == src.height:
|
|
23
|
+
if dst_buffer is None:
|
|
24
|
+
dst_buffer = Image(width, height)
|
|
25
|
+
copy(dst_buffer, src)
|
|
26
|
+
return dst_buffer
|
|
27
|
+
else:
|
|
28
|
+
copy(dst_buffer, src)
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
# Resize normal
|
|
32
|
+
if dst_buffer is None:
|
|
33
|
+
dst_buffer = Image(width, height)
|
|
34
|
+
return_buffer = True
|
|
35
|
+
else:
|
|
36
|
+
dst_buffer.resize(width, height)
|
|
37
|
+
return_buffer = False
|
|
38
|
+
|
|
39
|
+
resize_f32(
|
|
40
|
+
src._buffer._handle,
|
|
41
|
+
dst_buffer._buffer._handle,
|
|
42
|
+
src.width,
|
|
43
|
+
src.height,
|
|
44
|
+
width,
|
|
45
|
+
height,
|
|
46
|
+
method
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return dst_buffer if return_buffer else None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Resize:
|
|
53
|
+
@staticmethod
|
|
54
|
+
def nearest(
|
|
55
|
+
src: Image,
|
|
56
|
+
width: int | None = None,
|
|
57
|
+
height: int | None = None,
|
|
58
|
+
dst_buffer: Image | None = None
|
|
59
|
+
) -> Image | None:
|
|
60
|
+
"""
|
|
61
|
+
Resizes the image using nearest neighbor interpolation (returns new image or writes to buffer).
|
|
62
|
+
|
|
63
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/resize/#nearest
|
|
64
|
+
"""
|
|
65
|
+
return _resize_internal(src, width, height, 0, dst_buffer)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def bilinear(
|
|
69
|
+
src: Image,
|
|
70
|
+
width: int | None = None,
|
|
71
|
+
height: int | None = None,
|
|
72
|
+
dst_buffer: Image | None = None
|
|
73
|
+
) -> Image | None:
|
|
74
|
+
"""
|
|
75
|
+
Resizes the image using bilinear interpolation (returns new image or writes to buffer).
|
|
76
|
+
|
|
77
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/resize/#bilinear
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
return _resize_internal(src, width, height, 1, dst_buffer)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def bicubic(
|
|
84
|
+
src: Image,
|
|
85
|
+
width: int | None = None,
|
|
86
|
+
height: int | None = None,
|
|
87
|
+
dst_buffer: Image | None = None
|
|
88
|
+
) -> Image | None:
|
|
89
|
+
"""
|
|
90
|
+
Resizes the image using bicubic interpolation (returns new image or writes to buffer).
|
|
91
|
+
|
|
92
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/resize/#bicubic
|
|
93
|
+
"""
|
|
94
|
+
return _resize_internal(src, width, height, 2, dst_buffer)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def lanczos(
|
|
98
|
+
src: Image,
|
|
99
|
+
width: int | None = None,
|
|
100
|
+
height: int | None = None,
|
|
101
|
+
dst_buffer: Image | None = None
|
|
102
|
+
) -> Image | None:
|
|
103
|
+
"""
|
|
104
|
+
Resizes the image using Lanczos interpolation (returns new image or writes to buffer).
|
|
105
|
+
|
|
106
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/resize/#lanczos
|
|
107
|
+
"""
|
|
108
|
+
return _resize_internal(src, width, height, 3, dst_buffer)
|
pyimagecuda/text.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
import pyvips
|
|
3
|
+
|
|
4
|
+
from .image import Image, ImageU8
|
|
5
|
+
from .io import upload, convert_u8_to_float
|
|
6
|
+
|
|
7
|
+
class Text:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def create(
|
|
10
|
+
text: str,
|
|
11
|
+
font: str = "Sans",
|
|
12
|
+
size: float = 12.0,
|
|
13
|
+
color: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
|
|
14
|
+
bg_color: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0),
|
|
15
|
+
align: Literal['left', 'centre', 'right'] = 'left',
|
|
16
|
+
justify: bool = False,
|
|
17
|
+
spacing: int = 0,
|
|
18
|
+
letter_spacing: float = 0.0,
|
|
19
|
+
dst_buffer: Image | None = None,
|
|
20
|
+
u8_buffer: ImageU8 | None = None
|
|
21
|
+
) -> Image | None:
|
|
22
|
+
"""
|
|
23
|
+
Renders text into an image with specified font, size, color, alignment, and spacing.
|
|
24
|
+
|
|
25
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/text/#text-rendering
|
|
26
|
+
"""
|
|
27
|
+
full_font_string = f"{font} {size}"
|
|
28
|
+
|
|
29
|
+
text_opts = {
|
|
30
|
+
'font': full_font_string,
|
|
31
|
+
'dpi': 72,
|
|
32
|
+
'align': 0 if align == 'left' else (1 if align == 'centre' else 2),
|
|
33
|
+
'justify': justify,
|
|
34
|
+
'spacing': spacing
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
final_text = text
|
|
38
|
+
if letter_spacing != 0:
|
|
39
|
+
pango_spacing = int(letter_spacing * 1024)
|
|
40
|
+
final_text = f'<span letter_spacing="{pango_spacing}">{text}</span>'
|
|
41
|
+
text_opts['rgba'] = True
|
|
42
|
+
|
|
43
|
+
if '<' in text and '>' in text:
|
|
44
|
+
text_opts['rgba'] = True
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
if text_opts.get('rgba'):
|
|
48
|
+
mask_rgba = pyvips.Image.text(final_text, **text_opts)
|
|
49
|
+
if mask_rgba.bands == 4:
|
|
50
|
+
mask = mask_rgba[3]
|
|
51
|
+
else:
|
|
52
|
+
mask = mask_rgba.colourspace('b-w')[0]
|
|
53
|
+
else:
|
|
54
|
+
mask = pyvips.Image.text(final_text, **text_opts)
|
|
55
|
+
|
|
56
|
+
except pyvips.Error as e:
|
|
57
|
+
raise RuntimeError(f"Failed to render text: {e}")
|
|
58
|
+
|
|
59
|
+
fg_r, fg_g, fg_b, fg_a = [int(c * 255) for c in color]
|
|
60
|
+
bg_r, bg_g, bg_b, bg_a = [int(c * 255) for c in bg_color]
|
|
61
|
+
|
|
62
|
+
fg_rgb = mask.new_from_image([fg_r, fg_g, fg_b])
|
|
63
|
+
|
|
64
|
+
if fg_a < 255:
|
|
65
|
+
fg_alpha = (mask * (fg_a / 255.0)).cast('uchar')
|
|
66
|
+
else:
|
|
67
|
+
fg_alpha = mask
|
|
68
|
+
|
|
69
|
+
fg_layer = fg_rgb.bandjoin(fg_alpha)
|
|
70
|
+
fg_layer = fg_layer.copy(interpretation='srgb')
|
|
71
|
+
|
|
72
|
+
if bg_a > 0:
|
|
73
|
+
bg_layer = mask.new_from_image([bg_r, bg_g, bg_b, bg_a])
|
|
74
|
+
bg_layer = bg_layer.copy(interpretation='srgb')
|
|
75
|
+
final_vips = bg_layer.composite(fg_layer, 'over')
|
|
76
|
+
else:
|
|
77
|
+
final_vips = fg_layer
|
|
78
|
+
|
|
79
|
+
final_vips = final_vips.cast('uchar')
|
|
80
|
+
|
|
81
|
+
w, h = final_vips.width, final_vips.height
|
|
82
|
+
raw_bytes = final_vips.write_to_memory()
|
|
83
|
+
|
|
84
|
+
if dst_buffer is None:
|
|
85
|
+
result = Image(w, h)
|
|
86
|
+
return_result = True
|
|
87
|
+
else:
|
|
88
|
+
dst_buffer.resize(w, h)
|
|
89
|
+
result = dst_buffer
|
|
90
|
+
return_result = False
|
|
91
|
+
|
|
92
|
+
if u8_buffer is None:
|
|
93
|
+
u8_temp = ImageU8(w, h)
|
|
94
|
+
free_u8 = True
|
|
95
|
+
else:
|
|
96
|
+
u8_buffer.resize(w, h)
|
|
97
|
+
u8_temp = u8_buffer
|
|
98
|
+
free_u8 = False
|
|
99
|
+
|
|
100
|
+
upload(u8_temp, raw_bytes)
|
|
101
|
+
convert_u8_to_float(result, u8_temp)
|
|
102
|
+
|
|
103
|
+
if free_u8:
|
|
104
|
+
u8_temp.free()
|
|
105
|
+
|
|
106
|
+
return result if return_result else None
|