Browse Source

Basic operations implemented with new dispatch system.

Kestrel 11 tháng trước cách đây
mục cha
commit
c1ef8254d5
13 tập tin đã thay đổi với 669 bổ sung197 xóa
  1. 41 3
      src/bitmap.rs
  2. 29 1
      src/colour.rs
  3. 0 162
      src/dispatch.rs
  4. 3 13
      src/lib.rs
  5. 71 0
      src/math.rs
  6. 76 5
      src/op.rs
  7. 59 0
      src/op/composite.rs
  8. 54 0
      src/op/copy_from.rs
  9. 205 0
      src/op/dispatch.rs
  10. 21 11
      src/op/fill.rs
  11. 31 0
      src/op/fill_region.rs
  12. 77 0
      src/op/fill_region_masked.rs
  13. 2 2
      tests/common/mod.rs

+ 41 - 3
src/bitmap.rs

@@ -1,16 +1,42 @@
-use crate::{formats::PixelFormatSpec, math};
+use crate::{
+    formats::PixelFormatSpec,
+    math::{self, PixelBox, PixelSize},
+};
 
 pub trait BitmapData {
     fn data(&self) -> &[u8];
     fn width(&self) -> usize;
     fn height(&self) -> usize;
     fn stride(&self) -> usize;
+
+    fn size(&self) -> PixelSize {
+        PixelSize::new(self.width() as i32, self.height() as i32)
+    }
 }
 
 pub trait BitmapDataMut: BitmapData {
     fn data_mut(&mut self) -> &mut [u8];
 }
 
+pub trait MakeMutView {
+    fn make_view_mut<Format: PixelFormatSpec>(&mut self, area: PixelBox) -> BitmapMut<Format>;
+}
+
+// impl<T: BitmapDataMut> MakeMutView for T { }
+impl<'l> MakeMutView for &'l mut dyn BitmapDataMut {
+    fn make_view_mut<Format: PixelFormatSpec>(&mut self, area: PixelBox) -> BitmapMut<Format> {
+        let stride = self.stride();
+        let data_offset =
+            (area.min.y as usize * stride) + area.min.x as usize * Format::PIXEL_WIDTH;
+        BitmapMut::new_with_stride(
+            &mut self.data_mut()[data_offset..],
+            area.width() as usize,
+            area.height() as usize,
+            stride,
+        )
+    }
+}
+
 #[derive(PartialEq, Clone)]
 pub struct OwnedBitmapData {
     pub(crate) data: Vec<u8>,
@@ -101,6 +127,17 @@ pub trait BitmapAccess<Format: PixelFormatSpec> {
     }
 
     fn row_stride(&self) -> usize;
+
+    fn make_view(&self, area: PixelBox) -> BitmapRef<Format> {
+        let data_offset = (area.min.y as usize * self.data().stride())
+            + area.min.x as usize * Format::PIXEL_WIDTH;
+        BitmapRef::new_with_stride(
+            &self.data().data()[data_offset..],
+            area.width() as usize,
+            area.height() as usize,
+            self.data().stride(),
+        )
+    }
 }
 
 pub trait BitmapMutAccess<Format: PixelFormatSpec>: BitmapAccess<Format> {
@@ -171,6 +208,9 @@ impl<'l, Format: PixelFormatSpec, T: BitmapAccess<Format>> BitmapAccess<Format>
     fn row_stride(&self) -> usize {
         (*self).row_stride()
     }
+    fn make_view(&self, area: PixelBox) -> BitmapRef<Format> {
+        (*self).make_view(area)
+    }
 }
 
 impl<Format: PixelFormatSpec> BitmapMutAccess<Format> for Bitmap<Format> {
@@ -189,7 +229,6 @@ impl<Format: PixelFormatSpec> std::fmt::Debug for Bitmap<Format> {
     }
 }
 
-
 /// Borrowed bitmap with static pixel format information.
 #[derive(Clone)]
 pub struct BitmapRef<'l, Format: PixelFormatSpec> {
@@ -272,7 +311,6 @@ impl<'l, Format: PixelFormatSpec> BitmapMut<'l, Format> {
             _ghost: Default::default(),
         }
     }
-    
 }
 
 impl<'l, Format: PixelFormatSpec> BitmapAccess<Format> for BitmapMut<'l, Format> {

+ 29 - 1
src/colour.rs

@@ -1,4 +1,4 @@
-use crate::formats::PixelFormatSpec;
+use crate::formats::{PackedLocation, PixelFormatSpec};
 
 /// Helper type alias for Americans.
 pub type Color = Colour;
@@ -56,6 +56,34 @@ impl Colour {
 
 /// Generic conversion functions
 impl Colour {
+    #[inline]
+    fn convert_pack(
+        dst: &mut [u8],
+        src: &[u8],
+        src_pack: PackedLocation,
+        dst_pack: PackedLocation,
+    ) {
+        match (src_pack.index(), dst_pack.index()) {
+            (Some(srcindex), Some(dstindex)) => {
+                dst[dstindex as usize] = src[srcindex as usize];
+            }
+            (None, Some(dstindex)) => {
+                dst[dstindex as usize] = 255;
+            }
+            (_, None) => (),
+        }
+    }
+
+    pub(crate) fn convert_byte_form<SrcFormat: PixelFormatSpec, DstFormat: PixelFormatSpec>(
+        dst: &mut [u8],
+        src: &[u8],
+    ) {
+        Self::convert_pack(dst, src, SrcFormat::RED_PACK, DstFormat::RED_PACK);
+        Self::convert_pack(dst, src, SrcFormat::GREEN_PACK, DstFormat::GREEN_PACK);
+        Self::convert_pack(dst, src, SrcFormat::BLUE_PACK, DstFormat::BLUE_PACK);
+        Self::convert_pack(dst, src, SrcFormat::ALPHA_PACK, DstFormat::ALPHA_PACK);
+    }
+
     pub(crate) fn write_as_bytes<Format: PixelFormatSpec>(&self, to: &mut [u8]) {
         if let Some(offset) = Format::RED_PACK.index() {
             to[offset as usize] = self.r();

+ 0 - 162
src/dispatch.rs

@@ -1,162 +0,0 @@
-use crate::formats::{self, PixelFormatSpec, NUM_PIXEL_FORMATS};
-use crate::bitmap::{BitmapData, BitmapDataMut};
-use crate::op::{self, UnaryOpSpec};
-
-pub type UnaryFunc<Params> = fn(&mut dyn BitmapDataMut, &Params);
-pub type BinaryFunc<Params> = fn(&mut dyn BitmapDataMut, &dyn BitmapData, &Params);
-
-pub trait GenericUnary<Params> {
-    fn perform<Format: PixelFormatSpec>(write_data: &mut dyn BitmapDataMut, params: &Params);
-
-    fn make_dispatch() -> UnaryDispatch<Params> {
-        UnaryDispatch {
-            op_array: [
-                Self::perform::<formats::A8>,
-                Self::perform::<formats::Abgr32>,
-                Self::perform::<formats::Bgr24>,
-                Self::perform::<formats::Bgr32>,
-                Self::perform::<formats::Rgb24>,
-                Self::perform::<formats::Rgb32>,
-                Self::perform::<formats::Rgba32>,
-            ],
-            changed: Default::default(),
-        }
-    }
-}
-
-pub trait GenericBinary<Params> {
-    fn perform<Format: PixelFormatSpec, ReadFormat: PixelFormatSpec>(write_data: &mut dyn BitmapDataMut, read_data: &dyn BitmapData, params: &Params);
-
-    fn make_dispatch() -> BinaryDispatch<Params> {
-        BinaryDispatch {
-            op_array: [
-                [
-                    Self::perform::<formats::A8, formats::A8>,
-                    Self::perform::<formats::A8, formats::Abgr32>,
-                    Self::perform::<formats::A8, formats::Bgr24>,
-                    Self::perform::<formats::A8, formats::Bgr32>,
-                    Self::perform::<formats::A8, formats::Rgb24>,
-                    Self::perform::<formats::A8, formats::Rgb32>,
-                    Self::perform::<formats::A8, formats::Rgba32>,
-                ],
-                [
-                    Self::perform::<formats::Abgr32, formats::A8>,
-                    Self::perform::<formats::Abgr32, formats::Abgr32>,
-                    Self::perform::<formats::Abgr32, formats::Bgr24>,
-                    Self::perform::<formats::Abgr32, formats::Bgr32>,
-                    Self::perform::<formats::Abgr32, formats::Rgb24>,
-                    Self::perform::<formats::Abgr32, formats::Rgb32>,
-                    Self::perform::<formats::Abgr32, formats::Rgba32>,
-                ],
-                [
-                    Self::perform::<formats::Bgr24, formats::A8>,
-                    Self::perform::<formats::Bgr24, formats::Abgr32>,
-                    Self::perform::<formats::Bgr24, formats::Bgr24>,
-                    Self::perform::<formats::Bgr24, formats::Bgr32>,
-                    Self::perform::<formats::Bgr24, formats::Rgb24>,
-                    Self::perform::<formats::Bgr24, formats::Rgb32>,
-                    Self::perform::<formats::Bgr24, formats::Rgba32>,
-                ],
-                [
-                    Self::perform::<formats::Bgr32, formats::A8>,
-                    Self::perform::<formats::Bgr32, formats::Abgr32>,
-                    Self::perform::<formats::Bgr32, formats::Bgr24>,
-                    Self::perform::<formats::Bgr32, formats::Bgr32>,
-                    Self::perform::<formats::Bgr32, formats::Rgb24>,
-                    Self::perform::<formats::Bgr32, formats::Rgb32>,
-                    Self::perform::<formats::Bgr32, formats::Rgba32>,
-                ],
-                [
-                    Self::perform::<formats::Rgb24, formats::A8>,
-                    Self::perform::<formats::Rgb24, formats::Abgr32>,
-                    Self::perform::<formats::Rgb24, formats::Bgr24>,
-                    Self::perform::<formats::Rgb24, formats::Bgr32>,
-                    Self::perform::<formats::Rgb24, formats::Rgb24>,
-                    Self::perform::<formats::Rgb24, formats::Rgb32>,
-                    Self::perform::<formats::Rgb24, formats::Rgba32>,
-                ],
-                [
-                    Self::perform::<formats::Rgb32, formats::A8>,
-                    Self::perform::<formats::Rgb32, formats::Abgr32>,
-                    Self::perform::<formats::Rgb32, formats::Bgr24>,
-                    Self::perform::<formats::Rgb32, formats::Bgr32>,
-                    Self::perform::<formats::Rgb32, formats::Rgb24>,
-                    Self::perform::<formats::Rgb32, formats::Rgb32>,
-                    Self::perform::<formats::Rgb32, formats::Rgba32>,
-                ],
-                [
-                    Self::perform::<formats::Rgba32, formats::A8>,
-                    Self::perform::<formats::Rgba32, formats::Abgr32>,
-                    Self::perform::<formats::Rgba32, formats::Bgr24>,
-                    Self::perform::<formats::Rgba32, formats::Bgr32>,
-                    Self::perform::<formats::Rgba32, formats::Rgb24>,
-                    Self::perform::<formats::Rgba32, formats::Rgb32>,
-                    Self::perform::<formats::Rgba32, formats::Rgba32>,
-                ],
-            ],
-            changed: Default::default(),
-        }
-    }
-}
-
-pub struct UnaryDispatch<Params> {
-    op_array: [UnaryFunc<Params>; NUM_PIXEL_FORMATS],
-    changed: [bool; NUM_PIXEL_FORMATS],
-}
-
-impl<Params> UnaryDispatch<Params> {
-    pub fn with<Format: PixelFormatSpec>(mut self, specific_imp: UnaryFunc<Params>) -> Self {
-        self.op_array[Format::FORMAT_ENUM as usize] = specific_imp;
-        self.changed[Format::FORMAT_ENUM as usize] = true;
-        self
-    }
-    pub fn with_avx<Format: PixelFormatSpec>(mut self, avx_imp: UnaryFunc<Params>) -> Self {
-        if is_x86_feature_detected!("avx") {
-            self.op_array[Format::FORMAT_ENUM as usize] = avx_imp;
-            self.changed[Format::FORMAT_ENUM as usize] = true;
-            self
-        } else {
-            self
-        }
-    }
-
-    pub fn select<Format: PixelFormatSpec>(&self) -> &UnaryFunc<Params> {
-        &self.op_array[Format::FORMAT_ENUM as usize]
-    }
-}
-
-pub struct BinaryDispatch<Params> {
-    op_array: [[BinaryFunc<Params>; NUM_PIXEL_FORMATS]; NUM_PIXEL_FORMATS],
-    changed: [[bool; NUM_PIXEL_FORMATS]; NUM_PIXEL_FORMATS],
-}
-
-impl<Params> BinaryDispatch<Params> {
-    pub fn with_avx<WriteFormat: PixelFormatSpec, ReadFormat: PixelFormatSpec>(mut self, avx_imp: BinaryFunc<Params>) -> Self {
-        if is_x86_feature_detected!("avx") {
-            self.op_array[WriteFormat::FORMAT_ENUM as usize][ReadFormat::FORMAT_ENUM as usize] = avx_imp;
-            self
-        } else {
-            self
-        }
-    }
-
-    pub fn select<WriteFormat: PixelFormatSpec, ReadFormat: PixelFormatSpec>(&self) -> &BinaryFunc<Params> {
-        &self.op_array[WriteFormat::FORMAT_ENUM as usize][ReadFormat::FORMAT_ENUM as usize]
-    }
-}
-
-pub struct KahloDispatch {
-    pub fill: UnaryDispatch<<op::FillOp as UnaryOpSpec>::Params>,
-}
-
-impl Default for KahloDispatch {
-    fn default() -> Self {
-        Self {
-            fill: op::FillOp::build(),
-        }
-    }
-}
-
-lazy_static::lazy_static! {
-    static ref DISPATCH: KahloDispatch = KahloDispatch::default();
-}

+ 3 - 13
src/lib.rs

@@ -8,9 +8,7 @@
 //! purposes, and are written for clarity and not speed.
 
 mod bitmap;
-// mod ops;
 mod op;
-mod dispatch;
 
 #[cfg(target_endian = "big")]
 std::compile_error!("kahlo only works on little-endian targets!");
@@ -24,20 +22,12 @@ pub mod formats;
 pub mod prelude {
     // pub use crate::ops::{AlphaPaintable, Readable, RgbaPaintable, Paintable, Writable};
     pub use crate::bitmap::{BitmapAccess, BitmapMutAccess};
+    pub use crate::op::KahloOps;
 }
 
-/// Types for 2D mathematics.
-pub mod math {
-    pub struct PixelSpace;
-    pub type PixelPoint = euclid::Point2D<i32, PixelSpace>;
-    pub type PixelSize = euclid::Size2D<i32, PixelSpace>;
-    pub type PixelRect = euclid::Rect<i32, PixelSpace>;
-    pub type PixelBox = euclid::Box2D<i32, PixelSpace>;
-    pub type PixelVector = euclid::Vector2D<i32, PixelSpace>;
-    pub type PixelSideOffsets = euclid::SideOffsets2D<i32, PixelSpace>;
-}
+pub mod math;
 
-pub use bitmap::{Bitmap, BitmapRef, BitmapMut};
+pub use bitmap::{Bitmap, BitmapMut, BitmapRef};
 
 /// Helper type alias for a Rgba32 bitmap.
 pub type RgbaBitmap = Bitmap<formats::Rgba32>;

+ 71 - 0
src/math.rs

@@ -0,0 +1,71 @@
+//! Types for 2D mathematics.
+
+pub struct PixelSpace;
+pub type PixelPoint = euclid::Point2D<i32, PixelSpace>;
+pub type PixelSize = euclid::Size2D<i32, PixelSpace>;
+pub type PixelRect = euclid::Rect<i32, PixelSpace>;
+pub type PixelBox = euclid::Box2D<i32, PixelSpace>;
+pub type PixelVector = euclid::Vector2D<i32, PixelSpace>;
+pub type PixelSideOffsets = euclid::SideOffsets2D<i32, PixelSpace>;
+
+pub fn clip_to(
+    img1_clip_box: PixelBox,
+    img2_clip_box: PixelBox,
+    img1_victim_box: PixelBox,
+    img2_victim_box: PixelBox,
+) -> Option<(PixelBox, PixelBox)> {
+    if img1_victim_box.size() != img2_victim_box.size() {
+        return None;
+    }
+
+    let adj = PixelSideOffsets {
+        left: (img1_clip_box.min.x - img1_victim_box.min.x)
+            .max(img2_clip_box.min.x - img2_victim_box.min.x)
+            .max(0),
+        top: (img1_clip_box.min.y - img1_victim_box.min.y)
+            .max(img2_clip_box.min.y - img2_victim_box.min.y)
+            .max(0),
+        right: (img1_victim_box.max.x - img1_clip_box.max.x)
+            .max(img2_victim_box.max.x - img2_clip_box.max.x)
+            .max(0),
+        bottom: (img1_victim_box.max.y - img1_clip_box.max.y)
+            .max(img2_victim_box.max.y - img2_clip_box.max.y)
+            .max(0),
+        ..Default::default()
+    };
+
+    Some((
+        img1_victim_box.inner_box(adj),
+        img2_victim_box.inner_box(adj),
+    ))
+}
+
+/*pub fn clip_to(
+    img1_size: PixelSize,
+    pt1: PixelPoint,
+    img2_size: PixelSize,
+    pt2: PixelPoint,
+    sz: PixelSize,
+) -> Option<(PixelBox, PixelBox)> {
+    let left = (-pt1.x).max(-pt2.x).max(0);
+    let top = (-pt1.y).max(-pt2.y).max(0);
+    let right = ((sz.width + pt1.x) - img1_size.width as i32)
+        .max((sz.width + pt2.x) - img2_size.width as i32)
+        .max(0);
+    let bottom = ((sz.height + pt1.y) - img1_size.height as i32)
+        .max((sz.height + pt2.y) - img2_size.height as i32)
+        .max(0);
+
+    let adj = PixelSideOffsets {
+        left,
+        right,
+        top,
+        bottom,
+        ..Default::default()
+    };
+
+    Some((
+        PixelBox::from_origin_and_size(pt1, sz).inner_box(adj),
+        PixelBox::from_origin_and_size(pt2, sz).inner_box(adj),
+    ))
+}*/

+ 76 - 5
src/op.rs

@@ -1,10 +1,81 @@
-mod fill;
-pub use fill::FillOp;
+use crate::{
+    bitmap::{BitmapAccess, BitmapMutAccess},
+    colour::{BlendMode, Colour},
+    formats::{self, PixelFormatSpec},
+    math::{PixelBox, PixelPoint},
+};
+
+mod dispatch;
+use dispatch::{BinaryDispatch, GenericBinary, GenericUnary, UnaryDispatch, DISPATCH};
 
-use crate::dispatch::UnaryDispatch;
+// operation implementations
+mod composite;
+mod copy_from;
+mod fill;
+mod fill_region;
+mod fill_region_masked;
 
 pub trait UnaryOpSpec {
-    type Params;
+    type Params<'l>;
 
-    fn build() -> UnaryDispatch<Self::Params>;
+    fn build<'l>() -> UnaryDispatch<Self::Params<'l>>;
 }
+
+pub trait BinaryOpSpec {
+    type Params<'l>;
+
+    fn build<'l>() -> BinaryDispatch<Self::Params<'l>>;
+}
+
+pub trait KahloOps<Format: PixelFormatSpec>: BitmapMutAccess<Format> {
+    fn composite<SrcFormat: PixelFormatSpec>(
+        &mut self,
+        src: &dyn BitmapAccess<SrcFormat>,
+        src_area: PixelBox,
+        dst: PixelPoint,
+        blend: BlendMode,
+    ) {
+        DISPATCH.composite.select::<Format, SrcFormat>()(
+            self.data_mut(),
+            src.data(),
+            &(src_area, dst, blend),
+        )
+    }
+
+    fn copy_from<SrcFormat: PixelFormatSpec>(
+        &mut self,
+        src: &dyn BitmapAccess<SrcFormat>,
+        src_area: PixelBox,
+        dst: PixelPoint,
+    ) {
+        DISPATCH.copy_from.select::<Format, SrcFormat>()(
+            self.data_mut(),
+            src.data(),
+            &(src_area, dst),
+        );
+    }
+
+    fn fill(&mut self, colour: Colour) {
+        DISPATCH.fill.select::<Format>()(self.data_mut(), &colour);
+    }
+
+    fn fill_region(&mut self, region: PixelBox, colour: Colour) {
+        DISPATCH.fill_region.select::<Format>()(self.data_mut(), &(region, colour));
+    }
+
+    fn fill_region_masked<'l>(
+        &mut self,
+        mask: &'l dyn BitmapAccess<formats::A8>,
+        src_area: PixelBox,
+        dst: PixelPoint,
+        colour: Colour,
+        blend: BlendMode,
+    ) {
+        DISPATCH
+            .use_lifetime()
+            .fill_region_masked
+            .select::<Format>()(self.data_mut(), &(mask, src_area, dst, colour, blend))
+    }
+}
+
+impl<Format: PixelFormatSpec, T: BitmapMutAccess<Format>> KahloOps<Format> for T {}

+ 59 - 0
src/op/composite.rs

@@ -0,0 +1,59 @@
+use crate::{
+    bitmap::{BitmapData, BitmapDataMut},
+    colour::{BlendMode, Colour},
+    formats::{PixelFormatSpec, A8},
+    math::{clip_to, PixelBox, PixelPoint},
+};
+
+use super::{
+    dispatch::{BinaryDispatch, GenericBinary},
+    BinaryOpSpec,
+};
+
+pub struct CompositeOp;
+
+impl<'l> GenericBinary<(PixelBox, PixelPoint, BlendMode)> for CompositeOp {
+    fn perform<Format: PixelFormatSpec, SrcFormat: PixelFormatSpec>(
+        write_data: &mut dyn BitmapDataMut,
+        read_data: &dyn BitmapData,
+        params: &(PixelBox, PixelPoint, BlendMode),
+    ) {
+        let (read_box, dst, blend) = params;
+
+        let Some((write_box, read_box)) = clip_to(
+            PixelBox::from_size(write_data.size()),
+            PixelBox::from_size(read_data.size()),
+            PixelBox::from_origin_and_size(*dst, read_data.size()),
+            *read_box,
+        ) else {
+            return;
+        };
+
+        let read_stride = read_data.stride();
+        let write_stride = write_data.stride();
+        let read_data = read_data.data();
+        let write_data = write_data.data_mut();
+        for y in 0..read_box.height() {
+            let y_offset_r = (y + read_box.min.y) as usize * read_stride;
+            let y_offset_w = (y + write_box.min.y) as usize * write_stride;
+            for x in 0..read_box.width() {
+                let x_offset_r = (x + read_box.min.x) as usize * A8::PIXEL_WIDTH;
+                let x_offset_w = (x + write_box.min.x) as usize * Format::PIXEL_WIDTH;
+
+                let existing =
+                    Colour::read_from_bytes::<Format>(&write_data[y_offset_w + x_offset_w..]);
+                let source =
+                    Colour::read_from_bytes::<SrcFormat>(&read_data[y_offset_r + x_offset_r..]);
+                let new_colour = blend.blend(&source, &existing);
+                new_colour.write_as_bytes::<Format>(&mut write_data[y_offset_w + x_offset_w..]);
+            }
+        }
+    }
+}
+
+impl BinaryOpSpec for CompositeOp {
+    type Params<'l> = (PixelBox, PixelPoint, BlendMode);
+    fn build<'l>() -> BinaryDispatch<Self::Params<'l>> {
+        BinaryDispatch::from_generic::<Self>()
+    }
+}

+ 54 - 0
src/op/copy_from.rs

@@ -0,0 +1,54 @@
+use crate::{
+    bitmap::{BitmapData, BitmapDataMut},
+    colour::Colour,
+    formats::PixelFormatSpec,
+    math::{clip_to, PixelBox, PixelPoint},
+};
+
+use super::{dispatch::BinaryDispatch, BinaryOpSpec, GenericBinary};
+
+pub struct CopyFromOp;
+
+impl BinaryOpSpec for CopyFromOp {
+    type Params<'l> = (PixelBox, PixelPoint);
+
+    fn build<'l>() -> BinaryDispatch<Self::Params<'l>> {
+        BinaryDispatch::from_generic::<Self>()
+    }
+}
+
+impl GenericBinary<(PixelBox, PixelPoint)> for CopyFromOp {
+    fn perform<Format: PixelFormatSpec, ReadFormat: PixelFormatSpec>(
+        write_data: &mut dyn BitmapDataMut,
+        read_data: &dyn BitmapData,
+        params: &(PixelBox, PixelPoint),
+    ) {
+        let read_box = params.0;
+        let write_box = PixelBox::from_origin_and_size(params.1, read_box.size());
+        let Some((read_box, write_box)) = clip_to(
+            PixelBox::from_size(read_data.size()),
+            PixelBox::from_size(write_data.size()),
+            read_box,
+            write_box,
+        ) else {
+            // nothing to do
+            return;
+        };
+
+        let write_stride = write_data.stride();
+        let write_data = write_data.data_mut();
+        let read_stride = read_data.stride();
+        let read_data = read_data.data();
+
+        for y in 0..read_box.height() {
+            let write_slice = &mut write_data[(y + write_box.min.y) as usize * write_stride..];
+            let read_slice = &read_data[(y + read_box.min.y) as usize * read_stride..];
+            for x in 0..read_box.width() {
+                Colour::convert_byte_form::<ReadFormat, Format>(
+                    &mut write_slice[(x + write_box.min.x) as usize * Format::PIXEL_WIDTH..],
+                    &read_slice[(x + read_box.min.x) as usize * ReadFormat::PIXEL_WIDTH..],
+                );
+            }
+        }
+    }
+}

+ 205 - 0
src/op/dispatch.rs

@@ -0,0 +1,205 @@
+use crate::bitmap::{BitmapData, BitmapDataMut};
+use crate::formats::{self, PixelFormatSpec, NUM_PIXEL_FORMATS};
+use crate::op::{self, BinaryOpSpec, UnaryOpSpec};
+
+pub type UnaryFunc<Params> = for<'l> fn(&'l mut dyn BitmapDataMut, &'l Params);
+pub type BinaryFunc<Params> = for<'l> fn(&'l mut dyn BitmapDataMut, &'l dyn BitmapData, &'l Params);
+
+pub trait GenericUnary<Params> {
+    fn perform<'l, Format: PixelFormatSpec>(
+        write_data: &'l mut dyn BitmapDataMut,
+        params: &'l Params,
+    );
+}
+
+pub trait GenericBinary<Params> {
+    fn perform<'l, Format: PixelFormatSpec, ReadFormat: PixelFormatSpec>(
+        write_data: &'l mut dyn BitmapDataMut,
+        read_data: &'l dyn BitmapData,
+        params: &'l Params,
+    );
+}
+
+pub struct UnaryDispatch<Params> {
+    op_array: [UnaryFunc<Params>; NUM_PIXEL_FORMATS],
+    changed: [bool; NUM_PIXEL_FORMATS],
+}
+
+impl<Params> UnaryDispatch<Params> {
+    pub fn with<Format: PixelFormatSpec>(mut self, specific_imp: UnaryFunc<Params>) -> Self {
+        self.op_array[Format::FORMAT_ENUM as usize] = specific_imp;
+        self.changed[Format::FORMAT_ENUM as usize] = true;
+        self
+    }
+    pub fn with_avx<Format: PixelFormatSpec>(mut self, avx_imp: UnaryFunc<Params>) -> Self {
+        if is_x86_feature_detected!("avx") {
+            self.op_array[Format::FORMAT_ENUM as usize] = avx_imp;
+            self.changed[Format::FORMAT_ENUM as usize] = true;
+            self
+        } else {
+            self
+        }
+    }
+
+    pub fn select<Format: PixelFormatSpec>(&self) -> UnaryFunc<Params> {
+        self.op_array[Format::FORMAT_ENUM as usize]
+    }
+}
+
+pub struct BinaryDispatch<Params> {
+    op_array: [[BinaryFunc<Params>; NUM_PIXEL_FORMATS]; NUM_PIXEL_FORMATS],
+    changed: [[bool; NUM_PIXEL_FORMATS]; NUM_PIXEL_FORMATS],
+}
+
+impl<Params> BinaryDispatch<Params> {
+    pub fn with_avx<WriteFormat: PixelFormatSpec, ReadFormat: PixelFormatSpec>(
+        mut self,
+        avx_imp: BinaryFunc<Params>,
+    ) -> Self {
+        if is_x86_feature_detected!("avx") {
+            self.op_array[WriteFormat::FORMAT_ENUM as usize][ReadFormat::FORMAT_ENUM as usize] =
+                avx_imp;
+            self
+        } else {
+            self
+        }
+    }
+
+    pub fn select<WriteFormat: PixelFormatSpec, ReadFormat: PixelFormatSpec>(
+        &self,
+    ) -> BinaryFunc<Params> {
+        self.op_array[WriteFormat::FORMAT_ENUM as usize][ReadFormat::FORMAT_ENUM as usize]
+    }
+}
+
+macro_rules! dispatch_entry {
+    (unary, $sn:ident, $opn:ident) => {
+        UnaryDispatch < <op::$sn::$opn as UnaryOpSpec>::Params<'l>>
+    };
+
+    (binary, $sn:ident, $opn:ident) => {
+        BinaryDispatch < <op::$sn::$opn as BinaryOpSpec>::Params<'l>>
+    };
+}
+
+pub struct KahloDispatch<'l> {
+    pub composite: dispatch_entry!(binary, composite, CompositeOp),
+    pub copy_from: dispatch_entry!(binary, copy_from, CopyFromOp),
+    pub fill: dispatch_entry!(unary, fill, FillOp),
+    pub fill_region: dispatch_entry!(unary, fill_region, FillRegionOp),
+    pub fill_region_masked: dispatch_entry!(unary, fill_region_masked, FillRegionMaskedOp),
+}
+
+impl<'l> Default for KahloDispatch<'l> {
+    fn default() -> Self {
+        Self {
+            composite: op::composite::CompositeOp::build(),
+            copy_from: op::copy_from::CopyFromOp::build(),
+            fill: op::fill::FillOp::build(),
+            fill_region: op::fill_region::FillRegionOp::build(),
+            fill_region_masked: op::fill_region_masked::FillRegionMaskedOp::build(),
+        }
+    }
+}
+
+impl<'l> KahloDispatch<'l> {
+    pub fn use_lifetime(&self) -> &KahloDispatch<'_> {
+        unsafe { std::mem::transmute(self) }
+    }
+}
+
+lazy_static::lazy_static! {
+    pub static ref DISPATCH: KahloDispatch<'static> = KahloDispatch::default();
+}
+
+// long but simple implementations
+
+impl<Params> UnaryDispatch<Params> {
+    pub(crate) fn from_generic<G: GenericUnary<Params>>() -> Self {
+        Self {
+            op_array: [
+                G::perform::<formats::A8>,
+                G::perform::<formats::Abgr32>,
+                G::perform::<formats::Bgr24>,
+                G::perform::<formats::Bgr32>,
+                G::perform::<formats::Rgb24>,
+                G::perform::<formats::Rgb32>,
+                G::perform::<formats::Rgba32>,
+            ],
+            changed: Default::default(),
+        }
+    }
+}
+
+impl<Params> BinaryDispatch<Params> {
+    pub(crate) fn from_generic<G: GenericBinary<Params>>() -> Self {
+        Self {
+            op_array: [
+                [
+                    G::perform::<formats::A8, formats::A8>,
+                    G::perform::<formats::A8, formats::Abgr32>,
+                    G::perform::<formats::A8, formats::Bgr24>,
+                    G::perform::<formats::A8, formats::Bgr32>,
+                    G::perform::<formats::A8, formats::Rgb24>,
+                    G::perform::<formats::A8, formats::Rgb32>,
+                    G::perform::<formats::A8, formats::Rgba32>,
+                ],
+                [
+                    G::perform::<formats::Abgr32, formats::A8>,
+                    G::perform::<formats::Abgr32, formats::Abgr32>,
+                    G::perform::<formats::Abgr32, formats::Bgr24>,
+                    G::perform::<formats::Abgr32, formats::Bgr32>,
+                    G::perform::<formats::Abgr32, formats::Rgb24>,
+                    G::perform::<formats::Abgr32, formats::Rgb32>,
+                    G::perform::<formats::Abgr32, formats::Rgba32>,
+                ],
+                [
+                    G::perform::<formats::Bgr24, formats::A8>,
+                    G::perform::<formats::Bgr24, formats::Abgr32>,
+                    G::perform::<formats::Bgr24, formats::Bgr24>,
+                    G::perform::<formats::Bgr24, formats::Bgr32>,
+                    G::perform::<formats::Bgr24, formats::Rgb24>,
+                    G::perform::<formats::Bgr24, formats::Rgb32>,
+                    G::perform::<formats::Bgr24, formats::Rgba32>,
+                ],
+                [
+                    G::perform::<formats::Bgr32, formats::A8>,
+                    G::perform::<formats::Bgr32, formats::Abgr32>,
+                    G::perform::<formats::Bgr32, formats::Bgr24>,
+                    G::perform::<formats::Bgr32, formats::Bgr32>,
+                    G::perform::<formats::Bgr32, formats::Rgb24>,
+                    G::perform::<formats::Bgr32, formats::Rgb32>,
+                    G::perform::<formats::Bgr32, formats::Rgba32>,
+                ],
+                [
+                    G::perform::<formats::Rgb24, formats::A8>,
+                    G::perform::<formats::Rgb24, formats::Abgr32>,
+                    G::perform::<formats::Rgb24, formats::Bgr24>,
+                    G::perform::<formats::Rgb24, formats::Bgr32>,
+                    G::perform::<formats::Rgb24, formats::Rgb24>,
+                    G::perform::<formats::Rgb24, formats::Rgb32>,
+                    G::perform::<formats::Rgb24, formats::Rgba32>,
+                ],
+                [
+                    G::perform::<formats::Rgb32, formats::A8>,
+                    G::perform::<formats::Rgb32, formats::Abgr32>,
+                    G::perform::<formats::Rgb32, formats::Bgr24>,
+                    G::perform::<formats::Rgb32, formats::Bgr32>,
+                    G::perform::<formats::Rgb32, formats::Rgb24>,
+                    G::perform::<formats::Rgb32, formats::Rgb32>,
+                    G::perform::<formats::Rgb32, formats::Rgba32>,
+                ],
+                [
+                    G::perform::<formats::Rgba32, formats::A8>,
+                    G::perform::<formats::Rgba32, formats::Abgr32>,
+                    G::perform::<formats::Rgba32, formats::Bgr24>,
+                    G::perform::<formats::Rgba32, formats::Bgr32>,
+                    G::perform::<formats::Rgba32, formats::Rgb24>,
+                    G::perform::<formats::Rgba32, formats::Rgb32>,
+                    G::perform::<formats::Rgba32, formats::Rgba32>,
+                ],
+            ],
+            changed: Default::default(),
+        }
+    }
+}

+ 21 - 11
src/op/fill.rs

@@ -1,14 +1,17 @@
-use crate::{dispatch::GenericUnary, colour::Colour, bitmap::BitmapDataMut, formats::{MAX_PIXEL_WIDTH, A8}};
+use crate::{
+    bitmap::BitmapDataMut,
+    colour::Colour,
+    formats::{A8, MAX_PIXEL_WIDTH},
+};
 
-use super::UnaryOpSpec;
+use super::{GenericUnary, UnaryDispatch, UnaryOpSpec};
 
 pub struct FillOp;
 
 impl UnaryOpSpec for FillOp {
-    type Params = Colour;
-    fn build() -> crate::dispatch::UnaryDispatch<Self::Params> {
-        FillOp::make_dispatch()
-            .with::<A8>(fill_a8)
+    type Params<'l> = Colour;
+    fn build<'l>() -> UnaryDispatch<Self::Params<'l>> {
+        UnaryDispatch::from_generic::<Self>().with::<A8>(fill_a8)
     }
 }
 
@@ -22,14 +25,14 @@ fn fill_a8(target: &mut dyn BitmapDataMut, with: &Colour) {
 
     // optimization: can we fill the entire thing in one go?
     if stride == row_width {
-        data[0..(stride*height)].fill(alpha);
+        data[0..(stride * height)].fill(alpha);
         return;
     }
 
     for y in 0..height {
         let yoff = y * stride;
 
-        data[yoff..(yoff+row_width)].fill(alpha);
+        data[yoff..(yoff + row_width)].fill(alpha);
     }
 }
 
@@ -59,8 +62,13 @@ fn fill_helper<const WIDTH: usize>(target: &mut dyn BitmapDataMut, pattern: &[u8
 }
 
 impl<'l> GenericUnary<Colour> for FillOp {
-    fn perform<Format: crate::formats::PixelFormatSpec>(write_data: &mut dyn BitmapDataMut, params: &Colour) {
-        if write_data.width() == 0 || write_data.height() == 0 { return }
+    fn perform<Format: crate::formats::PixelFormatSpec>(
+        write_data: &mut dyn BitmapDataMut,
+        params: &Colour,
+    ) {
+        if write_data.width() == 0 || write_data.height() == 0 {
+            return;
+        }
 
         let mut cdata = [0u8; MAX_PIXEL_WIDTH];
         params.write_as_bytes::<Format>(&mut cdata);
@@ -75,7 +83,9 @@ impl<'l> GenericUnary<Colour> for FillOp {
             fill_helper::<3>(write_data, cdata);
         } else if Format::PIXEL_WIDTH == 4 {
             fill_helper::<4>(write_data, cdata);
-        } else { unreachable!() }
+        } else {
+            unreachable!()
+        }
     }
 }
 

+ 31 - 0
src/op/fill_region.rs

@@ -0,0 +1,31 @@
+use crate::{
+    bitmap::{BitmapDataMut, MakeMutView},
+    colour::Colour,
+    formats::PixelFormatSpec,
+    math::PixelBox,
+};
+
+use super::{
+    dispatch::{GenericUnary, UnaryDispatch},
+    KahloOps, UnaryOpSpec,
+};
+
+pub struct FillRegionOp;
+
+impl GenericUnary<(PixelBox, Colour)> for FillRegionOp {
+    fn perform<Format: PixelFormatSpec>(
+        mut write_data: &mut dyn BitmapDataMut,
+        params: &(PixelBox, Colour),
+    ) {
+        // make a view and forward to FillOp
+        let mut subregion = write_data.make_view_mut::<Format>(params.0);
+        subregion.fill(params.1);
+    }
+}
+
+impl UnaryOpSpec for FillRegionOp {
+    type Params<'l> = (PixelBox, Colour);
+    fn build<'l>() -> UnaryDispatch<Self::Params<'l>> {
+        UnaryDispatch::from_generic::<Self>()
+    }
+}

+ 77 - 0
src/op/fill_region_masked.rs

@@ -0,0 +1,77 @@
+use crate::{
+    bitmap::{BitmapAccess, BitmapDataMut},
+    colour::{BlendMode, Colour},
+    formats::{PixelFormatSpec, A8},
+    math::{clip_to, PixelBox, PixelPoint},
+};
+
+use super::{
+    dispatch::{GenericUnary, UnaryDispatch},
+    UnaryOpSpec,
+};
+
+pub struct FillRegionMaskedOp;
+
+impl<'l>
+    GenericUnary<(
+        &'l dyn BitmapAccess<A8>,
+        PixelBox,
+        PixelPoint,
+        Colour,
+        BlendMode,
+    )> for FillRegionMaskedOp
+{
+    fn perform<Format: PixelFormatSpec>(
+        write_data: &mut dyn BitmapDataMut,
+        params: &(
+            &'l dyn BitmapAccess<A8>,
+            PixelBox,
+            PixelPoint,
+            Colour,
+            BlendMode,
+        ),
+    ) {
+        let (read_alphamap, read_box, dst, colour, blend) = params;
+
+        let Some((write_box, read_box)) = clip_to(
+            PixelBox::from_size(write_data.size()),
+            PixelBox::from_size(read_alphamap.size()),
+            PixelBox::from_origin_and_size(*dst, read_alphamap.size()),
+            *read_box,
+        ) else {
+            return;
+        };
+
+        let read_stride = read_alphamap.row_stride();
+        let write_stride = write_data.stride();
+        let read_data = read_alphamap.data().data();
+        let write_data = write_data.data_mut();
+        for y in 0..read_box.height() {
+            let y_offset_r = (y + read_box.min.y) as usize * read_stride;
+            let y_offset_w = (y + write_box.min.y) as usize * write_stride;
+            for x in 0..read_box.width() {
+                let x_offset_r = (x + read_box.min.x) as usize * A8::PIXEL_WIDTH;
+                let x_offset_w = (x + write_box.min.x) as usize * Format::PIXEL_WIDTH;
+
+                let existing =
+                    Colour::read_from_bytes::<Format>(&write_data[y_offset_w + x_offset_w..]);
+                let alpha_override = read_data[y_offset_r + x_offset_r];
+                let new_colour = blend.blend(&colour.with_a(alpha_override), &existing);
+                new_colour.write_as_bytes::<Format>(&mut write_data[y_offset_w + x_offset_w..]);
+            }
+        }
+    }
+}
+
+impl UnaryOpSpec for FillRegionMaskedOp {
+    type Params<'l> = (
+        &'l dyn BitmapAccess<A8>,
+        PixelBox,
+        PixelPoint,
+        Colour,
+        BlendMode,
+    );
+    fn build<'l>() -> UnaryDispatch<Self::Params<'l>> {
+        UnaryDispatch::from_generic::<Self>()
+    }
+}

+ 2 - 2
tests/common/mod.rs

@@ -1,4 +1,4 @@
-use kahlo::{Bitmap, prelude::*};
+use kahlo::{prelude::*, Bitmap};
 
 pub fn load_test_resource(name: &'static str) -> Bitmap<kahlo::formats::Rgba32> {
     let mut path = std::path::PathBuf::new();
@@ -27,7 +27,7 @@ pub fn save_test_result_rgba32(
 
     image::save_buffer(
         path,
-        bitmap.data(),
+        bitmap.data().data(),
         bitmap.width() as u32,
         bitmap.height() as u32,
         image::ExtendedColorType::Rgba8,