solstice-2d 0.2.21

Easy, cross-platform 2D graphics.
Documentation
use image::GenericImageView;
use solstice::image::MappedImage;
use solstice::texture::Texture;
use std::num::NonZeroU32;
use wfc::orientation::OrientationTable;
use wfc::overlapping::{OverlappingPatterns, Pattern};
use wfc::retry as wfc_retry;
use wfc::*;

pub mod retry {
    pub use super::wfc_retry::RetryOwn as Retry;
    pub use super::wfc_retry::{Forever, NumTimes};

    pub trait ImageRetry: Retry {
        type ImageReturn;
        #[doc(hidden)]
        fn image_return(
            r: Self::Return,
            image_patterns: &super::ImagePatterns,
            ctx: &mut solstice::Context,
        ) -> Self::ImageReturn;
    }
}

struct Forbid {
    bottom_left_corner_id: PatternId,
    flower_id: PatternId,
    sprout_coord: Coord,
    sprout_id: PatternId,
}

impl ForbidPattern for Forbid {
    fn forbid<W: Wrap, R: rand::Rng>(&mut self, fi: &mut ForbidInterface<W>, rng: &mut R) {
        fi.forbid_all_patterns_except(self.sprout_coord, self.sprout_id, rng)
            .unwrap();
        let output_size = fi.wave_size();
        for i in 0..(output_size.width() as i32) {
            let coord = Coord::new(i, output_size.height() as i32 - 1);
            fi.forbid_all_patterns_except(coord, self.bottom_left_corner_id, rng)
                .unwrap();
        }
        for i in 0..8 {
            for j in 0..(output_size.width() as i32) {
                let coord = Coord::new(j, output_size.height() as i32 - 1 - i);
                fi.forbid_pattern(coord, self.flower_id, rng).unwrap();
            }
        }
    }
}

pub struct ImagePatterns {
    overlapping_patterns: OverlappingPatterns<[u8; 4]>,
    empty_colour: [u8; 4],
}

impl ImagePatterns {
    pub fn new(
        image: &MappedImage,
        pattern_size: NonZeroU32,
        orientations: &[Orientation],
    ) -> Self {
        let width = image.inner().get_texture_info().width() as u32;
        let size = Size::new(width, image.inner().get_texture_info().height() as u32);
        let grid = grid_2d::Grid::new_fn(size, |Coord { x, y }| {
            let pixel = image.get_pixel(x as usize, y as usize);
            [pixel[0], pixel[1], pixel[2], 255]
        });
        let overlapping_patterns = OverlappingPatterns::new(grid, pattern_size, orientations);
        Self {
            overlapping_patterns,
            empty_colour: [0, 0, 0, 0],
        }
    }

    pub fn set_empty_colour(&mut self, empty_colour: [u8; 4]) {
        self.empty_colour = empty_colour;
    }

    pub fn image_from_wave(&self, wave: &Wave, ctx: &mut solstice::Context) -> MappedImage {
        let size = wave.grid().size();
        let mut image = MappedImage::with_data(
            ctx,
            solstice::texture::TextureType::Tex2D,
            solstice::PixelFormat::RGB8,
            size.width(),
            size.height(),
            vec![0; (size.width() * size.height() * 3) as usize],
            solstice::image::Settings {
                filter: solstice::texture::FilterMode::Nearest,
                ..solstice::image::Settings::default()
            },
        )
        .unwrap();
        let pixel_stride = image.pixel_stride();
        wave.grid().enumerate().for_each(|(Coord { x, y }, cell)| {
            let color = match cell.chosen_pattern_id() {
                Ok(pattern_id) => *self.overlapping_patterns.pattern_top_left_value(pattern_id),
                Err(_) => self.empty_colour,
            };
            image.set_pixel(x as usize, y as usize, &color[..pixel_stride]);
        });
        image
    }

    pub fn weighted_average_colour<'a>(&self, cell: &'a WaveCellRef<'a>) -> [u8; 4] {
        use wfc::EnumerateCompatiblePatternWeights::*;
        match cell.enumerate_compatible_pattern_weights() {
            MultipleCompatiblePatternsWithoutWeights | NoCompatiblePattern => self.empty_colour,
            SingleCompatiblePatternWithoutWeight(pattern_id) => {
                *self.overlapping_patterns.pattern_top_left_value(pattern_id)
            }
            CompatiblePatternsWithWeights(iter) => {
                let (r, g, b, a) = iter
                    .map(|(pattern_id, weight)| {
                        let &[r, g, b, a] =
                            self.overlapping_patterns.pattern_top_left_value(pattern_id);
                        [
                            r as u32 * weight,
                            g as u32 * weight,
                            b as u32 * weight,
                            a as u32 * weight,
                        ]
                    })
                    .fold(
                        (0, 0, 0, 0),
                        |(acc_r, acc_g, acc_b, acc_a), [r, g, b, a]| {
                            (acc_r + r, acc_g + g, acc_b + b, acc_a + a)
                        },
                    );
                let total_weight = cell.sum_compatible_pattern_weight();
                [
                    (r / total_weight) as u8,
                    (g / total_weight) as u8,
                    (b / total_weight) as u8,
                    (a / total_weight) as u8,
                ]
            }
        }
    }

    pub fn grid(&self) -> &grid_2d::Grid<[u8; 4]> {
        self.overlapping_patterns.grid()
    }

    pub fn id_grid(&self) -> grid_2d::Grid<OrientationTable<PatternId>> {
        self.overlapping_patterns.id_grid()
    }

    pub fn id_grid_original_orientation(&self) -> grid_2d::Grid<PatternId> {
        self.overlapping_patterns.id_grid_original_orientation()
    }

    pub fn pattern(&self, pattern_id: PatternId) -> &Pattern {
        self.overlapping_patterns.pattern(pattern_id)
    }

    pub fn pattern_mut(&mut self, pattern_id: PatternId) -> &mut Pattern {
        self.overlapping_patterns.pattern_mut(pattern_id)
    }

    pub fn global_stats(&self) -> GlobalStats {
        self.overlapping_patterns.global_stats()
    }

    pub fn collapse_wave_retrying<W, F, RT, R>(
        &self,
        output_size: Size,
        wrap: W,
        forbid: F,
        retry: RT,
        rng: &mut R,
    ) -> RT::Return
    where
        W: Wrap,
        F: ForbidPattern + Send + Sync + Clone,
        RT: retry::Retry,
        R: rand::Rng + Send + Sync + Clone,
    {
        let global_stats = self.global_stats();
        let run = RunOwn::new_wrap_forbid(output_size, &global_stats, wrap, forbid, rng);
        run.collapse_retrying(retry, rng)
    }
}

impl retry::ImageRetry for retry::Forever {
    type ImageReturn = MappedImage;
    fn image_return(
        r: Self::Return,
        image_patterns: &ImagePatterns,
        ctx: &mut solstice::Context,
    ) -> Self::ImageReturn {
        image_patterns.image_from_wave(&r, ctx)
    }
}

impl retry::ImageRetry for retry::NumTimes {
    type ImageReturn = Result<MappedImage, PropagateError>;
    fn image_return(
        r: Self::Return,
        image_patterns: &ImagePatterns,
        ctx: &mut solstice::Context,
    ) -> Self::ImageReturn {
        match r {
            Ok(r) => Ok(image_patterns.image_from_wave(&r, ctx)),
            Err(e) => Err(e),
        }
    }
}

fn main() {
    use glutin::{
        event::*,
        event_loop::{ControlFlow, EventLoop},
        window::WindowBuilder,
    };

    let event_loop = EventLoop::new();
    let (width, height) = (1280, 720);
    let wb = WindowBuilder::new().with_inner_size(glutin::dpi::PhysicalSize::new(width, height));
    let window = glutin::ContextBuilder::new()
        .with_multisampling(16)
        .with_double_buffer(Some(true))
        .with_vsync(true)
        .build_windowed(wb, &event_loop)
        .unwrap();
    let window = unsafe { window.make_current().unwrap() };
    let glow_ctx = unsafe {
        solstice::glow::Context::from_loader_function(|name| window.get_proc_address(name))
    };
    let mut context = solstice::Context::new(glow_ctx);
    let mut d2 = solstice_2d::Graphics::new(&mut context, width as _, height as _).unwrap();

    let image_settings = solstice::image::Settings {
        mipmaps: false,
        filter: solstice::texture::FilterMode::Nearest,
        ..solstice::image::Settings::default()
    };
    let output_size = Size::new(64, 64);
    let pattern_size = NonZeroU32::new(3).unwrap();

    let input_image = {
        let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
            .join("examples")
            .join("resources")
            .join("flowers.png");
        image::open(path).unwrap()
    };

    let proto = {
        let buf = input_image.as_rgb8().unwrap().to_vec();
        solstice::image::MappedImage::with_data(
            &mut context,
            solstice::texture::TextureType::Tex2D,
            solstice::PixelFormat::RGB8,
            input_image.width(),
            input_image.height(),
            buf,
            image_settings,
        )
        .unwrap()
    };

    let image = {
        use rand::SeedableRng;
        let image_patterns = ImagePatterns::new(&proto, pattern_size, &[Orientation::Original]);
        let wave = image_patterns.collapse_wave_retrying(
            output_size,
            wfc::wrap::WrapXY,
            ForbidNothing,
            retry::NumTimes(10),
            &mut rand::rngs::StdRng::from_entropy(),
        );
        let mut image = <retry::NumTimes as retry::ImageRetry>::image_return(
            wave,
            &image_patterns,
            &mut context,
        )
        .unwrap();
        image.unmap(&mut context);
        image
    };

    event_loop.run(move |event, _, cf| match event {
        Event::WindowEvent { window_id, event } => {
            if window_id == window.window().id() {
                match event {
                    WindowEvent::CloseRequested => *cf = ControlFlow::Exit,
                    WindowEvent::KeyboardInput {
                        input:
                            KeyboardInput {
                                state: ElementState::Pressed,
                                virtual_keycode: Some(VirtualKeyCode::Escape),
                                ..
                            },
                        ..
                    } => *cf = ControlFlow::Exit,
                    _ => {}
                }
            }
        }
        Event::RedrawRequested(window_id) => {
            if window_id == window.window().id() {
                use solstice_2d::*;
                let mut g = DrawList::default();
                g.clear([1., 0., 0., 1.]);
                let rectangle = solstice_2d::Rectangle {
                    x: 0.0,
                    y: 0.0,
                    width: 400.0,
                    height: 400.0,
                };
                g.image(rectangle, image.inner());

                let rectangle = solstice_2d::Rectangle {
                    x: 400.0,
                    y: 0.0,
                    width: 400.0,
                    height: 400.0,
                };
                g.image(rectangle, proto.inner());
                d2.process(&mut context, &mut g);

                window.swap_buffers().unwrap();
            }
        }
        Event::MainEventsCleared => {
            window.window().request_redraw();
        }
        _ => {}
    })
}