Skip to content

Commit 0330e9e

Browse files
committed
init
0 parents  commit 0330e9e

41 files changed

Lines changed: 3248 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/build.yml‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Build Minimal Test
2+
3+
on: [push]
4+
5+
jobs:
6+
build_wheels:
7+
name: Build on ${{ matrix.os }}
8+
runs-on: ${{ matrix.os }}
9+
strategy:
10+
matrix:
11+
os: [windows-2022]
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Install CUDA Toolkit
17+
uses: Jimver/cuda-toolkit@v0.2.21
18+
with:
19+
cuda: '12.5.1'
20+
method: 'network'
21+
use-github-cache: 'true'
22+
23+
- name: Build wheels
24+
uses: pypa/cibuildwheel@v2.21.3
25+
env:
26+
CIBW_ARCHS_WINDOWS: "AMD64"
27+
CIBW_SKIP: "cp36-* cp37-* cp38-* cp39-* pp*"
28+
CIBW_BUILD_VERBOSITY: 1
29+
30+
- uses: actions/upload-artifact@v4
31+
with:
32+
name: wheels
33+
path: ./wheelhouse/*.whl

‎.gitignore‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
__pycache__/
2+
*.pyd
3+
build/
4+
dist/
5+
wheelhouse/
6+
*.obj
7+
*.lib
8+
*.exp
9+
.vscode/

‎CMakeLists.txt‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
cmake_minimum_required(VERSION 3.18)
2+
3+
set(CMAKE_CUDA_RUNTIME_LIBRARY Static)
4+
5+
project(pyimagecuda_internal LANGUAGES CXX CUDA)
6+
7+
set(CMAKE_CXX_STANDARD 14)
8+
set(CMAKE_CUDA_STANDARD 14)
9+
10+
if(NOT CMAKE_CUDA_ARCHITECTURES)
11+
set(CMAKE_CUDA_ARCHITECTURES "50;60;61;70;75;80;86;89")
12+
endif()
13+
14+
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
15+
find_package(CUDAToolkit REQUIRED)
16+
17+
file(GLOB_RECURSE SOURCES "src/*.cu" "src/*.cpp")
18+
19+
python_add_library(pyimagecuda_internal MODULE ${SOURCES} WITH_SOABI)
20+
21+
target_include_directories(pyimagecuda_internal PRIVATE src)
22+
23+
target_link_libraries(pyimagecuda_internal PRIVATE CUDA::cudart_static Python::Module)
24+
25+
install(TARGETS pyimagecuda_internal DESTINATION pyimagecuda)
26+
install(DIRECTORY pyimagecuda DESTINATION .)

‎README.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# PyImageCUDA
2+
3+
[![Build Status](https://github.com/offerrall/pyimagecuda/actions/workflows/build.yml/badge.svg)](https://github.com/offerrall/pyimagecuda/actions)
4+
5+
> ⚠️ **STATUS: PRE-ALPHA / INFRASTRUCTURE TESTING**
6+
>
7+
> This repository is currently validating the build and distribution system.
8+
> **It is NOT ready for production use yet.**
9+
10+
### Goal
11+
GPU-accelerated (CUDA) image processing library for Python.
12+
Designed to be installed via a simple `pip install` without requiring the user to have the CUDA Toolkit or Visual Studio installed.
13+
14+
### Verification (Testers Only)
15+
16+
If you have installed a test build, you can verify that hardware acceleration is working correctly by running:
17+
18+
```python
19+
import pyimagecuda
20+
# Verifies drivers, loads the DLL, and runs a test kernel
21+
pyimagecuda.check_system()
22+
```

‎examples/blend.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from pyimagecuda import Image, load, save, Fill, Blend
2+
3+
4+
# Create background with gradient
5+
background = Image(1920, 1080)
6+
Fill.gradient(background,
7+
rgba1=(0.2, 0.3, 0.5, 1.0), # Dark blue
8+
rgba2=(0.8, 0.4, 0.6, 1.0), # Pink
9+
direction='diagonal')
10+
save(background, "./background_gradient.jpg")
11+
12+
# Load overlay image
13+
overlay = load("examples/test.jpg")
14+
15+
# Blend overlay onto gradient background
16+
Blend.normal(background, overlay, pos_x=100, pos_y=50, opacity=0.8)
17+
save(background, "./blended_on_gradient.jpg")
18+
19+
# Create solid color background
20+
background.free()
21+
background = Image(1920, 1080)
22+
Fill.color(background, rgba=(0.1, 0.1, 0.15, 1.0)) # Dark gray
23+
24+
# Multiply blend
25+
Blend.multiply(background, overlay, pos_x=200, pos_y=100, opacity=0.9)
26+
save(background, "./blended_multiply.jpg")
27+
28+
# Radial gradient background
29+
background.free()
30+
background = Image(1920, 1080)
31+
Fill.gradient(background,
32+
rgba1=(1.0, 1.0, 1.0, 1.0), # White center
33+
rgba2=(0.0, 0.0, 0.0, 1.0), # Black edges
34+
direction='radial')
35+
36+
# Screen blend
37+
Blend.screen(background, overlay, pos_x=400, pos_y=300, opacity=0.7)
38+
save(background, "./blended_screen.jpg")
39+
40+
41+
background.free()
42+
overlay.free()

‎examples/effect.py‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from pyimagecuda import Image, save, Fill, Blend, Effect
2+
3+
4+
# Create large transparent canvas
5+
canvas = Image(1920, 1080)
6+
Fill.color(canvas, rgba=(0.0, 0.0, 0.0, 0.0))
7+
8+
# Create smaller square with gradient
9+
square_size = 600
10+
square = Image(square_size, square_size)
11+
Fill.gradient(square,
12+
rgba1=(0.2, 0.5, 0.9, 1.0), # Blue
13+
rgba2=(0.9, 0.3, 0.6, 1.0), # Pink
14+
direction='diagonal')
15+
16+
# Round the corners
17+
Effect.rounded_corners(square, radius=60.0)
18+
19+
# Blend square onto canvas FIRST (centered)
20+
pos_x = (canvas.width - square_size) // 2
21+
pos_y = (canvas.height - square_size) // 2
22+
Blend.normal(canvas, square, pos_x=pos_x, pos_y=pos_y)
23+
24+
# Apply drop shadow with auto-allocated buffers
25+
canvas_with_shadow = Effect.drop_shadow(canvas,
26+
offset_x=20,
27+
offset_y=20,
28+
blur=40,
29+
color=(0.0, 0.0, 0.0, 0.7))
30+
31+
save(canvas_with_shadow, "./effect_showcase.png")
32+
33+
34+
# --- VERSION WITH CACHED BUFFERS FOR PERFORMANCE ---
35+
36+
# Pre-allocate reusable buffers
37+
dst_buffer = Image(canvas.width, canvas.height)
38+
shadow_buffer = Image(canvas.width, canvas.height)
39+
temp_buffer = Image(canvas.width, canvas.height)
40+
41+
# Reset canvas for second version
42+
Fill.color(canvas, rgba=(0.0, 0.0, 0.0, 0.0))
43+
Blend.normal(canvas, square, pos_x=pos_x, pos_y=pos_y)
44+
45+
# Apply drop shadow with cached buffers (no allocation)
46+
Effect.drop_shadow(canvas,
47+
offset_x=20,
48+
offset_y=20,
49+
blur=40,
50+
color=(0.0, 0.0, 0.0, 0.7),
51+
dst_buffer=dst_buffer,
52+
shadow_buffer=shadow_buffer,
53+
temp_buffer=temp_buffer)
54+
55+
save(dst_buffer, "./effect_showcase_cached.png")
56+
57+
58+
# Cleanup
59+
canvas.free()
60+
square.free()
61+
canvas_with_shadow.free()
62+
dst_buffer.free()
63+
shadow_buffer.free()
64+
temp_buffer.free()

‎examples/filters.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from pyimagecuda import Image, load, save, Filter
2+
3+
4+
# Load image
5+
image = load("examples/test.jpg")
6+
7+
# Gaussian blur - auto-allocated buffers
8+
blurred = Filter.gaussian_blur(image, radius=10)
9+
save(blurred, "./blurred_auto.jpg")
10+
11+
# Gaussian blur - with custom sigma
12+
blurred_custom = Filter.gaussian_blur(image, radius=15, sigma=5.0)
13+
save(blurred_custom, "./blurred_custom_sigma.jpg")
14+
15+
# Sharpen filter
16+
sharpened = Filter.sharpen(image, strength=1.5)
17+
save(sharpened, "./sharpened.jpg")
18+
19+
# Reuse buffers for performance
20+
dst_buffer = Image(image.width, image.height)
21+
temp_buffer = Image(image.width, image.height)
22+
23+
Filter.gaussian_blur(image, radius=20, dst_buffer=dst_buffer, temp_buffer=temp_buffer)
24+
save(dst_buffer, "./blurred_reused.jpg")
25+
26+
# Chain filters using buffer reuse
27+
Filter.gaussian_blur(image, radius=5, dst_buffer=dst_buffer, temp_buffer=temp_buffer)
28+
Filter.sharpen(dst_buffer, strength=2.0, dst_buffer=dst_buffer)
29+
save(dst_buffer, "./blur_then_sharpen.jpg")
30+
31+
32+
image.free()
33+
blurred.free()
34+
blurred_custom.free()
35+
sharpened.free()
36+
dst_buffer.free()
37+
temp_buffer.free()

‎examples/resize.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from pyimagecuda import Image, load, save, Resize
2+
3+
4+
target_resolution = (1024, 768)
5+
6+
image = load("examples/test.jpg")
7+
cache_result = Image(*target_resolution)
8+
9+
10+
# No dst buffer provided, a new Image will be created and returned
11+
resized_image = Resize.bilinear(image, *target_resolution)
12+
save(resized_image, "./resized_test.jpg")
13+
14+
# Reuse existing dst buffer
15+
Resize.bilinear(image, *target_resolution, dst_buffer=cache_result)
16+
save(cache_result, "./resized_test_cached.jpg")
17+
18+
19+
image.free()
20+
resized_image.free()
21+
cache_result.free()

‎examples/test.jpg‎

269 KB
Loading

‎pyimagecuda/__init__.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import ctypes
2+
import sys
3+
4+
def _check_nvidia_driver():
5+
try:
6+
ctypes.windll.LoadLibrary("nvcuda.dll")
7+
return True
8+
except OSError:
9+
return False
10+
11+
_INTERNAL_LOADED = False
12+
13+
try:
14+
from . import pyimagecuda_internal
15+
_INTERNAL_LOADED = True
16+
except ImportError as e:
17+
_INTERNAL_LOADED = False
18+
error_msg = str(e).lower()
19+
20+
if "dll load failed" in error_msg:
21+
print("\n" + "!" * 75)
22+
print(" [CRITICAL ERROR] Failed to load pyimagecuda backend.")
23+
24+
if not _check_nvidia_driver():
25+
print(" CAUSE: NVIDIA Drivers are missing or not detected.")
26+
print(" SOLUTION: Install latest drivers from: https://www.nvidia.com/Download/index.aspx")
27+
else:
28+
print(" CAUSE: Microsoft Visual C++ Redistributable is missing.")
29+
print(" SOLUTION: Install it from: https://aka.ms/vs/17/release/vc_redist.x64.exe")
30+
31+
print("!" * 75 + "\n")
32+
else:
33+
print(f"Error loading internal module: {e}")
34+
35+
if _INTERNAL_LOADED:
36+
try:
37+
from .image import Image, ImageU8
38+
from .io import upload, download, copy, save, load, convert_float_to_u8, convert_u8_to_float
39+
from .fill import Fill
40+
from .resize import Resize
41+
from .blend import Blend
42+
from .filter import Filter
43+
from .effect import Effect
44+
except ImportError as e:
45+
print(f"Warning: Error importing Python wrappers: {e}")
46+
47+
def check_system():
48+
print("--- PYIMAGECUDA DIAGNOSTIC ---")
49+
50+
if not _INTERNAL_LOADED:
51+
print("❌ Backend C++ NOT loaded. See errors above.")
52+
return False
53+
54+
try:
55+
pyimagecuda_internal.cuda_sync()
56+
print("✅ SYSTEM OK. GPU Ready & Libraries Loaded.")
57+
return True
58+
except Exception as e:
59+
print(f"❌ Backend loaded but GPU runtime failed: {e}")
60+
return False

0 commit comments

Comments
 (0)