pixbufloader_hvif/
lib.rs

1// Copyright (c) 2021 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#![deny(missing_docs)]
8#![allow(non_camel_case_types)]
9
10//! Generates a shared object suitable for use with gdk-pixbuf, adding support for the HVIF format.
11
12use 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/// The gobject version of bool.
28#[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    /// Flags which allow a module to specify further details about the supported operations.
45    pub struct GdkPixbufFormatFlags: u32 {
46        /// The module can write out images in the format.
47        const WRITABLE = 0b001;
48
49        /// The image format is scalable.
50        const SCALABLE = 0b010;
51
52        /// The module is threadsafe. gdk-pixbuf ignores modules that are not marked as threadsafe.
53        const THREADSAFE = 0b100;
54    }
55}
56
57/// A `GdkPixbufFormat` contains information about the image format accepted by a module.
58#[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/// A module.
88#[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
208// TODO: optimise that a bit.
209fn 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        // Query the size wanted by the caller.
226        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        // Cairo and GDK-Pixbuf don’t support the same colour format, so we have to implement the
248        // conversion manually…
249        bgra_to_rgba(pixbuf.get_pixels_mut());
250
251        // Everything went fine, now we can signal the pixbuf is ready!
252        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/// Sets the entrypoints of the module’s vtable.
275#[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/// Fills the metadata of this module.
291#[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}