1#![deny(missing_docs)]
8#![allow(non_camel_case_types)]
9
10use bitflags::bitflags;
13use cairo::{Format, ImageSurface};
14use core::ffi::c_void;
15use core::ptr::{null, null_mut};
16use hvif::Hvif;
17use std::os::raw::c_char;
18
19type GdkPixbuf = c_void;
20type GdkPixbufAnimation = c_void;
21type GModule = c_void;
22type GError = c_void;
23type gpointer = *mut c_void;
24type gchar = c_char;
25type guchar = u8;
26
27#[repr(C)]
29pub struct gboolean(i32);
30
31impl gboolean {
32 const FALSE: gboolean = gboolean(0);
33 const TRUE: gboolean = gboolean(1);
34}
35
36#[repr(C)]
37struct GdkPixbufModulePattern {
38 prefix: *const c_char,
39 mask: *const c_char,
40 relevance: i32,
41}
42
43bitflags! {
44 pub struct GdkPixbufFormatFlags: u32 {
46 const WRITABLE = 0b001;
48
49 const SCALABLE = 0b010;
51
52 const THREADSAFE = 0b100;
54 }
55}
56
57#[repr(C)]
59pub struct GdkPixbufFormat {
60 name: *const gchar,
61 signature: *const GdkPixbufModulePattern,
62 domain: *mut gchar,
63 description: *const gchar,
64 mime_types: *const *const gchar,
65 extensions: *const *const gchar,
66 flags: GdkPixbufFormatFlags,
67 disabled: gboolean,
68 license: *const gchar,
69}
70
71type GdkPixbufModuleSizeFunc =
72 Option<extern "C" fn(width: *mut i32, height: *mut i32, user_data: gpointer)>;
73type GdkPixbufModulePreparedFunc = Option<
74 extern "C" fn(pixbuf: *mut GdkPixbuf, anim: *mut GdkPixbufAnimation, user_data: gpointer),
75>;
76type GdkPixbufModuleUpdatedFunc = Option<
77 extern "C" fn(
78 pixbuf: *mut GdkPixbuf,
79 x: i32,
80 y: i32,
81 width: i32,
82 height: i32,
83 user_data: gpointer,
84 ),
85>;
86
87#[repr(C)]
89pub struct GdkPixbufModule {
90 module_name: *const c_char,
91 module_path: *const c_char,
92 module: *const GModule,
93 info: *const GdkPixbufFormat,
94 load: *const c_void,
95 load_xpm_data: *const c_void,
96 begin_load: Option<
97 extern "C" fn(
98 GdkPixbufModuleSizeFunc,
99 GdkPixbufModulePreparedFunc,
100 GdkPixbufModuleUpdatedFunc,
101 gpointer,
102 *mut *mut GError,
103 ) -> gpointer,
104 >,
105 stop_load: Option<extern "C" fn(gpointer, *mut *mut GError) -> gboolean>,
106 load_increment:
107 Option<extern "C" fn(gpointer, *const guchar, u32, *mut *mut GError) -> gboolean>,
108 load_animation: *const c_void,
109 save: *const c_void,
110 save_to_callback: *const c_void,
111 is_save_option_supported: *const c_void,
112 reserved1: *const c_void,
113 reserved2: *const c_void,
114 reserved3: *const c_void,
115 reserved4: *const c_void,
116}
117
118struct Decoder {
119 data: Vec<u8>,
120 size_func: GdkPixbufModuleSizeFunc,
121 prepared_func: GdkPixbufModulePreparedFunc,
122 user_data: gpointer,
123}
124
125impl Decoder {
126 fn new(
127 size_func: GdkPixbufModuleSizeFunc,
128 prepared_func: GdkPixbufModulePreparedFunc,
129 user_data: gpointer,
130 ) -> Decoder {
131 Decoder {
132 data: Vec::with_capacity(1024),
133 size_func,
134 prepared_func,
135 user_data,
136 }
137 }
138}
139
140extern "C" fn begin_load(
141 size_func: GdkPixbufModuleSizeFunc,
142 prepared_func: GdkPixbufModulePreparedFunc,
143 _updated_func: GdkPixbufModuleUpdatedFunc,
144 user_data: gpointer,
145 _error: *mut *mut GError,
146) -> gpointer {
147 let decoder = Decoder::new(size_func, prepared_func, user_data);
148 let boxed = Box::new(decoder);
149 Box::into_raw(boxed) as *mut c_void
150}
151
152#[repr(u32)]
153enum GdkColorspace {
154 Rgb = 0,
155}
156
157#[link(name = "gdk_pixbuf-2.0")]
158extern "C" {
159 fn gdk_pixbuf_new(
160 colorspace: GdkColorspace,
161 has_alpha: gboolean,
162 bits_per_sample: i32,
163 width: i32,
164 height: i32,
165 ) -> *mut GdkPixbuf;
166 fn gdk_pixbuf_get_pixels_with_length(pixbuf: *const GdkPixbuf, length: *mut u32)
167 -> *mut guchar;
168 fn gdk_pixbuf_get_rowstride(pixbuf: *const GdkPixbuf) -> i32;
169}
170
171struct Pixbuf {
172 inner: *mut GdkPixbuf,
173}
174
175impl Pixbuf {
176 fn new(has_alpha: bool, bits_per_sample: i32, width: i32, height: i32) -> Pixbuf {
177 let has_alpha = if has_alpha {
178 gboolean::TRUE
179 } else {
180 gboolean::FALSE
181 };
182 let inner = unsafe {
183 gdk_pixbuf_new(
184 GdkColorspace::Rgb,
185 has_alpha,
186 bits_per_sample,
187 width,
188 height,
189 )
190 };
191 Pixbuf { inner }
192 }
193
194 fn get_pixels_mut(&self) -> &mut [u8] {
195 let mut length = core::mem::MaybeUninit::uninit();
196 unsafe {
197 let pixels = gdk_pixbuf_get_pixels_with_length(self.inner, length.as_mut_ptr());
198 let length = length.assume_init();
199 std::slice::from_raw_parts_mut(pixels, length as usize)
200 }
201 }
202
203 fn get_rowstride(&self) -> i32 {
204 unsafe { gdk_pixbuf_get_rowstride(self.inner) }
205 }
206}
207
208fn bgra_to_rgba(data: &mut [u8]) {
210 for chunk in data.chunks_exact_mut(4) {
211 let b = chunk[0];
212 let r = chunk[2];
213 chunk[0] = r;
214 chunk[2] = b;
215 }
216}
217
218extern "C" fn stop_load(context: gpointer, _error: *mut *mut GError) -> gboolean {
219 let decoder = unsafe { Box::from_raw(context as *mut Decoder) };
220 if let Ok((i, hvif)) = Hvif::parse(&decoder.data) {
221 if !i.is_empty() {
222 return gboolean::FALSE;
223 }
224
225 let mut width = 64;
227 let mut height = 64;
228 if let Some(size_func) = decoder.size_func {
229 size_func(&mut width, &mut height, decoder.user_data);
230 }
231 if width == 0 || height == 0 {
232 return gboolean::FALSE;
233 }
234
235 let pixbuf = Pixbuf::new(true, 8, width, height);
236 let stride = pixbuf.get_rowstride();
237
238 unsafe {
239 let pixels = pixbuf.get_pixels_mut();
240 pixels.fill(0);
241 let mut surface =
242 ImageSurface::create_for_data_unsafe(pixels.as_mut_ptr(), Format::ARgb32, width, height, stride)
243 .unwrap();
244 hvif::render(hvif, &mut surface).unwrap();
245 }
246
247 bgra_to_rgba(pixbuf.get_pixels_mut());
250
251 if let Some(prepared_func) = decoder.prepared_func {
253 prepared_func(pixbuf.inner, null_mut(), decoder.user_data);
254 }
255 gboolean::TRUE
256 } else {
257 gboolean::FALSE
258 }
259}
260
261extern "C" fn load_increment(
262 context: gpointer,
263 buf: *const guchar,
264 size: u32,
265 _error: *mut *mut GError,
266) -> gboolean {
267 let mut decoder = unsafe { Box::from_raw(context as *mut Decoder) };
268 let slice = unsafe { std::slice::from_raw_parts(buf, size as usize) };
269 decoder.data.extend(slice);
270 Box::into_raw(decoder);
271 gboolean::TRUE
272}
273
274#[no_mangle]
276pub unsafe extern "C" fn fill_vtable(module: *mut GdkPixbufModule) {
277 let mut module = Box::from_raw(module);
278
279 module.begin_load = Some(begin_load);
280 module.stop_load = Some(stop_load);
281 module.load_increment = Some(load_increment);
282
283 Box::into_raw(module);
284}
285
286const fn c_str(content: &[u8]) -> *const gchar {
287 content.as_ptr() as *const gchar
288}
289
290#[no_mangle]
292pub unsafe extern "C" fn fill_info(info: *mut GdkPixbufFormat) {
293 const SIGNATURE: [GdkPixbufModulePattern; 2] = [
294 GdkPixbufModulePattern {
295 prefix: c_str(b"ncif\0"),
296 mask: c_str(b" \0"),
297 relevance: 100,
298 },
299 GdkPixbufModulePattern {
300 prefix: null(),
301 mask: null(),
302 relevance: 0,
303 },
304 ];
305 const MIME_TYPES: [*const gchar; 2] = [c_str(b"image/x-hvif\0"), null()];
306 const EXTENSIONS: [*const gchar; 2] = [c_str(b"hvif\0"), null()];
307
308 let mut info = Box::from_raw(info);
309
310 info.name = c_str(b"hvif\0");
311 info.signature = SIGNATURE.as_ptr();
312 info.description = c_str(b"Haiku Vector Icon Format\0");
313 info.mime_types = MIME_TYPES.as_ptr();
314 info.extensions = EXTENSIONS.as_ptr();
315 info.flags = GdkPixbufFormatFlags::THREADSAFE;
316 info.license = c_str(b"BSD\0");
317 info.disabled = gboolean::FALSE;
318
319 Box::into_raw(info);
320}