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