Skip to main content

gfx/
lib.rs

1// gfx -- surfaces and the draw context (clip, origin, colour), fills, blits  [src/gfx/surface.s]
2//
3//   Surface::new(w, h, fmt)            zeroed pixels, stride 64-byte aligned (sides 0..65536)
4//   Dc::new(w, h) / dc.begin(&sf) / dc.end()      the draw context of a target; begin saves
5//                                      clip, origin and opacity and resets them for another
6//                                      target, end restores them (gfx_begin / gfx_end)
7//   dc.begin_rows(&sf, row0, h)        sf holds rows row0 .. row0 + sf.h of a target h rows
8//                                      high (a row band drawn on a thread of its own): the
9//                                      coordinates stay the target's, only the pixel rows move
10//   dc: set_color(argb), set_opacity(op), clip_push(x, y, w, h) / clip_pop(), origin(x, y),
11//       translate(dx, dy), fill / clear / hline / vline / frame / blit / blit_scaled (sf, ...)
12//   path (Path, 24.8 and float API), raster (Raster), stroke (Path::stroke), shapes (Shapes),
13//   blend (row kernels), straight (straight-alpha rows), blendmode (paint.net blend modes),
14//   tmask (tiled masks), flood (flood fill), gradient, pathdata (SVG path data), dash
15//
16// Colours are straight 0xAARRGGBB; Dc keeps the premultiplied form for the kernels.  The
17// surface is passed to every drawing call: Dc holds no pointer to it.
18
19#![no_std]
20#![forbid(unsafe_code)]
21
22// in a module's build (--cfg jflib_module, docs/CRATES.md) the module runtime goes by the same name
23#[cfg(jflib_module)]
24extern crate modrt as rt;
25
26extern crate alloc;
27
28pub mod blend;
29pub mod blendmode;
30pub mod dash;
31pub mod flood;
32pub mod gradient;
33pub mod path;
34mod panel;
35pub mod pathdata;
36pub mod raster;
37pub mod shapes;
38pub mod straight;
39pub mod stroke;
40pub mod tmask;
41pub mod xform;
42
43use alloc::vec;
44use alloc::vec::Vec;
45
46pub use rt::fx::div255;
47
48pub const FMT_XRGB: u8 = 0; // 0x??RRGGBB, alpha byte ignored (window framebuffer)
49pub const FMT_PARGB: u8 = 1; // premultiplied 0xAARRGGBB (images, layers)
50pub const FMT_A8: u8 = 2; // 8-bit coverage / mask
51pub const FMT_ARGB: u8 = 3; // straight 0xAARRGGBB = bytes B, G, R, A
52pub const SFF_OWNED: u8 = 1;
53pub const SFF_OPAQUE: u8 = 2; // hint: every alpha is 255
54pub const FX_ONE: i32 = 256; // 24.8 fixed point: 1 pixel
55pub const RULE_NONZERO: u32 = 0;
56pub const RULE_EVENODD: u32 = 1;
57pub const JOIN_MITER: u32 = 0; // stroke style = join | cap << 4
58pub const JOIN_ROUND: u32 = 1;
59pub const JOIN_BEVEL: u32 = 2;
60pub const CAP_BUTT: u32 = 0;
61pub const CAP_ROUND: u32 = 1;
62pub const CAP_SQUARE: u32 = 2;
63pub const ERR_FAIL: i32 = -1;
64pub const ERR_NOMEM: i32 = -8;
65pub const ERR_LIMIT: i32 = -15;
66const SURF_MAX: i32 = 65536; // largest surface side (px)
67
68/// pixels of one image: rows of `stride` bytes (4 per pixel, A8: 1)
69#[derive(Default)]
70pub struct Surface {
71    pub px: Vec<u8>,
72    pub w: i32,
73    pub h: i32,
74    pub stride: i32,
75    pub fmt: u8,
76    pub flags: u8,
77    pub opac: u8,  // layer opacity 0..255
78    pub blend: u8, // layer blend mode
79}
80
81/// n zeroed bytes | None when the memory is not there (instead of an abort). A probe
82/// reservation first, then vec![0; n]: that is alloc_zeroed, whose fresh pages need no
83/// clearing pass.
84pub fn try_zeroed(n: usize) -> Option<Vec<u8>> {
85    let mut probe: Vec<u8> = Vec::new();
86    probe.try_reserve_exact(n).ok()?;
87    drop(probe);
88    Some(vec![0u8; n])
89}
90
91impl Surface {
92    /// zeroed pixels, stride 64-byte aligned; sides outside 0..65536 give an empty surface
93    /// (surf_alloc fails). Byte lengths beyond the target's addressable allocation size
94    /// also give an empty surface, before allocating pixels.
95    pub fn new(w: i32, h: i32, fmt: u8) -> Surface {
96        let Some((stride, bytes)) = Surface::layout(w, h, fmt) else {
97            return Surface { px: Vec::new(), w: 0, h: 0, stride: 0, fmt, flags: 0, opac: 255, blend: 0 };
98        };
99        Surface { px: vec![0u8; bytes], w, h, stride, fmt, flags: SFF_OWNED, opac: 255, blend: 0 }
100    }
101
102    /// like `new`, but None (instead of an empty surface or an abort) when the size is
103    /// invalid or the memory is not there (try_zeroed). For sizes that come from a file or
104    /// from the user.
105    pub fn try_new(w: i32, h: i32, fmt: u8) -> Option<Surface> {
106        let (stride, bytes) = Surface::layout(w, h, fmt)?;
107        Some(Surface { px: try_zeroed(bytes)?, w, h, stride, fmt, flags: SFF_OWNED, opac: 255, blend: 0 })
108    }
109
110    // (stride, pixel bytes) or None for sides outside 0..=SURF_MAX or a size beyond isize::MAX
111    fn layout(w: i32, h: i32, fmt: u8) -> Option<(i32, usize)> {
112        if !(0..=SURF_MAX).contains(&w) || !(0..=SURF_MAX).contains(&h) {
113            return None;
114        }
115        let row = if fmt == FMT_A8 { w } else { w * 4 };
116        let stride = (row + 63) & !63;
117        let bytes = (stride as usize).checked_mul(h as usize).filter(|&n| n <= isize::MAX as usize)?;
118        Some((stride, bytes.max(64)))
119    }
120
121    /// a tightly packed A8 surface (glyph masks: stride = w)
122    pub fn new_a8_packed(w: i32, h: i32) -> Surface {
123        Surface { px: vec![0u8; (w * h) as usize], w, h, stride: w, fmt: FMT_A8, flags: SFF_OWNED, opac: 255, blend: 0 }
124    }
125
126    /// new size, same format; the contents are NOT kept  [surf_resize]
127    pub fn resize(&mut self, w: i32, h: i32) {
128        *self = Surface::new(w, h, self.fmt);
129    }
130
131    /// bytes per pixel
132    pub fn bpp(&self) -> usize {
133        if self.fmt == FMT_A8 { 1 } else { 4 }
134    }
135}
136
137// what begin saves of the previous target
138#[derive(Clone, Copy)]
139struct Saved {
140    w: i32,
141    h: i32,
142    clip: [i32; 4],
143    csp: usize,
144    ox: i32,
145    oy: i32,
146    opac: u32,
147    row0: i32,
148}
149
150/// the draw context of a target: clip (x0, y0, x1, y1 exclusive, in target pixels), origin,
151/// colour (straight and premultiplied), opacity
152pub struct Dc {
153    pub clip: [i32; 4],
154    clip_stack: Vec<[i32; 4]>,
155    targets: Vec<Saved>,
156    pub ox: i32,
157    pub oy: i32,
158    pub w: i32,
159    pub h: i32,
160    pub color: u32,
161    pub opac: u32,
162    pub pcol: [u32; 4], // premultiplied B, G, R, A
163    pub pcol32: u32,
164    pub ia: u32, // 255 - premultiplied alpha
165    /// the target row in the surface's first row (begin_rows: a band of a taller target);
166    /// 0 otherwise.  The fills and blits here, the rasterizer (paths, shapes, panels) and the
167    /// text engine's glyph blits honour it; other row writers (tmask, gradient, flood) expect 0.
168    pub row0: i32,
169}
170
171impl Dc {
172    /// a context for a w x h target: clip = the whole target, colour opaque black  [gfx_init]
173    pub fn new(w: i32, h: i32) -> Dc {
174        let mut dc = Dc {
175            clip: [0, 0, w, h],
176            clip_stack: Vec::new(),
177            targets: Vec::new(),
178            ox: 0,
179            oy: 0,
180            w,
181            h,
182            color: 0,
183            opac: 255,
184            pcol: [0; 4],
185            pcol32: 0,
186            ia: 255,
187            row0: 0,
188        };
189        dc.set_color(0xFF00_0000);
190        dc
191    }
192
193    /// draw into another target: clip, origin and opacity of the current one are saved; the
194    /// clip becomes the whole new target, the origin 0  [gfx_begin]
195    pub fn begin(&mut self, sf: &Surface) {
196        self.begin_rows(sf, 0, sf.h);
197    }
198
199    /// begin for a band: sf holds rows row0 .. row0 + sf.h of a target h rows high.  Drawing
200    /// keeps the target's coordinates (clip, origin, paths, glyph positions), so a band gets
201    /// the bytes the whole target would get there; the clip starts as the band's rows.
202    pub fn begin_rows(&mut self, sf: &Surface, row0: i32, h: i32) {
203        self.targets.push(Saved {
204            w: self.w,
205            h: self.h,
206            clip: self.clip,
207            csp: self.clip_stack.len(),
208            ox: self.ox,
209            oy: self.oy,
210            opac: self.opac,
211            row0: self.row0,
212        });
213        self.w = sf.w;
214        self.h = h;
215        self.ox = 0;
216        self.oy = 0;
217        self.row0 = row0;
218        let y1 = row0.saturating_add(sf.h).min(h);
219        self.clip = [0, row0.clamp(0, y1.max(0)), sf.w, y1.max(0)];
220    }
221
222    /// back to the previous target with its clip, origin and opacity  [gfx_end]
223    pub fn end(&mut self) {
224        let Some(s) = self.targets.pop() else { return };
225        self.w = s.w;
226        self.h = s.h;
227        self.clip = s.clip;
228        self.clip_stack.truncate(s.csp);
229        self.ox = s.ox;
230        self.oy = s.oy;
231        self.row0 = s.row0;
232        self.clamp_clip();
233        self.set_opacity(s.opac);
234    }
235
236    /// store the colour and its premultiplied forms  [gfx_set_color]
237    pub fn set_color(&mut self, argb: u32) {
238        self.color = argb;
239        let a = div255((argb >> 24) * self.opac);
240        self.ia = 255 - a;
241        self.pcol[3] = a;
242        let mut packed = a << 24;
243        for ch in 0..3 {
244            let c = (argb >> (ch * 8)) & 255;
245            let p = div255(c * a);
246            self.pcol[ch] = p;
247            packed |= p << (ch * 8);
248        }
249        self.pcol32 = packed;
250    }
251
252    pub fn get_color(&self) -> u32 {
253        self.color
254    }
255
256    /// global opacity 0..255 (multiplies every alpha)
257    pub fn set_opacity(&mut self, op: u32) {
258        self.opac = op & 255;
259        let c = self.color;
260        self.set_color(c);
261    }
262
263    pub fn origin(&mut self, x: i32, y: i32) {
264        self.ox = x;
265        self.oy = y;
266    }
267
268    pub fn translate(&mut self, dx: i32, dy: i32) {
269        self.ox = self.ox.wrapping_add(dx);
270        self.oy = self.oy.wrapping_add(dy);
271    }
272
273    /// save the clip and intersect it with the rectangle (origin-relative)
274    pub fn clip_push(&mut self, x: i32, y: i32, w: i32, h: i32) {
275        self.clip_stack.push(self.clip);
276        let x = x.wrapping_add(self.ox);
277        let y = y.wrapping_add(self.oy);
278        let x0 = x.max(self.clip[0]);
279        let y0 = y.max(self.clip[1]);
280        let x1 = x.wrapping_add(w).min(self.clip[2]).max(x0); // an empty clip stays empty
281        let y1 = y.wrapping_add(h).min(self.clip[3]).max(y0);
282        self.clip = [x0, y0, x1, y1];
283    }
284
285    pub fn clip_pop(&mut self) {
286        if let Some(c) = self.clip_stack.pop() {
287            self.clip = c;
288            self.clamp_clip();
289        }
290    }
291
292    /// 0 <= x0 <= x1 <= W, 0 <= y0 <= y1 <= H (a restored clip may belong to a larger target)
293    fn clamp_clip(&mut self) {
294        let x0 = self.clip[0].max(0).min(self.w);
295        let x1 = self.clip[2].min(self.w).max(x0);
296        let y0 = self.clip[1].max(0).min(self.h);
297        let y1 = self.clip[3].min(self.h).max(y0);
298        self.clip = [x0, y0, x1, y1];
299    }
300
301    /// origin-relative rectangle -> clipped target rectangle (x0, y0, x1, y1), or None
302    pub fn clip_rect(&self, x: i32, y: i32, w: i32, h: i32) -> Option<[i32; 4]> {
303        let x = x.wrapping_add(self.ox);
304        let y = y.wrapping_add(self.oy);
305        let x0 = x.max(self.clip[0]);
306        let y0 = y.max(self.clip[1]);
307        let x1 = x.wrapping_add(w).min(self.clip[2]);
308        let y1 = y.wrapping_add(h).min(self.clip[3]);
309        if x1 <= x0 || y1 <= y0 { None } else { Some([x0, y0, x1, y1]) }
310    }
311
312    /// blend the colour over a rectangle (A8 targets: store the alpha)  [gfx_fill]
313    pub fn fill(&self, sf: &mut Surface, x: i32, y: i32, w: i32, h: i32) {
314        let Some([x0, y0, x1, y1]) = self.clip_rect(x, y, w, h) else { return };
315        let stride = sf.stride as usize;
316        if sf.fmt == FMT_A8 {
317            let a = (self.pcol32 >> 24) as u8;
318            for yy in y0..y1 {
319                let o = (yy - self.row0) as usize * stride;
320                sf.px[o + x0 as usize..o + x1 as usize].fill(a);
321            }
322            return;
323        }
324        if self.ia != 0 && self.pcol32 == 0 {
325            return; // fully transparent
326        }
327        for yy in y0..y1 {
328            let o = (yy - self.row0) as usize * stride + x0 as usize * 4;
329            let row = &mut sf.px[o..o + (x1 - x0) as usize * 4];
330            if self.ia == 0 {
331                blend::fill_solid(row, self.pcol32);
332            } else {
333                blend::fill_over(row, &self.pcol, self.ia);
334            }
335        }
336    }
337
338    /// replace every pixel inside the clip by argb (premultiplied, no blending)  [gfx_clear]
339    pub fn clear(&self, sf: &mut Surface, argb: u32) {
340        let [x0, y0, x1, y1] = self.clip;
341        if x1 <= x0 || y1 <= y0 {
342            return;
343        }
344        let a = argb >> 24;
345        let mut c = argb;
346        if a != 255 {
347            c = argb & 0xFF00_0000;
348            for ch in 0..3 {
349                c |= div255(((argb >> (ch * 8)) & 255) * a) << (ch * 8);
350            }
351        }
352        let stride = sf.stride as usize;
353        for yy in y0..y1 {
354            let o = (yy - self.row0) as usize * stride;
355            if sf.fmt == FMT_A8 {
356                sf.px[o + x0 as usize..o + x1 as usize].fill((c >> 24) as u8);
357            } else {
358                blend::fill_solid(&mut sf.px[o + x0 as usize * 4..o + x1 as usize * 4], c);
359            }
360        }
361    }
362
363    pub fn hline(&self, sf: &mut Surface, x: i32, y: i32, w: i32) {
364        self.fill(sf, x, y, w, 1);
365    }
366
367    pub fn vline(&self, sf: &mut Surface, x: i32, y: i32, h: i32) {
368        self.fill(sf, x, y, 1, h);
369    }
370
371    /// 1-pixel outline inside the rectangle  [gfx_frame]
372    pub fn frame(&self, sf: &mut Surface, x: i32, y: i32, w: i32, h: i32) {
373        if w <= 0 || h <= 0 {
374            return;
375        }
376        self.hline(sf, x, y, w);
377        if h == 1 {
378            return;
379        }
380        self.hline(sf, x, y + h - 1, w);
381        if h == 2 {
382            return;
383        }
384        self.vline(sf, x, y + 1, h - 2);
385        if w == 1 {
386            return;
387        }
388        self.vline(sf, x + w - 1, y + 1, h - 2);
389    }
390
391    /// draw a surface at x, y: premultiplied over (x opacity), XRGB sources are copied  [gfx_blit]
392    pub fn blit(&self, sf: &mut Surface, src: &Surface, x: i32, y: i32) {
393        if sf.fmt == FMT_A8 || src.fmt == FMT_A8 {
394            return;
395        }
396        let Some([x0, y0, x1, y1]) = self.clip_rect(x, y, src.w, src.h) else { return };
397        let sx = (x0 - x.wrapping_add(self.ox)) as usize;
398        let sy = (y0 - y.wrapping_add(self.oy)) as usize;
399        let n = (x1 - x0) as usize;
400        for r in 0..(y1 - y0) as usize {
401            let d = ((y0 - self.row0) as usize + r) * sf.stride as usize + x0 as usize * 4;
402            let s = (sy + r) * src.stride as usize + sx * 4;
403            let dst = &mut sf.px[d..d + n * 4];
404            let srow = &src.px[s..s + n * 4];
405            if src.fmt == FMT_XRGB {
406                dst.copy_from_slice(srow);
407            } else {
408                blend::blit_over(dst, srow, self.opac);
409            }
410        }
411    }
412
413    /// nearest-neighbour blit scaled to w x h (over)  [gfx_blit_scaled]
414    pub fn blit_scaled(&self, sf: &mut Surface, src: &Surface, x: i32, y: i32, w: i32, h: i32) {
415        if w == src.w && h == src.h {
416            self.blit(sf, src, x, y);
417            return;
418        }
419        if w <= 0 || h <= 0 || src.w <= 0 || src.h <= 0 || sf.fmt == FMT_A8 || src.fmt == FMT_A8 {
420            return;
421        }
422        let xa = x.wrapping_add(self.ox);
423        let ya = y.wrapping_add(self.oy);
424        let Some([x0, y0, x1, y1]) = self.clip_rect(x, y, w, h) else { return };
425        let vw = (x1 - x0) as usize;
426        let xstep = ((src.w as u64) << 16) / w as u64;
427        let ystep = ((src.h as u64) << 16) / h as u64;
428        let mut row = vec![0u8; vw * 4];
429        for r in 0..(y1 - y0) {
430            let sy = (((y0 + r - ya) as u32 as u64 * ystep + (ystep >> 1)) >> 16) as usize;
431            let srow = sy * src.stride as usize;
432            let mut fx = (x0 - xa) as u32 as u64 * xstep + (xstep >> 1);
433            for i in 0..vw {
434                let s = srow + (fx >> 16) as usize * 4;
435                row[i * 4..i * 4 + 4].copy_from_slice(&src.px[s..s + 4]);
436                fx += xstep;
437            }
438            let d = (y0 + r - self.row0) as usize * sf.stride as usize + x0 as usize * 4;
439            let dst = &mut sf.px[d..d + vw * 4];
440            if src.fmt == FMT_XRGB {
441                dst.copy_from_slice(&row);
442            } else {
443                blend::blit_over(dst, &row, self.opac);
444            }
445        }
446    }
447}