]> git.nihav.org Git - nihav.git/commitdiff
qdraw: implement some more painting opcodes
authorKostya Shishkov <kostya.shishkov@gmail.com>
Mon, 2 Nov 2026 14:47:34 +0000 (15:47 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Mon, 2 Nov 2026 14:47:34 +0000 (15:47 +0100)
nihav-qt/src/codecs/qdraw.rs

index bee8d43f2b3b13dce1e20a9bd42a1639cef7f547..394143c5dcef853d3ab6790a7538c14181f88f04 100644 (file)
@@ -35,6 +35,145 @@ impl<T: ?Sized + ByteIO> ReadQDTypes for T {
     }
 }
 
+#[derive(Clone,Copy,Default)]
+struct Rect {
+    sx:     isize,
+    sy:     isize,
+    dx:     isize,
+    dy:     isize,
+}
+
+impl Rect {
+    fn read(br: &mut dyn ByteIO) -> DecoderResult<Self> {
+        let sy              = isize::from(br.read_u16be()? as i16);
+        let sx              = isize::from(br.read_u16be()? as i16);
+        let dy              = isize::from(br.read_u16be()? as i16);
+        let dx              = isize::from(br.read_u16be()? as i16);
+        validate!(sy < dy && sx < dx);
+        Ok(Self { sx, sy, dx, dy })
+    }
+    fn paint(&self, dst: &mut [u8], stride: usize, width: usize, height: usize, clr: &[u8; 3]) {
+        let sx = self.sx.max(0) as usize;
+        let sy = self.sy.max(0) as usize;
+        let dx = self.dx.max(0) as usize;
+        let dy = self.dy.max(0) as usize;
+        for dline in dst.chunks_exact_mut(stride).take(dy.min(height)).skip(sy) {
+            for pix in dline.chunks_exact_mut(3).take(dx.min(width)).skip(sx) {
+                pix.copy_from_slice(clr);
+            }
+        }
+    }
+    fn paint_ellipse(&self, dst: &mut [u8], stride: usize, width: usize, height: usize, clr: &[u8; 3]) {
+        let (cx, cy) = ((self.sx + self.dx) as f32 * 0.5, (self.sy + self.dy) as f32 * 0.5);
+        let ell_x = 1.0 / ((self.dx as f32 - cx) * (self.dx as f32 - cx));
+        let ell_y = 1.0 / ((self.dy as f32 - cy) * (self.dy as f32 - cy));
+
+        let sx = self.sx.max(0) as usize;
+        let sy = self.sy.max(0) as usize;
+        let dx = self.dx.max(0) as usize;
+        let dy = self.dy.max(0) as usize;
+        for (y, dline) in dst.chunks_exact_mut(stride).enumerate().take(dy.min(height)).skip(sy) {
+            for (x, pix) in dline.chunks_exact_mut(3).enumerate().take(dx.min(width)).skip(sx) {
+                let dx = (x as f32 - cx).abs();
+                let dy = (y as f32 - cy).abs();
+                if dx * dx * ell_x + dy * dy * ell_y <= 1.0 {
+                    pix.copy_from_slice(clr);
+                }
+            }
+        }
+    }
+}
+
+#[derive(Default)]
+struct Painter {
+    width:      usize,
+    height:     usize,
+    stride:     usize,
+    pos_x:      isize,
+    pos_y:      isize,
+    fg_clr:     [u8; 3],
+    bg_clr:     [u8; 3],
+    fill_clr:   [u8; 3],
+    last_rect:  Option<Rect>,
+    last_oval:  Option<Rect>,
+}
+
+impl Painter {
+    fn line_to(&mut self, dst: &mut [u8], mut dx: isize, mut dy: isize) {
+        let mut sx = self.pos_x;
+        let mut sy = self.pos_y;
+        if sx > dx {
+            std::mem::swap(&mut sx, &mut dx);
+            std::mem::swap(&mut sy, &mut dy);
+        }
+        let bound_x = 0..(self.width as isize);
+        let bound_y = 0..(self.height as isize);
+        let xdiff = dx - sx;
+        let mut ydiff = dy - sy;
+        let mut yinc = 1;
+        if ydiff < 0 {
+            ydiff = -ydiff;
+            yinc = -yinc;
+        }
+
+        if xdiff >= ydiff {
+            let mut delta = 2 * ydiff - xdiff;
+            let mut y = sy;
+            for x in sx..dy {
+                if bound_x.contains(&x) && bound_y.contains(&y) {
+                    dst[(x as usize) * 3 + (y as usize) * self.stride..][..3].copy_from_slice(&self.fg_clr);
+                }
+                if delta > 0 {
+                    y += yinc;
+                    delta += 2 * (ydiff - xdiff);
+                } else {
+                    delta += 2 * ydiff;
+                }
+            }
+        } else {
+            let mut delta = 2 * xdiff - ydiff;
+            let mut x = sx;
+            for y in sy..dy {
+                if bound_x.contains(&x) && bound_y.contains(&y) {
+                    dst[(x as usize) * 3 + (y as usize) * self.stride..][..3].copy_from_slice(&self.fg_clr);
+                }
+                if delta > 0 {
+                    x += 1;
+                    delta += 2 * (xdiff - ydiff);
+                } else {
+                    delta += 2 * xdiff;
+                }
+            }
+        }
+    }
+    fn paint_rect(&mut self, dst: &mut [u8], rect: Rect) {
+        rect.paint(dst, self.stride, self.width, self.height, &self.fg_clr);
+        self.last_rect = Some(rect);
+    }
+    fn paint_same_rect(&mut self, dst: &mut [u8]) {
+        if let Some(ref rect) = self.last_rect {
+            rect.paint(dst, self.stride, self.width, self.height, &self.fg_clr);
+        }
+    }
+    fn erase_rect(&mut self, dst: &mut [u8], rect: Rect) {
+        rect.paint(dst, self.stride, self.width, self.height, &self.bg_clr);
+        self.last_rect = Some(rect);
+    }
+    fn paint_oval(&mut self, dst: &mut [u8], oval: Rect) {
+        oval.paint_ellipse(dst, self.stride, self.width, self.height, &self.fg_clr);
+        self.last_oval = Some(oval);
+    }
+    fn paint_same_oval(&mut self, dst: &mut [u8]) {
+        if let Some(ref oval) = self.last_oval {
+            oval.paint_ellipse(dst, self.stride, self.width, self.height, &self.fg_clr);
+        }
+    }
+    fn erase_oval(&mut self, dst: &mut [u8], oval: Rect) {
+        oval.paint_ellipse(dst, self.stride, self.width, self.height, &self.fg_clr);
+        self.last_oval = Some(oval);
+    }
+}
+
 #[derive(Default)]
 struct QDrawDecoder {
     info:       NACodecInfoRef,
@@ -349,9 +488,8 @@ impl NADecoder for QDrawDecoder {
         }
         let mut frm = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap();
 
-        let mut fg_clr = [0; 3];
-        let mut bg_clr = [0; 3];
-        let mut fill_clr = [0; 3];
+        let mut painter = Painter { width: self.width, height: self.height, stride: frm.stride[0], ..Default::default() };
+        let mut dst = &mut frm.data[frm.offset[0]..];
         while br.left() >= 2 {
             let op                      = br.read_u16be()?;
             match op {
@@ -404,65 +542,82 @@ impl NADecoder for QDrawDecoder {
                     return Err(DecoderError::InvalidData);
                 },
                 0x001A => { // fore color
-                    fg_clr              = br.read_colour()?;
+                    painter.fg_clr      = br.read_colour()?;
                 },
                 0x001B => { // back color
-                    bg_clr              = br.read_colour()?;
+                    painter.bg_clr      = br.read_colour()?;
                 },
                 0x001E => {}, // def hilite
                 0x001F => { // fill color
-                    fill_clr            = br.read_colour()?;
+                    painter.fill_clr    = br.read_colour()?;
                 },
                 0x0020 => { // line
-                    let sx              = br.read_u16be()?;
-                    let sy              = br.read_u16be()?;
-                    let dx              = br.read_u16be()?;
-                    let dy              = br.read_u16be()?;
+                    painter.pos_y       = isize::from(br.read_u16be()? as i16);
+                    painter.pos_x       = isize::from(br.read_u16be()? as i16);
+                    let dy              = isize::from(br.read_u16be()? as i16);
+                    let dx              = isize::from(br.read_u16be()? as i16);
+                    painter.line_to(dst, dx, dy);
                 },
                 0x0021 => { // line from
-                    let dx              = br.read_u16be()?;
-                    let dy              = br.read_u16be()?;
+                    let dy              = isize::from(br.read_u16be()? as i16);
+                    let dx              = isize::from(br.read_u16be()? as i16);
+                    painter.line_to(dst, dx, dy);
                 },
                 0x0022 => { // short line
-                    let xpos            = br.read_u16be()?;
-                    let ypos            = br.read_u16be()?;
-                    let dx              = br.read_byte()? as i8;
-                    let dy              = br.read_byte()? as i8;
+                    painter.pos_y       = isize::from(br.read_u16be()? as i16);
+                    painter.pos_x       = isize::from(br.read_u16be()? as i16);
+                    let dy              = isize::from(br.read_byte()? as i8);
+                    let dx              = isize::from(br.read_byte()? as i8);
+                    painter.line_to(dst, dx, dy);
                 },
                 0x0023 => { // short line from
-                    let dx              = br.read_byte()? as i8;
-                    let dy              = br.read_byte()? as i8;
+                    let dy              = isize::from(br.read_byte()? as i8);
+                    let dx              = isize::from(br.read_byte()? as i8);
+                    painter.line_to(dst, dx, dy);
                 },
                 0x0028 => { // long text
-                    let _xpos           = br.read_u16be()?;
                     let _ypos           = br.read_u16be()?;
+                    let _xpos           = br.read_u16be()?;
                     let count           = usize::from(br.read_byte()?);
                                           br.read_skip(count)?;
                 },
                 0x0031 => { // paint rect
-                    let sy              = usize::from(br.read_u16be()?);
-                    let sx              = usize::from(br.read_u16be()?);
-                    let dy              = usize::from(br.read_u16be()?);
-                    let dx              = usize::from(br.read_u16be()?);
-                    validate!(sy < dy && sx < dx);
-                    for dline in frm.data[frm.offset[0]..].chunks_exact_mut(frm.stride[0])
-                            .take(dy.min(self.height)).skip(sy) {
-                        for pix in dline.chunks_exact_mut(3).take(dx.min(self.width)).skip(sx) {
-                            pix.copy_from_slice(&fg_clr);
-                        }
-                    }
+                    let rect            = Rect::read(&mut br)?;
+                    painter.paint_rect(dst, rect);
+                },
+                0x0032 => { // erase rect
+                    let rect            = Rect::read(&mut br)?;
+                    painter.erase_rect(dst, rect);
+                },
+                0x0039 => { // paint same rect
+                    painter.paint_same_rect(dst);
+                },
+                0x0051 => { // paint oval
+                    let oval            = Rect::read(&mut br)?;
+                    painter.paint_oval(dst, oval);
+                },
+                0x0052 => { // erase oval
+                    let oval            = Rect::read(&mut br)?;
+                    painter.erase_oval(dst, oval);
+                },
+                0x0059 => { // paint same oval
+                    painter.paint_same_oval(dst);
                 },
                 0x0098 => { // PackBits rect
                     decode_bits(&mut br, &mut frm, self.width, self.height, false, false)?;
+                    dst = &mut frm.data[frm.offset[0]..];
                 },
                 0x0099 => { // PackBits region
                     decode_bits(&mut br, &mut frm, self.width, self.height, true,  false)?;
+                    dst = &mut frm.data[frm.offset[0]..];
                 },
                 0x009A => { // DirectBits rect
                     decode_bits(&mut br, &mut frm, self.width, self.height, false, true)?;
+                    dst = &mut frm.data[frm.offset[0]..];
                 },
                 0x009B => { // DirectBits region
                     decode_bits(&mut br, &mut frm, self.width, self.height, true,  true)?;
+                    dst = &mut frm.data[frm.offset[0]..];
                 },
                 0x00A0 => { // short comment
                                           br.read_u16be()?;