pyimagecuda 0.0.8__cp313-cp313-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.
@@ -0,0 +1,66 @@
1
+ import ctypes
2
+ import sys
3
+
4
+ __version__ = "0.0.7"
5
+
6
+ def _check_nvidia_driver():
7
+ try:
8
+ ctypes.windll.LoadLibrary("nvcuda.dll")
9
+ return True
10
+ except OSError:
11
+ return False
12
+
13
+ _INTERNAL_LOADED = False
14
+
15
+ try:
16
+ from . import pyimagecuda_internal
17
+ _INTERNAL_LOADED = True
18
+ except ImportError as e:
19
+ _INTERNAL_LOADED = False
20
+ error_msg = str(e).lower()
21
+
22
+ if "dll load failed" in error_msg:
23
+ print("\n" + "!" * 75)
24
+ print(" [CRITICAL ERROR] Failed to load pyimagecuda backend.")
25
+
26
+ if not _check_nvidia_driver():
27
+ print(" CAUSE: NVIDIA Drivers are missing or not detected.")
28
+ print(" SOLUTION: Install latest drivers from: https://www.nvidia.com/Download/index.aspx")
29
+ else:
30
+ print(" CAUSE: Microsoft Visual C++ Redistributable is missing.")
31
+ print(" SOLUTION: Install it from: https://aka.ms/vs/17/release/vc_redist.x64.exe")
32
+
33
+ print("!" * 75 + "\n")
34
+ else:
35
+ print(f"Error loading internal module: {e}")
36
+
37
+ if _INTERNAL_LOADED:
38
+ try:
39
+ from .image import Image, ImageU8
40
+ from .io import upload, download, copy, save, load, convert_float_to_u8, convert_u8_to_float, from_numpy, to_numpy
41
+ from .fill import Fill
42
+ from .resize import Resize
43
+ from .blend import Blend
44
+ from .filter import Filter
45
+ from .effect import Effect
46
+ from .adjust import Adjust
47
+ from .transform import Transform
48
+ from .text import Text
49
+ from .pyimagecuda_internal import cuda_sync # type: ignore
50
+ except ImportError as e:
51
+ print(f"Warning: Error importing Python wrappers: {e}")
52
+
53
+ def check_system():
54
+ print("--- PYIMAGECUDA DIAGNOSTIC ---")
55
+
56
+ if not _INTERNAL_LOADED:
57
+ print("❌ Backend C++ NOT loaded. See errors above.")
58
+ return False
59
+
60
+ try:
61
+ pyimagecuda_internal.cuda_sync()
62
+ print("✅ SYSTEM OK. GPU Ready & Libraries Loaded.")
63
+ return True
64
+ except Exception as e:
65
+ print(f"❌ Backend loaded but GPU runtime failed: {e}")
66
+ return False
pyimagecuda/adjust.py ADDED
@@ -0,0 +1,115 @@
1
+ from .image import Image
2
+ from .pyimagecuda_internal import ( #type: ignore
3
+ adjust_brightness_f32,
4
+ adjust_contrast_f32,
5
+ adjust_saturation_f32,
6
+ adjust_gamma_f32,
7
+ adjust_opacity_f32
8
+ )
9
+
10
+
11
+ class Adjust:
12
+
13
+ @staticmethod
14
+ def brightness(image: Image, factor: float) -> None:
15
+ """
16
+ Adjusts image brightness by adding a factor (in-place).
17
+
18
+ Positive factor brightens, negative darkens.
19
+
20
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#brightness
21
+ """
22
+ if abs(factor) < 1e-6:
23
+ return
24
+
25
+ adjust_brightness_f32(
26
+ image._buffer._handle,
27
+ image.width,
28
+ image.height,
29
+ float(factor)
30
+ )
31
+
32
+ @staticmethod
33
+ def contrast(image: Image, factor: float) -> None:
34
+ """
35
+ Adjusts image contrast relative to middle gray (in-place).
36
+
37
+ factor > 1.0 increases contrast.
38
+ factor < 1.0 decreases contrast.
39
+
40
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#contrast
41
+ """
42
+ if abs(factor - 1.0) < 1e-6:
43
+ return
44
+
45
+ adjust_contrast_f32(
46
+ image._buffer._handle,
47
+ image.width,
48
+ image.height,
49
+ float(factor)
50
+ )
51
+
52
+ @staticmethod
53
+ def saturation(image: Image, factor: float) -> None:
54
+ """
55
+ Adjusts color intensity (in-place).
56
+
57
+ factor 0.0 = Grayscale
58
+ factor 1.0 = Original
59
+ factor > 1.0 = More vibrant
60
+
61
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#saturation
62
+ """
63
+ if abs(factor - 1.0) < 1e-6:
64
+ return
65
+
66
+ adjust_saturation_f32(
67
+ image._buffer._handle,
68
+ image.width,
69
+ image.height,
70
+ float(factor)
71
+ )
72
+
73
+ @staticmethod
74
+ def gamma(image: Image, gamma: float) -> None:
75
+ """
76
+ Adjusts gamma correction (non-linear brightness) (in-place).
77
+
78
+ Useful for brightening shadows without washing out highlights.
79
+
80
+ gamma > 1.0: Brightens midtones.
81
+ gamma < 1.0: Darkens midtones.
82
+
83
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#gamma
84
+ """
85
+ if gamma <= 0:
86
+ raise ValueError("Gamma must be positive")
87
+
88
+ if abs(gamma - 1.0) < 1e-6:
89
+ return
90
+
91
+ adjust_gamma_f32(
92
+ image._buffer._handle,
93
+ image.width,
94
+ image.height,
95
+ float(gamma)
96
+ )
97
+
98
+ @staticmethod
99
+ def opacity(image: Image, factor: float) -> None:
100
+ """
101
+ Multiplies the alpha channel by a factor (In-Place).
102
+
103
+ - factor: 0.0 (Fully Transparent) to 1.0 (No change).
104
+
105
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/adjust/#opacity
106
+ """
107
+ if abs(factor - 1.0) < 1e-6:
108
+ return
109
+
110
+ adjust_opacity_f32(
111
+ image._buffer._handle,
112
+ image.width,
113
+ image.height,
114
+ float(factor)
115
+ )
pyimagecuda/blend.py ADDED
@@ -0,0 +1,294 @@
1
+ from typing import Literal
2
+ from .image import Image
3
+ from .pyimagecuda_internal import blend_f32, blend_mask_f32 # type: ignore
4
+
5
+
6
+ def _calculate_position(
7
+ base_width: int,
8
+ base_height: int,
9
+ overlay_width: int,
10
+ overlay_height: int,
11
+ anchor: str,
12
+ offset_x: int,
13
+ offset_y: int
14
+ ) -> tuple[int, int]:
15
+
16
+ pos_x = offset_x
17
+ pos_y = offset_y
18
+
19
+ if 'center' in anchor and anchor in ['top-center', 'center', 'bottom-center']:
20
+ pos_x += (base_width - overlay_width) // 2
21
+ elif 'right' in anchor:
22
+ pos_x += base_width - overlay_width
23
+
24
+ if 'center' in anchor and anchor in ['center-left', 'center', 'center-right']:
25
+ pos_y += (base_height - overlay_height) // 2
26
+ elif 'bottom' in anchor:
27
+ pos_y += base_height - overlay_height
28
+
29
+ return pos_x, pos_y
30
+
31
+
32
+ class Blend:
33
+
34
+ @staticmethod
35
+ def normal(
36
+ base: Image,
37
+ overlay: Image,
38
+ anchor: Literal['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'] = 'top-left',
39
+ offset_x: int = 0,
40
+ offset_y: int = 0,
41
+ opacity: float = 1.0
42
+ ) -> None:
43
+ """
44
+ Standard alpha blending (in-place).
45
+
46
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#normal
47
+ """
48
+ if abs(opacity) < 1e-6:
49
+ return
50
+
51
+ pos_x, pos_y = _calculate_position(
52
+ base.width, base.height,
53
+ overlay.width, overlay.height,
54
+ anchor, offset_x, offset_y
55
+ )
56
+
57
+ blend_f32(
58
+ base._buffer._handle,
59
+ overlay._buffer._handle,
60
+ base.width, base.height,
61
+ overlay.width, overlay.height,
62
+ pos_x, pos_y,
63
+ 0,
64
+ opacity
65
+ )
66
+
67
+ @staticmethod
68
+ def multiply(
69
+ base: Image,
70
+ overlay: Image,
71
+ anchor: Literal['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'] = 'top-left',
72
+ offset_x: int = 0,
73
+ offset_y: int = 0,
74
+ opacity: float = 1.0
75
+ ) -> None:
76
+ """
77
+ Multiplies color values, darkening the image (in-place).
78
+
79
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#multiply
80
+ """
81
+ if abs(opacity) < 1e-6:
82
+ return
83
+
84
+ pos_x, pos_y = _calculate_position(
85
+ base.width, base.height,
86
+ overlay.width, overlay.height,
87
+ anchor, offset_x, offset_y
88
+ )
89
+
90
+ blend_f32(
91
+ base._buffer._handle,
92
+ overlay._buffer._handle,
93
+ base.width, base.height,
94
+ overlay.width, overlay.height,
95
+ pos_x, pos_y,
96
+ 1,
97
+ opacity
98
+ )
99
+
100
+ @staticmethod
101
+ def screen(
102
+ base: Image,
103
+ overlay: Image,
104
+ anchor: Literal['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'] = 'top-left',
105
+ offset_x: int = 0,
106
+ offset_y: int = 0,
107
+ opacity: float = 1.0
108
+ ) -> None:
109
+ """
110
+ Inverted multiply, lightening the image (in-place).
111
+
112
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#screen
113
+ """
114
+ if abs(opacity) < 1e-6:
115
+ return
116
+
117
+ pos_x, pos_y = _calculate_position(
118
+ base.width, base.height,
119
+ overlay.width, overlay.height,
120
+ anchor, offset_x, offset_y
121
+ )
122
+
123
+ blend_f32(
124
+ base._buffer._handle,
125
+ overlay._buffer._handle,
126
+ base.width, base.height,
127
+ overlay.width, overlay.height,
128
+ pos_x, pos_y,
129
+ 2,
130
+ opacity
131
+ )
132
+
133
+ @staticmethod
134
+ def add(
135
+ base: Image,
136
+ overlay: Image,
137
+ anchor: Literal['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'] = 'top-left',
138
+ offset_x: int = 0,
139
+ offset_y: int = 0,
140
+ opacity: float = 1.0
141
+ ) -> None:
142
+ """
143
+ Additive blending, useful for light effects (in-place).
144
+
145
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#add
146
+ """
147
+ if abs(opacity) < 1e-6:
148
+ return
149
+
150
+ pos_x, pos_y = _calculate_position(
151
+ base.width, base.height,
152
+ overlay.width, overlay.height,
153
+ anchor, offset_x, offset_y
154
+ )
155
+
156
+ blend_f32(
157
+ base._buffer._handle,
158
+ overlay._buffer._handle,
159
+ base.width, base.height,
160
+ overlay.width, overlay.height,
161
+ pos_x, pos_y,
162
+ 3,
163
+ opacity
164
+ )
165
+
166
+ @staticmethod
167
+ def overlay(
168
+ base: Image,
169
+ overlay: Image,
170
+ anchor: Literal['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'] = 'top-left',
171
+ offset_x: int = 0,
172
+ offset_y: int = 0,
173
+ opacity: float = 1.0
174
+ ) -> None:
175
+ """
176
+ Combines Multiply and Screen. Increases contrast (in-place).
177
+
178
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#overlay
179
+ """
180
+ if abs(opacity) < 1e-6:
181
+ return
182
+
183
+ pos_x, pos_y = _calculate_position(
184
+ base.width, base.height,
185
+ overlay.width, overlay.height,
186
+ anchor, offset_x, offset_y
187
+ )
188
+
189
+ blend_f32(
190
+ base._buffer._handle,
191
+ overlay._buffer._handle,
192
+ base.width, base.height,
193
+ overlay.width, overlay.height,
194
+ pos_x, pos_y,
195
+ 4,
196
+ opacity
197
+ )
198
+
199
+ @staticmethod
200
+ def soft_light(
201
+ base: Image,
202
+ overlay: Image,
203
+ anchor: Literal['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'] = 'top-left',
204
+ offset_x: int = 0,
205
+ offset_y: int = 0,
206
+ opacity: float = 1.0
207
+ ) -> None:
208
+ """
209
+ Gentle lighting effect, like a diffuse spotlight (in-place).
210
+
211
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#soft-light
212
+ """
213
+ if abs(opacity) < 1e-6:
214
+ return
215
+
216
+ pos_x, pos_y = _calculate_position(
217
+ base.width, base.height,
218
+ overlay.width, overlay.height,
219
+ anchor, offset_x, offset_y
220
+ )
221
+
222
+ blend_f32(
223
+ base._buffer._handle,
224
+ overlay._buffer._handle,
225
+ base.width, base.height,
226
+ overlay.width, overlay.height,
227
+ pos_x, pos_y,
228
+ 5,
229
+ opacity
230
+ )
231
+
232
+ @staticmethod
233
+ def hard_light(
234
+ base: Image,
235
+ overlay: Image,
236
+ anchor: Literal['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'] = 'top-left',
237
+ offset_x: int = 0,
238
+ offset_y: int = 0,
239
+ opacity: float = 1.0
240
+ ) -> None:
241
+ """
242
+ Strong lighting effect, like a harsh spotlight (in-place).
243
+
244
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#hard-light
245
+ """
246
+ if abs(opacity) < 1e-6:
247
+ return
248
+
249
+ pos_x, pos_y = _calculate_position(
250
+ base.width, base.height,
251
+ overlay.width, overlay.height,
252
+ anchor, offset_x, offset_y
253
+ )
254
+
255
+ blend_f32(
256
+ base._buffer._handle,
257
+ overlay._buffer._handle,
258
+ base.width, base.height,
259
+ overlay.width, overlay.height,
260
+ pos_x, pos_y,
261
+ 6,
262
+ opacity
263
+ )
264
+
265
+ @staticmethod
266
+ def mask(
267
+ base: Image,
268
+ mask: Image,
269
+ anchor: Literal['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'] = 'top-left',
270
+ offset_x: int = 0,
271
+ offset_y: int = 0,
272
+ mode: Literal['alpha', 'luminance'] = 'luminance'
273
+ ) -> None:
274
+ """
275
+ Applies an image as an alpha mask to the base image (in-place).
276
+
277
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/blend/#mask
278
+ """
279
+ pos_x, pos_y = _calculate_position(
280
+ base.width, base.height,
281
+ mask.width, mask.height,
282
+ anchor, offset_x, offset_y
283
+ )
284
+
285
+ mode_int = 1 if mode == 'luminance' else 0
286
+
287
+ blend_mask_f32(
288
+ base._buffer._handle,
289
+ mask._buffer._handle,
290
+ base.width, base.height,
291
+ mask.width, mask.height,
292
+ pos_x, pos_y,
293
+ mode_int
294
+ )
pyimagecuda/effect.py ADDED
@@ -0,0 +1,165 @@
1
+ from typing import Literal
2
+
3
+ from .image import Image
4
+ from .filter import Filter
5
+ from .blend import Blend
6
+ from .fill import Fill
7
+ from .utils import ensure_capacity
8
+ from .pyimagecuda_internal import ( #type: ignore
9
+ rounded_corners_f32,
10
+ extract_alpha_f32,
11
+ colorize_alpha_mask_f32,
12
+ compute_distance_field_f32,
13
+ generate_stroke_composite_f32,
14
+ effect_vignette_f32
15
+ )
16
+
17
+
18
+ class Effect:
19
+
20
+ @staticmethod
21
+ def rounded_corners(image: Image, radius: float) -> None:
22
+ """
23
+ Applies rounded corners to the image (in-place).
24
+
25
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/effect/#rounded_corners
26
+ """
27
+ max_radius = min(image.width, image.height) / 2.0
28
+ if radius < 0: raise ValueError("Radius must be non-negative")
29
+ if radius == 0: return
30
+ if radius > max_radius: radius = max_radius
31
+
32
+ rounded_corners_f32(image._buffer._handle, image.width, image.height, float(radius))
33
+
34
+ @staticmethod
35
+ def drop_shadow(
36
+ image: Image, offset_x: int = 10,
37
+ offset_y: int = 10,
38
+ blur: int = 20,
39
+ color: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.5),
40
+ expand: bool = True,
41
+ dst_buffer: Image | None = None,
42
+ shadow_buffer: Image | None = None,
43
+ temp_buffer: Image | None = None
44
+ ) -> Image | None:
45
+ """
46
+ Adds a drop shadow to the image.
47
+
48
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/effect/#drop_shadow
49
+ """
50
+ if expand:
51
+ pad_l = blur + max(0, -offset_x)
52
+ pad_r = blur + max(0, offset_x)
53
+ pad_t = blur + max(0, -offset_y)
54
+ pad_b = blur + max(0, offset_y)
55
+ res_w, res_h = image.width + pad_l + pad_r, image.height + pad_t + pad_b
56
+ img_x, img_y = pad_l, pad_t
57
+ else:
58
+ res_w, res_h = image.width, image.height
59
+ img_x, img_y = 0, 0
60
+
61
+ if dst_buffer is None:
62
+ result = Image(res_w, res_h); ret = True
63
+ else:
64
+ ensure_capacity(dst_buffer, res_w, res_h); result = dst_buffer; ret = False
65
+
66
+ if shadow_buffer is None:
67
+ shadow = Image(res_w, res_h); own_shadow = True
68
+ else:
69
+ ensure_capacity(shadow_buffer, res_w, res_h); shadow = shadow_buffer; own_shadow = False
70
+
71
+ if expand:
72
+ if temp_buffer is None: temp = Image(res_w, res_h)
73
+ else: ensure_capacity(temp_buffer, res_w, res_h); temp = temp_buffer
74
+ Fill.color(shadow, (0,0,0,0))
75
+ temp.width, temp.height = image.width, image.height
76
+ extract_alpha_f32(image._buffer._handle, temp._buffer._handle, image.width, image.height)
77
+ Blend.normal(shadow, temp, anchor='top-left', offset_x=pad_l, offset_y=pad_t)
78
+ if temp_buffer is None: temp.free()
79
+ else:
80
+ extract_alpha_f32(image._buffer._handle, shadow._buffer._handle, image.width, image.height)
81
+
82
+ if blur > 0:
83
+ Filter.gaussian_blur(shadow, radius=blur, sigma=blur/3.0, dst_buffer=shadow)
84
+
85
+ colorize_alpha_mask_f32(shadow._buffer._handle, shadow.width, shadow.height, color)
86
+ Fill.color(result, (0,0,0,0))
87
+ Blend.normal(result, shadow, anchor='top-left', offset_x=offset_x, offset_y=offset_y)
88
+ Blend.normal(result, image, anchor='top-left', offset_x=img_x, offset_y=img_y)
89
+
90
+ if own_shadow: shadow.free()
91
+ return result if ret else None
92
+
93
+ @staticmethod
94
+ def stroke(
95
+ image: Image,
96
+ width: int = 2,
97
+ color: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
98
+ position: Literal['outside', 'inside'] = 'outside',
99
+ expand: bool = True,
100
+ dst_buffer: Image | None = None,
101
+ distance_buffer: Image | None = None,
102
+ ) -> Image | None:
103
+ """
104
+ Adds a stroke (outline) to the image using distance field method.
105
+
106
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/effect/#stroke
107
+ """
108
+ if width < 1 or width > 1000: raise ValueError("Invalid stroke width")
109
+ if position not in ('outside', 'inside'): raise ValueError("Invalid position")
110
+
111
+ if position == 'outside' and expand:
112
+ res_w, res_h = image.width + width * 2, image.height + width * 2
113
+ off_x, off_y = width, width
114
+ else:
115
+ res_w, res_h = image.width, image.height
116
+ off_x, off_y = 0, 0
117
+
118
+ if dst_buffer is None:
119
+ result = Image(res_w, res_h); ret = True
120
+ else:
121
+ ensure_capacity(dst_buffer, res_w, res_h); result = dst_buffer; ret = False
122
+
123
+ if distance_buffer is None:
124
+ distance = Image(res_w, res_h); own_dist = True
125
+ else:
126
+ ensure_capacity(distance_buffer, res_w, res_h); distance = distance_buffer; own_dist = False
127
+
128
+ compute_distance_field_f32(
129
+ image._buffer._handle, distance._buffer._handle,
130
+ image.width, image.height, res_w, res_h, off_x, off_y
131
+ )
132
+
133
+ generate_stroke_composite_f32(
134
+ image._buffer._handle, distance._buffer._handle, result._buffer._handle,
135
+ image.width, image.height, res_w, res_h, off_x, off_y,
136
+ float(width), color, 0 if position == 'outside' else 1
137
+ )
138
+
139
+ if own_dist: distance.free()
140
+
141
+ return result if ret else None
142
+
143
+ @staticmethod
144
+ def vignette(
145
+ image: Image,
146
+ radius: float = 0.9,
147
+ softness: float = 1.0,
148
+ color: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0)
149
+ ) -> None:
150
+ """
151
+ Applies a vignette effect (darkening edges) (in-place).
152
+
153
+ Docs & Examples: https://offerrall.github.io/pyimagecuda/effect/#vignette
154
+ """
155
+ if radius < 0: radius = 0.0
156
+ if softness < 0: softness = 0.0
157
+
158
+ effect_vignette_f32(
159
+ image._buffer._handle,
160
+ image.width,
161
+ image.height,
162
+ float(radius),
163
+ float(softness),
164
+ color
165
+ )