pyimagecuda 0.0.4__cp312-cp312-win_amd64.whl → 0.1.0__cp312-cp312-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 +5 -2
- pyimagecuda/adjust.py +14 -0
- pyimagecuda/blend.py +21 -0
- pyimagecuda/effect.py +89 -37
- pyimagecuda/filter.py +45 -14
- pyimagecuda/gl_interop.py +84 -0
- pyimagecuda/image.py +40 -14
- pyimagecuda/io.py +126 -25
- pyimagecuda/pyimagecuda_internal.cp312-win_amd64.pyd +0 -0
- pyimagecuda/resize.py +14 -3
- pyimagecuda/text.py +106 -0
- pyimagecuda/transform.py +103 -14
- {pyimagecuda-0.0.4.dist-info → pyimagecuda-0.1.0.dist-info}/METADATA +67 -12
- pyimagecuda-0.1.0.dist-info/RECORD +17 -0
- pyimagecuda/utils.py +0 -17
- pyimagecuda-0.0.4.dist-info/RECORD +0 -16
- {pyimagecuda-0.0.4.dist-info → pyimagecuda-0.1.0.dist-info}/WHEEL +0 -0
- {pyimagecuda-0.0.4.dist-info → pyimagecuda-0.1.0.dist-info}/licenses/LICENSE +0 -0
pyimagecuda/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ctypes
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
-
__version__ = "0.0
|
|
4
|
+
__version__ = "0.1.0"
|
|
5
5
|
|
|
6
6
|
def _check_nvidia_driver():
|
|
7
7
|
try:
|
|
@@ -37,7 +37,7 @@ except ImportError as e:
|
|
|
37
37
|
if _INTERNAL_LOADED:
|
|
38
38
|
try:
|
|
39
39
|
from .image import Image, ImageU8
|
|
40
|
-
from .io import upload, download, copy, save, load, convert_float_to_u8, convert_u8_to_float
|
|
40
|
+
from .io import upload, download, copy, save, load, convert_float_to_u8, convert_u8_to_float, from_numpy, to_numpy, save_u8
|
|
41
41
|
from .fill import Fill
|
|
42
42
|
from .resize import Resize
|
|
43
43
|
from .blend import Blend
|
|
@@ -45,6 +45,9 @@ if _INTERNAL_LOADED:
|
|
|
45
45
|
from .effect import Effect
|
|
46
46
|
from .adjust import Adjust
|
|
47
47
|
from .transform import Transform
|
|
48
|
+
from .text import Text
|
|
49
|
+
from .gl_interop import GLResource
|
|
50
|
+
from .pyimagecuda_internal import cuda_sync # type: ignore
|
|
48
51
|
except ImportError as e:
|
|
49
52
|
print(f"Warning: Error importing Python wrappers: {e}")
|
|
50
53
|
|
pyimagecuda/adjust.py
CHANGED
|
@@ -19,6 +19,9 @@ class Adjust:
|
|
|
19
19
|
|
|
20
20
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#brightness
|
|
21
21
|
"""
|
|
22
|
+
if abs(factor) < 1e-6:
|
|
23
|
+
return
|
|
24
|
+
|
|
22
25
|
adjust_brightness_f32(
|
|
23
26
|
image._buffer._handle,
|
|
24
27
|
image.width,
|
|
@@ -36,6 +39,9 @@ class Adjust:
|
|
|
36
39
|
|
|
37
40
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#contrast
|
|
38
41
|
"""
|
|
42
|
+
if abs(factor - 1.0) < 1e-6:
|
|
43
|
+
return
|
|
44
|
+
|
|
39
45
|
adjust_contrast_f32(
|
|
40
46
|
image._buffer._handle,
|
|
41
47
|
image.width,
|
|
@@ -54,6 +60,9 @@ class Adjust:
|
|
|
54
60
|
|
|
55
61
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#saturation
|
|
56
62
|
"""
|
|
63
|
+
if abs(factor - 1.0) < 1e-6:
|
|
64
|
+
return
|
|
65
|
+
|
|
57
66
|
adjust_saturation_f32(
|
|
58
67
|
image._buffer._handle,
|
|
59
68
|
image.width,
|
|
@@ -75,6 +84,9 @@ class Adjust:
|
|
|
75
84
|
"""
|
|
76
85
|
if gamma <= 0:
|
|
77
86
|
raise ValueError("Gamma must be positive")
|
|
87
|
+
|
|
88
|
+
if abs(gamma - 1.0) < 1e-6:
|
|
89
|
+
return
|
|
78
90
|
|
|
79
91
|
adjust_gamma_f32(
|
|
80
92
|
image._buffer._handle,
|
|
@@ -92,6 +104,8 @@ class Adjust:
|
|
|
92
104
|
|
|
93
105
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#opacity
|
|
94
106
|
"""
|
|
107
|
+
if abs(factor - 1.0) < 1e-6:
|
|
108
|
+
return
|
|
95
109
|
|
|
96
110
|
adjust_opacity_f32(
|
|
97
111
|
image._buffer._handle,
|
pyimagecuda/blend.py
CHANGED
|
@@ -45,6 +45,9 @@ class Blend:
|
|
|
45
45
|
|
|
46
46
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#normal
|
|
47
47
|
"""
|
|
48
|
+
if abs(opacity) < 1e-6:
|
|
49
|
+
return
|
|
50
|
+
|
|
48
51
|
pos_x, pos_y = _calculate_position(
|
|
49
52
|
base.width, base.height,
|
|
50
53
|
overlay.width, overlay.height,
|
|
@@ -75,6 +78,9 @@ class Blend:
|
|
|
75
78
|
|
|
76
79
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#multiply
|
|
77
80
|
"""
|
|
81
|
+
if abs(opacity) < 1e-6:
|
|
82
|
+
return
|
|
83
|
+
|
|
78
84
|
pos_x, pos_y = _calculate_position(
|
|
79
85
|
base.width, base.height,
|
|
80
86
|
overlay.width, overlay.height,
|
|
@@ -105,6 +111,9 @@ class Blend:
|
|
|
105
111
|
|
|
106
112
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#screen
|
|
107
113
|
"""
|
|
114
|
+
if abs(opacity) < 1e-6:
|
|
115
|
+
return
|
|
116
|
+
|
|
108
117
|
pos_x, pos_y = _calculate_position(
|
|
109
118
|
base.width, base.height,
|
|
110
119
|
overlay.width, overlay.height,
|
|
@@ -135,6 +144,9 @@ class Blend:
|
|
|
135
144
|
|
|
136
145
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#add
|
|
137
146
|
"""
|
|
147
|
+
if abs(opacity) < 1e-6:
|
|
148
|
+
return
|
|
149
|
+
|
|
138
150
|
pos_x, pos_y = _calculate_position(
|
|
139
151
|
base.width, base.height,
|
|
140
152
|
overlay.width, overlay.height,
|
|
@@ -165,6 +177,9 @@ class Blend:
|
|
|
165
177
|
|
|
166
178
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#overlay
|
|
167
179
|
"""
|
|
180
|
+
if abs(opacity) < 1e-6:
|
|
181
|
+
return
|
|
182
|
+
|
|
168
183
|
pos_x, pos_y = _calculate_position(
|
|
169
184
|
base.width, base.height,
|
|
170
185
|
overlay.width, overlay.height,
|
|
@@ -195,6 +210,9 @@ class Blend:
|
|
|
195
210
|
|
|
196
211
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#soft-light
|
|
197
212
|
"""
|
|
213
|
+
if abs(opacity) < 1e-6:
|
|
214
|
+
return
|
|
215
|
+
|
|
198
216
|
pos_x, pos_y = _calculate_position(
|
|
199
217
|
base.width, base.height,
|
|
200
218
|
overlay.width, overlay.height,
|
|
@@ -225,6 +243,9 @@ class Blend:
|
|
|
225
243
|
|
|
226
244
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#hard-light
|
|
227
245
|
"""
|
|
246
|
+
if abs(opacity) < 1e-6:
|
|
247
|
+
return
|
|
248
|
+
|
|
228
249
|
pos_x, pos_y = _calculate_position(
|
|
229
250
|
base.width, base.height,
|
|
230
251
|
overlay.width, overlay.height,
|
pyimagecuda/effect.py
CHANGED
|
@@ -4,7 +4,6 @@ from .image import Image
|
|
|
4
4
|
from .filter import Filter
|
|
5
5
|
from .blend import Blend
|
|
6
6
|
from .fill import Fill
|
|
7
|
-
from .utils import ensure_capacity
|
|
8
7
|
from .pyimagecuda_internal import ( #type: ignore
|
|
9
8
|
rounded_corners_f32,
|
|
10
9
|
extract_alpha_f32,
|
|
@@ -25,14 +24,19 @@ class Effect:
|
|
|
25
24
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/effect/#rounded_corners
|
|
26
25
|
"""
|
|
27
26
|
max_radius = min(image.width, image.height) / 2.0
|
|
28
|
-
if radius < 0:
|
|
29
|
-
|
|
27
|
+
if radius < 0:
|
|
28
|
+
raise ValueError("Radius must be non-negative")
|
|
29
|
+
if radius == 0:
|
|
30
|
+
return
|
|
31
|
+
if radius > max_radius:
|
|
32
|
+
radius = max_radius
|
|
30
33
|
|
|
31
34
|
rounded_corners_f32(image._buffer._handle, image.width, image.height, float(radius))
|
|
32
35
|
|
|
33
36
|
@staticmethod
|
|
34
37
|
def drop_shadow(
|
|
35
|
-
image: Image,
|
|
38
|
+
image: Image,
|
|
39
|
+
offset_x: int = 10,
|
|
36
40
|
offset_y: int = 10,
|
|
37
41
|
blur: int = 20,
|
|
38
42
|
color: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.5),
|
|
@@ -51,30 +55,46 @@ class Effect:
|
|
|
51
55
|
pad_r = blur + max(0, offset_x)
|
|
52
56
|
pad_t = blur + max(0, -offset_y)
|
|
53
57
|
pad_b = blur + max(0, offset_y)
|
|
54
|
-
res_w
|
|
55
|
-
|
|
58
|
+
res_w = image.width + pad_l + pad_r
|
|
59
|
+
res_h = image.height + pad_t + pad_b
|
|
60
|
+
img_x = pad_l
|
|
61
|
+
img_y = pad_t
|
|
56
62
|
else:
|
|
57
|
-
res_w
|
|
58
|
-
|
|
63
|
+
res_w = image.width
|
|
64
|
+
res_h = image.height
|
|
65
|
+
img_x = 0
|
|
66
|
+
img_y = 0
|
|
59
67
|
|
|
60
68
|
if dst_buffer is None:
|
|
61
|
-
result = Image(res_w, res_h)
|
|
69
|
+
result = Image(res_w, res_h)
|
|
70
|
+
ret = True
|
|
62
71
|
else:
|
|
63
|
-
|
|
72
|
+
dst_buffer.resize(res_w, res_h)
|
|
73
|
+
result = dst_buffer
|
|
74
|
+
ret = False
|
|
64
75
|
|
|
65
76
|
if shadow_buffer is None:
|
|
66
|
-
shadow = Image(res_w, res_h)
|
|
77
|
+
shadow = Image(res_w, res_h)
|
|
78
|
+
own_shadow = True
|
|
67
79
|
else:
|
|
68
|
-
|
|
80
|
+
shadow_buffer.resize(res_w, res_h)
|
|
81
|
+
shadow = shadow_buffer
|
|
82
|
+
own_shadow = False
|
|
69
83
|
|
|
70
84
|
if expand:
|
|
71
|
-
if temp_buffer is None:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
if temp_buffer is None:
|
|
86
|
+
temp = Image(res_w, res_h)
|
|
87
|
+
else:
|
|
88
|
+
temp_buffer.resize(res_w, res_h)
|
|
89
|
+
temp = temp_buffer
|
|
90
|
+
|
|
91
|
+
Fill.color(shadow, (0, 0, 0, 0))
|
|
92
|
+
temp.resize(image.width, image.height)
|
|
75
93
|
extract_alpha_f32(image._buffer._handle, temp._buffer._handle, image.width, image.height)
|
|
76
94
|
Blend.normal(shadow, temp, anchor='top-left', offset_x=pad_l, offset_y=pad_t)
|
|
77
|
-
|
|
95
|
+
|
|
96
|
+
if temp_buffer is None:
|
|
97
|
+
temp.free()
|
|
78
98
|
else:
|
|
79
99
|
extract_alpha_f32(image._buffer._handle, shadow._buffer._handle, image.width, image.height)
|
|
80
100
|
|
|
@@ -82,11 +102,13 @@ class Effect:
|
|
|
82
102
|
Filter.gaussian_blur(shadow, radius=blur, sigma=blur/3.0, dst_buffer=shadow)
|
|
83
103
|
|
|
84
104
|
colorize_alpha_mask_f32(shadow._buffer._handle, shadow.width, shadow.height, color)
|
|
85
|
-
Fill.color(result, (0,0,0,0))
|
|
105
|
+
Fill.color(result, (0, 0, 0, 0))
|
|
86
106
|
Blend.normal(result, shadow, anchor='top-left', offset_x=offset_x, offset_y=offset_y)
|
|
87
107
|
Blend.normal(result, image, anchor='top-left', offset_x=img_x, offset_y=img_y)
|
|
88
108
|
|
|
89
|
-
if own_shadow:
|
|
109
|
+
if own_shadow:
|
|
110
|
+
shadow.free()
|
|
111
|
+
|
|
90
112
|
return result if ret else None
|
|
91
113
|
|
|
92
114
|
@staticmethod
|
|
@@ -104,38 +126,66 @@ class Effect:
|
|
|
104
126
|
|
|
105
127
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/effect/#stroke
|
|
106
128
|
"""
|
|
107
|
-
if width < 1 or width > 1000:
|
|
108
|
-
|
|
129
|
+
if width < 1 or width > 1000:
|
|
130
|
+
raise ValueError("Invalid stroke width")
|
|
131
|
+
if position not in ('outside', 'inside'):
|
|
132
|
+
raise ValueError("Invalid position")
|
|
109
133
|
|
|
110
134
|
if position == 'outside' and expand:
|
|
111
|
-
res_w
|
|
112
|
-
|
|
135
|
+
res_w = image.width + width * 2
|
|
136
|
+
res_h = image.height + width * 2
|
|
137
|
+
off_x = width
|
|
138
|
+
off_y = width
|
|
113
139
|
else:
|
|
114
|
-
res_w
|
|
115
|
-
|
|
140
|
+
res_w = image.width
|
|
141
|
+
res_h = image.height
|
|
142
|
+
off_x = 0
|
|
143
|
+
off_y = 0
|
|
116
144
|
|
|
117
145
|
if dst_buffer is None:
|
|
118
|
-
result = Image(res_w, res_h)
|
|
146
|
+
result = Image(res_w, res_h)
|
|
147
|
+
ret = True
|
|
119
148
|
else:
|
|
120
|
-
|
|
149
|
+
dst_buffer.resize(res_w, res_h)
|
|
150
|
+
result = dst_buffer
|
|
151
|
+
ret = False
|
|
121
152
|
|
|
122
153
|
if distance_buffer is None:
|
|
123
|
-
distance = Image(res_w, res_h)
|
|
154
|
+
distance = Image(res_w, res_h)
|
|
155
|
+
own_dist = True
|
|
124
156
|
else:
|
|
125
|
-
|
|
157
|
+
distance_buffer.resize(res_w, res_h)
|
|
158
|
+
distance = distance_buffer
|
|
159
|
+
own_dist = False
|
|
126
160
|
|
|
127
161
|
compute_distance_field_f32(
|
|
128
|
-
image._buffer._handle,
|
|
129
|
-
|
|
162
|
+
image._buffer._handle,
|
|
163
|
+
distance._buffer._handle,
|
|
164
|
+
image.width,
|
|
165
|
+
image.height,
|
|
166
|
+
res_w,
|
|
167
|
+
res_h,
|
|
168
|
+
off_x,
|
|
169
|
+
off_y
|
|
130
170
|
)
|
|
131
171
|
|
|
132
172
|
generate_stroke_composite_f32(
|
|
133
|
-
image._buffer._handle,
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
image._buffer._handle,
|
|
174
|
+
distance._buffer._handle,
|
|
175
|
+
result._buffer._handle,
|
|
176
|
+
image.width,
|
|
177
|
+
image.height,
|
|
178
|
+
res_w,
|
|
179
|
+
res_h,
|
|
180
|
+
off_x,
|
|
181
|
+
off_y,
|
|
182
|
+
float(width),
|
|
183
|
+
color,
|
|
184
|
+
0 if position == 'outside' else 1
|
|
136
185
|
)
|
|
137
186
|
|
|
138
|
-
if own_dist:
|
|
187
|
+
if own_dist:
|
|
188
|
+
distance.free()
|
|
139
189
|
|
|
140
190
|
return result if ret else None
|
|
141
191
|
|
|
@@ -151,8 +201,10 @@ class Effect:
|
|
|
151
201
|
|
|
152
202
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/effect/#vignette
|
|
153
203
|
"""
|
|
154
|
-
if radius < 0:
|
|
155
|
-
|
|
204
|
+
if radius < 0:
|
|
205
|
+
radius = 0.0
|
|
206
|
+
if softness < 0:
|
|
207
|
+
softness = 0.0
|
|
156
208
|
|
|
157
209
|
effect_vignette_f32(
|
|
158
210
|
image._buffer._handle,
|
pyimagecuda/filter.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
from .image import Image
|
|
2
|
-
from .
|
|
3
|
-
from .pyimagecuda_internal import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
from .io import copy
|
|
3
|
+
from .pyimagecuda_internal import ( #type: ignore
|
|
4
|
+
gaussian_blur_separable_f32,
|
|
5
|
+
sharpen_f32,
|
|
6
|
+
sepia_f32,
|
|
7
|
+
invert_f32,
|
|
8
|
+
threshold_f32,
|
|
9
|
+
solarize_f32,
|
|
10
|
+
filter_sobel_f32,
|
|
11
|
+
filter_emboss_f32
|
|
12
|
+
)
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class Filter:
|
|
@@ -26,6 +28,15 @@ class Filter:
|
|
|
26
28
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/filter/#gaussian-blur
|
|
27
29
|
"""
|
|
28
30
|
|
|
31
|
+
if radius == 0 or (sigma is not None and sigma <= 0.001):
|
|
32
|
+
if dst_buffer is None:
|
|
33
|
+
dst_buffer = Image(src.width, src.height)
|
|
34
|
+
copy(dst_buffer, src)
|
|
35
|
+
return dst_buffer
|
|
36
|
+
else:
|
|
37
|
+
copy(dst_buffer, src)
|
|
38
|
+
return None
|
|
39
|
+
|
|
29
40
|
if sigma is None:
|
|
30
41
|
sigma = radius / 3.0
|
|
31
42
|
|
|
@@ -33,14 +44,14 @@ class Filter:
|
|
|
33
44
|
dst_buffer = Image(src.width, src.height)
|
|
34
45
|
return_dst = True
|
|
35
46
|
else:
|
|
36
|
-
|
|
47
|
+
dst_buffer.resize(src.width, src.height)
|
|
37
48
|
return_dst = False
|
|
38
49
|
|
|
39
50
|
if temp_buffer is None:
|
|
40
51
|
temp_buffer = Image(src.width, src.height)
|
|
41
52
|
owns_temp = True
|
|
42
53
|
else:
|
|
43
|
-
|
|
54
|
+
temp_buffer.resize(src.width, src.height)
|
|
44
55
|
owns_temp = False
|
|
45
56
|
|
|
46
57
|
gaussian_blur_separable_f32(
|
|
@@ -70,11 +81,20 @@ class Filter:
|
|
|
70
81
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/filter/#sharpen
|
|
71
82
|
"""
|
|
72
83
|
|
|
84
|
+
if abs(strength) < 1e-6:
|
|
85
|
+
if dst_buffer is None:
|
|
86
|
+
dst_buffer = Image(src.width, src.height)
|
|
87
|
+
copy(dst_buffer, src)
|
|
88
|
+
return dst_buffer
|
|
89
|
+
else:
|
|
90
|
+
copy(dst_buffer, src)
|
|
91
|
+
return None
|
|
92
|
+
|
|
73
93
|
if dst_buffer is None:
|
|
74
94
|
dst_buffer = Image(src.width, src.height)
|
|
75
95
|
return_buffer = True
|
|
76
96
|
else:
|
|
77
|
-
|
|
97
|
+
dst_buffer.resize(src.width, src.height)
|
|
78
98
|
return_buffer = False
|
|
79
99
|
|
|
80
100
|
sharpen_f32(
|
|
@@ -94,6 +114,8 @@ class Filter:
|
|
|
94
114
|
|
|
95
115
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/filter/#sepia
|
|
96
116
|
"""
|
|
117
|
+
if abs(intensity) < 1e-6:
|
|
118
|
+
return
|
|
97
119
|
|
|
98
120
|
sepia_f32(image._buffer._handle, image.width, image.height, float(intensity))
|
|
99
121
|
|
|
@@ -136,7 +158,7 @@ class Filter:
|
|
|
136
158
|
dst_buffer = Image(src.width, src.height)
|
|
137
159
|
return_buffer = True
|
|
138
160
|
else:
|
|
139
|
-
|
|
161
|
+
dst_buffer.resize(src.width, src.height)
|
|
140
162
|
return_buffer = False
|
|
141
163
|
|
|
142
164
|
filter_sobel_f32(
|
|
@@ -153,11 +175,20 @@ class Filter:
|
|
|
153
175
|
|
|
154
176
|
Docs & Examples: https://offerrall.github.io/pyimagecuda/filter/#emboss
|
|
155
177
|
"""
|
|
178
|
+
if abs(strength) < 1e-6:
|
|
179
|
+
if dst_buffer is None:
|
|
180
|
+
dst_buffer = Image(src.width, src.height)
|
|
181
|
+
copy(dst_buffer, src)
|
|
182
|
+
return dst_buffer
|
|
183
|
+
else:
|
|
184
|
+
copy(dst_buffer, src)
|
|
185
|
+
return None
|
|
186
|
+
|
|
156
187
|
if dst_buffer is None:
|
|
157
188
|
dst_buffer = Image(src.width, src.height)
|
|
158
189
|
return_buffer = True
|
|
159
190
|
else:
|
|
160
|
-
|
|
191
|
+
dst_buffer.resize(src.width, src.height)
|
|
161
192
|
return_buffer = False
|
|
162
193
|
|
|
163
194
|
filter_emboss_f32(
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from .pyimagecuda_internal import ( # type: ignore
|
|
2
|
+
register_gl_pbo,
|
|
3
|
+
unregister_gl_resource,
|
|
4
|
+
copy_to_gl_pbo
|
|
5
|
+
)
|
|
6
|
+
from .image import ImageU8
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GLResource:
|
|
10
|
+
"""
|
|
11
|
+
CUDA-OpenGL interop resource for direct GPU-to-GPU transfers.
|
|
12
|
+
|
|
13
|
+
Represents a registered OpenGL PBO that can receive ImageU8 data
|
|
14
|
+
directly without CPU roundtrips.
|
|
15
|
+
|
|
16
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/opengl/
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, pbo_id: int):
|
|
20
|
+
"""
|
|
21
|
+
Parameters:
|
|
22
|
+
pbo_id: Valid OpenGL PBO ID. The PBO must exist and remain
|
|
23
|
+
valid for the lifetime of this GLResource.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: If pbo_id is invalid
|
|
27
|
+
RuntimeError: If CUDA registration fails
|
|
28
|
+
|
|
29
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/opengl/
|
|
30
|
+
"""
|
|
31
|
+
if not isinstance(pbo_id, int) or pbo_id <= 0:
|
|
32
|
+
raise ValueError(f"Invalid PBO ID: {pbo_id}")
|
|
33
|
+
|
|
34
|
+
self._handle = None
|
|
35
|
+
self.pbo_id = pbo_id
|
|
36
|
+
self._handle = register_gl_pbo(pbo_id)
|
|
37
|
+
|
|
38
|
+
def copy_from(self, image: ImageU8, sync: bool = True) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Copies ImageU8 data directly to the registered PBO (GPU→GPU).
|
|
41
|
+
|
|
42
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/opengl/
|
|
43
|
+
"""
|
|
44
|
+
if not isinstance(image, ImageU8):
|
|
45
|
+
raise TypeError(f"Expected ImageU8, got {type(image).__name__}")
|
|
46
|
+
|
|
47
|
+
if self._handle is None:
|
|
48
|
+
raise RuntimeError("GLResource has been freed")
|
|
49
|
+
|
|
50
|
+
copy_to_gl_pbo(
|
|
51
|
+
image._buffer._handle,
|
|
52
|
+
self._handle,
|
|
53
|
+
image.width,
|
|
54
|
+
image.height,
|
|
55
|
+
sync
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def free(self) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Unregisters the OpenGL resource from CUDA.
|
|
61
|
+
|
|
62
|
+
Docs & Examples: https://offerrall.github.io/pyimagecuda/opengl/
|
|
63
|
+
"""
|
|
64
|
+
if self._handle is not None:
|
|
65
|
+
unregister_gl_resource(self._handle)
|
|
66
|
+
self._handle = None
|
|
67
|
+
|
|
68
|
+
def __enter__(self):
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
72
|
+
self.free()
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def __del__(self):
|
|
76
|
+
if hasattr(self, '_handle') and self._handle is not None:
|
|
77
|
+
try:
|
|
78
|
+
self.free()
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
status = "active" if getattr(self, '_handle', None) is not None else "freed"
|
|
84
|
+
return f"GLResource(pbo_id={self.pbo_id}, status={status})"
|
pyimagecuda/image.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from .pyimagecuda_internal import create_buffer_f32, free_buffer, create_buffer_u8 #type: ignore
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
class Buffer:
|
|
5
4
|
|
|
6
5
|
def __init__(self, width: int, height: int, is_u8: bool = False):
|
|
7
6
|
create_func = create_buffer_u8 if is_u8 else create_buffer_f32
|
|
8
7
|
self._handle = create_func(width, height)
|
|
9
|
-
self.
|
|
10
|
-
self.capacity_height = height
|
|
8
|
+
self.capacity_pixels = width * height
|
|
11
9
|
|
|
12
10
|
def free(self) -> None:
|
|
13
11
|
free_buffer(self._handle)
|
|
@@ -29,11 +27,11 @@ class ImageBase:
|
|
|
29
27
|
value = int(value)
|
|
30
28
|
if value <= 0:
|
|
31
29
|
raise ValueError(f"Width must be positive, got {value}")
|
|
32
|
-
|
|
33
|
-
if value > self._buffer.
|
|
30
|
+
|
|
31
|
+
if value * self._height > self._buffer.capacity_pixels:
|
|
34
32
|
raise ValueError(
|
|
35
|
-
f"
|
|
36
|
-
f"{self._buffer.
|
|
33
|
+
f"Dimensions {value}×{self._height} exceed buffer capacity "
|
|
34
|
+
f"({self._buffer.capacity_pixels:,} pixels)"
|
|
37
35
|
)
|
|
38
36
|
|
|
39
37
|
self._width = value
|
|
@@ -48,14 +46,46 @@ class ImageBase:
|
|
|
48
46
|
if value <= 0:
|
|
49
47
|
raise ValueError(f"Height must be positive, got {value}")
|
|
50
48
|
|
|
51
|
-
if value > self._buffer.
|
|
49
|
+
if self._width * value > self._buffer.capacity_pixels:
|
|
52
50
|
raise ValueError(
|
|
53
|
-
f"
|
|
54
|
-
f"{self._buffer.
|
|
51
|
+
f"Dimensions {self._width}×{value} exceed buffer capacity "
|
|
52
|
+
f"({self._buffer.capacity_pixels:,} pixels)"
|
|
55
53
|
)
|
|
56
54
|
|
|
57
55
|
self._height = value
|
|
58
56
|
|
|
57
|
+
def resize(self, width: int, height: int) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Sets both width and height atomically with proper validation.
|
|
60
|
+
|
|
61
|
+
Recommended when changing aspect ratio to avoid temporary invalid states.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
img = Image(1920, 1080)
|
|
65
|
+
img.resize(3840, 540) # Changes aspect ratio safely
|
|
66
|
+
|
|
67
|
+
Note: This not change image data, only dimensions.
|
|
68
|
+
"""
|
|
69
|
+
width = int(width)
|
|
70
|
+
height = int(height)
|
|
71
|
+
|
|
72
|
+
if width <= 0 or height <= 0:
|
|
73
|
+
raise ValueError(f"Dimensions must be positive, got {width}×{height}")
|
|
74
|
+
|
|
75
|
+
total_pixels = width * height
|
|
76
|
+
if total_pixels > self._buffer.capacity_pixels:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"Dimensions {width}×{height} ({total_pixels:,} pixels) "
|
|
79
|
+
f"exceed buffer capacity ({self._buffer.capacity_pixels:,} pixels)"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._width = width
|
|
83
|
+
self._height = height
|
|
84
|
+
|
|
85
|
+
def get_max_capacity(self) -> int:
|
|
86
|
+
"""Returns the total pixel capacity of the buffer."""
|
|
87
|
+
return self._buffer.capacity_pixels
|
|
88
|
+
|
|
59
89
|
def free(self) -> None:
|
|
60
90
|
self._buffer.free()
|
|
61
91
|
|
|
@@ -66,13 +96,9 @@ class ImageBase:
|
|
|
66
96
|
self.free()
|
|
67
97
|
return False
|
|
68
98
|
|
|
69
|
-
def get_max_capacity(self) -> tuple[int, int]:
|
|
70
|
-
return (self._buffer.capacity_width, self._buffer.capacity_height)
|
|
71
|
-
|
|
72
99
|
def __repr__(self) -> str:
|
|
73
100
|
return f"{self.__class__.__name__}({self.width}×{self.height})"
|
|
74
101
|
|
|
75
|
-
|
|
76
102
|
class Image(ImageBase):
|
|
77
103
|
|
|
78
104
|
def __init__(self, width: int, height: int):
|