Skip to content

Commit d374cbb

Browse files
authored
Initial image implementation (#5)
1 parent 4548240 commit d374cbb

File tree

18 files changed

+669
-214
lines changed

18 files changed

+669
-214
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/images/logo.png

66.8 KB
Loading

crates/processing_ffi/src/color.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use bevy::color::{LinearRgba, Srgba};
2+
13
/// A sRGB (?) color
24
#[repr(C)]
35
#[derive(Debug, Clone, Copy)]
@@ -13,3 +15,21 @@ impl From<Color> for bevy::color::Color {
1315
bevy::color::Color::srgba(color.r, color.g, color.b, color.a)
1416
}
1517
}
18+
19+
impl From<LinearRgba> for Color {
20+
fn from(lin: LinearRgba) -> Self {
21+
let srgb: Srgba = lin.into();
22+
srgb.into()
23+
}
24+
}
25+
26+
impl From<Srgba> for Color {
27+
fn from(srgb: Srgba) -> Self {
28+
Color {
29+
r: srgb.red,
30+
g: srgb.green,
31+
b: srgb.blue,
32+
a: srgb.alpha,
33+
}
34+
}
35+
}

crates/processing_ffi/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
panic,
55
};
66

7-
use processing::prelude::error::ProcessingError;
7+
pub(crate) use processing::prelude::error::ProcessingError;
88

99
thread_local! {
1010
static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };

crates/processing_ffi/src/lib.rs

Lines changed: 151 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use bevy::prelude::Entity;
1+
use bevy::{
2+
prelude::Entity,
3+
render::render_resource::{Extent3d, TextureFormat},
4+
};
25
use processing::prelude::*;
36

47
use crate::color::Color;
@@ -26,43 +29,43 @@ pub extern "C" fn processing_init() {
2629
/// - window_handle is a valid GLFW window pointer.
2730
/// - This is called from the same thread as init.
2831
#[unsafe(no_mangle)]
29-
pub extern "C" fn processing_create_surface(
32+
pub extern "C" fn processing_surface_create(
3033
window_handle: u64,
3134
display_handle: u64,
3235
width: u32,
3336
height: u32,
3437
scale_factor: f32,
3538
) -> u64 {
3639
error::clear_error();
37-
error::check(|| create_surface(window_handle, display_handle, width, height, scale_factor))
40+
error::check(|| surface_create(window_handle, display_handle, width, height, scale_factor))
3841
.map(|e| e.to_bits())
3942
.unwrap_or(0)
4043
}
4144

4245
/// Destroy the surface associated with the given window ID.
4346
///
4447
/// SAFETY:
45-
/// - Init and create_surface have been called.
46-
/// - window_id is a valid ID returned from create_surface.
48+
/// - Init and surface_create have been called.
49+
/// - window_id is a valid ID returned from surface_create.
4750
/// - This is called from the same thread as init.
4851
#[unsafe(no_mangle)]
49-
pub extern "C" fn processing_destroy_surface(window_id: u64) {
52+
pub extern "C" fn processing_surface_destroy(window_id: u64) {
5053
error::clear_error();
5154
let window_entity = Entity::from_bits(window_id);
52-
error::check(|| destroy_surface(window_entity));
55+
error::check(|| surface_destroy(window_entity));
5356
}
5457

5558
/// Update window size when resized.
5659
///
5760
/// SAFETY:
58-
/// - Init and create_surface have been called.
59-
/// - window_id is a valid ID returned from create_surface.
61+
/// - Init and surface_create have been called.
62+
/// - window_id is a valid ID returned from surface_create.
6063
/// - This is called from the same thread as init.
6164
#[unsafe(no_mangle)]
62-
pub extern "C" fn processing_resize_surface(window_id: u64, width: u32, height: u32) {
65+
pub extern "C" fn processing_surface_resize(window_id: u64, width: u32, height: u32) {
6366
error::clear_error();
6467
let window_entity = Entity::from_bits(window_id);
65-
error::check(|| resize_surface(window_entity, width, height));
68+
error::check(|| surface_resize(window_entity, width, height));
6669
}
6770

6871
/// Set the background color for the given window.
@@ -73,7 +76,21 @@ pub extern "C" fn processing_resize_surface(window_id: u64, width: u32, height:
7376
pub extern "C" fn processing_background_color(window_id: u64, color: Color) {
7477
error::clear_error();
7578
let window_entity = Entity::from_bits(window_id);
76-
error::check(|| background_color(window_entity, color.into()));
79+
error::check(|| record_command(window_entity, DrawCommand::BackgroundColor(color.into())));
80+
}
81+
82+
/// Set the background image for the given window.
83+
///
84+
/// SAFETY:
85+
/// - This is called from the same thread as init.
86+
/// - image_id is a valid ID returned from processing_image_create.
87+
/// - The image has been fully uploaded.
88+
#[unsafe(no_mangle)]
89+
pub extern "C" fn processing_background_image(window_id: u64, image_id: u64) {
90+
error::clear_error();
91+
let window_entity = Entity::from_bits(window_id);
92+
let image_entity = Entity::from_bits(image_id);
93+
error::check(|| record_command(window_entity, DrawCommand::BackgroundImage(image_entity)));
7794
}
7895

7996
/// Begins the draw for the given window.
@@ -126,8 +143,8 @@ pub extern "C" fn processing_exit(exit_code: u8) {
126143
/// Set the fill color.
127144
///
128145
/// SAFETY:
129-
/// - Init and create_surface have been called.
130-
/// - window_id is a valid ID returned from create_surface.
146+
/// - Init and surface_create have been called.
147+
/// - window_id is a valid ID returned from surface_create.
131148
/// - This is called from the same thread as init.
132149
#[unsafe(no_mangle)]
133150
pub extern "C" fn processing_set_fill(window_id: u64, r: f32, g: f32, b: f32, a: f32) {
@@ -140,8 +157,8 @@ pub extern "C" fn processing_set_fill(window_id: u64, r: f32, g: f32, b: f32, a:
140157
/// Set the stroke color.
141158
///
142159
/// SAFETY:
143-
/// - Init and create_surface have been called.
144-
/// - window_id is a valid ID returned from create_surface.
160+
/// - Init and surface_create have been called.
161+
/// - window_id is a valid ID returned from surface_create.
145162
/// - This is called from the same thread as init.
146163
#[unsafe(no_mangle)]
147164
pub extern "C" fn processing_set_stroke_color(window_id: u64, r: f32, g: f32, b: f32, a: f32) {
@@ -154,8 +171,8 @@ pub extern "C" fn processing_set_stroke_color(window_id: u64, r: f32, g: f32, b:
154171
/// Set the stroke weight.
155172
///
156173
/// SAFETY:
157-
/// - Init and create_surface have been called.
158-
/// - window_id is a valid ID returned from create_surface.
174+
/// - Init and surface_create have been called.
175+
/// - window_id is a valid ID returned from surface_create.
159176
/// - This is called from the same thread as init.
160177
#[unsafe(no_mangle)]
161178
pub extern "C" fn processing_set_stroke_weight(window_id: u64, weight: f32) {
@@ -167,8 +184,8 @@ pub extern "C" fn processing_set_stroke_weight(window_id: u64, weight: f32) {
167184
/// Disable fill for subsequent shapes.
168185
///
169186
/// SAFETY:
170-
/// - Init and create_surface have been called.
171-
/// - window_id is a valid ID returned from create_surface.
187+
/// - Init and surface_create have been called.
188+
/// - window_id is a valid ID returned from surface_create.
172189
/// - This is called from the same thread as init.
173190
#[unsafe(no_mangle)]
174191
pub extern "C" fn processing_no_fill(window_id: u64) {
@@ -180,8 +197,8 @@ pub extern "C" fn processing_no_fill(window_id: u64) {
180197
/// Disable stroke for subsequent shapes.
181198
///
182199
/// SAFETY:
183-
/// - Init and create_surface have been called.
184-
/// - window_id is a valid ID returned from create_surface.
200+
/// - Init and surface_create have been called.
201+
/// - window_id is a valid ID returned from surface_create.
185202
/// - This is called from the same thread as init.
186203
#[unsafe(no_mangle)]
187204
pub extern "C" fn processing_no_stroke(window_id: u64) {
@@ -193,8 +210,8 @@ pub extern "C" fn processing_no_stroke(window_id: u64) {
193210
/// Draw a rectangle.
194211
///
195212
/// SAFETY:
196-
/// - Init and create_surface have been called.
197-
/// - window_id is a valid ID returned from create_surface.
213+
/// - Init and surface_create have been called.
214+
/// - window_id is a valid ID returned from surface_create.
198215
/// - This is called from the same thread as init.
199216
#[unsafe(no_mangle)]
200217
pub extern "C" fn processing_rect(
@@ -223,3 +240,113 @@ pub extern "C" fn processing_rect(
223240
)
224241
});
225242
}
243+
244+
/// Create an image from raw pixel data.
245+
///
246+
/// # Safety
247+
/// - Init has been called.
248+
/// - data is a valid pointer to data_len bytes of RGBA pixel data.
249+
/// - This is called from the same thread as init.
250+
#[unsafe(no_mangle)]
251+
pub unsafe extern "C" fn processing_image_create(
252+
width: u32,
253+
height: u32,
254+
data: *const u8,
255+
data_len: usize,
256+
) -> u64 {
257+
error::clear_error();
258+
// SAFETY: Caller must ensure that `data` is valid for `data_len` bytes.
259+
let data = unsafe { std::slice::from_raw_parts(data, data_len) };
260+
error::check(|| {
261+
let size = Extent3d {
262+
width,
263+
height,
264+
depth_or_array_layers: 1,
265+
};
266+
image_create(size, data.to_vec(), TextureFormat::Rgba8UnormSrgb)
267+
})
268+
.map(|entity| entity.to_bits())
269+
.unwrap_or(0)
270+
}
271+
272+
/// Load an image from a file path.
273+
///
274+
/// # Safety
275+
/// - Init has been called.
276+
/// - path is a valid null-terminated C string.
277+
/// - This is called from the same thread as init.
278+
///
279+
/// Note: This function is currently synchronous but Bevy's asset loading is async.
280+
/// The image may not be immediately available. This needs to be improved.
281+
#[unsafe(no_mangle)]
282+
pub unsafe extern "C" fn processing_image_load(path: *const std::ffi::c_char) -> u64 {
283+
error::clear_error();
284+
285+
// SAFETY: Caller guarantees path is a valid C string
286+
let c_str = unsafe { std::ffi::CStr::from_ptr(path) };
287+
let path_str = match c_str.to_str() {
288+
Ok(s) => s,
289+
Err(_) => {
290+
error::set_error("Invalid UTF-8 in image path");
291+
return 0;
292+
}
293+
};
294+
295+
error::check(|| image_load(path_str))
296+
.map(|entity| entity.to_bits())
297+
.unwrap_or(0)
298+
}
299+
300+
#[unsafe(no_mangle)]
301+
pub extern "C" fn processing_image_resize(image_id: u64, new_width: u32, new_height: u32) {
302+
error::clear_error();
303+
let image_entity = Entity::from_bits(image_id);
304+
let new_size = Extent3d {
305+
width: new_width,
306+
height: new_height,
307+
depth_or_array_layers: 1,
308+
};
309+
error::check(|| image_resize(image_entity, new_size));
310+
}
311+
312+
/// Load pixels from an image into a caller-provided buffer.
313+
///
314+
/// # Safety
315+
/// - Init and image_create have been called.
316+
/// - image_id is a valid ID returned from image_create.
317+
/// - buffer is a valid pointer to at least buffer_len Color elements.
318+
/// - buffer_len must equal width * height of the image.
319+
/// - This is called from the same thread as init.
320+
#[unsafe(no_mangle)]
321+
pub unsafe extern "C" fn processing_image_load_pixels(
322+
image_id: u64,
323+
buffer: *mut Color,
324+
buffer_len: usize,
325+
) {
326+
error::clear_error();
327+
let image_entity = Entity::from_bits(image_id);
328+
error::check(|| {
329+
let colors = image_load_pixels(image_entity)?;
330+
331+
// Validate buffer size
332+
if colors.len() != buffer_len {
333+
let error_msg = format!(
334+
"Buffer size mismatch: expected {}, got {}",
335+
colors.len(),
336+
buffer_len
337+
);
338+
error::set_error(&error_msg);
339+
return Err(error::ProcessingError::InvalidArgument(error_msg));
340+
}
341+
342+
// SAFETY: Caller guarantees buffer is valid for buffer_len elements
343+
unsafe {
344+
let buffer_slice = std::slice::from_raw_parts_mut(buffer, buffer_len);
345+
for (i, color) in colors.iter().enumerate() {
346+
buffer_slice[i] = Color::from(*color);
347+
}
348+
}
349+
350+
Ok(())
351+
});
352+
}

crates/processing_render/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ raw-window-handle = "0.6"
1313
thiserror = "2"
1414
tracing = "0.1"
1515
tracing-subscriber = "0.3"
16+
half = "2.7"
17+
crossbeam-channel = "0.5"
1618

1719
[target.'cfg(target_os = "macos")'.dependencies]
1820
objc2 = { version = "0.6", default-features = false }

crates/processing_render/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ pub enum ProcessingError {
1414
HandleError(#[from] raw_window_handle::HandleError),
1515
#[error("Invalid window handle provided")]
1616
InvalidWindowHandle,
17+
#[error("Image not found")]
18+
ImageNotFound,
19+
#[error("Unsupported texture format")]
20+
UnsupportedTextureFormat,
21+
#[error("Invalid argument: {0}")]
22+
InvalidArgument(String),
1723
}

0 commit comments

Comments
 (0)