From 3b7638a012975f60da89d4ce27a6e0bf55c4e364 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Fri, 4 Oct 2019 22:48:29 -0700 Subject: [PATCH] Initial simple-invaders WIP - Currently animates two flavors of invader in their usual formation - Lots left to do, but this is a good start --- Cargo.lock | 17 + Cargo.toml | 2 + examples/invaders/main.rs | 63 +-- simple-invaders/Cargo.toml | 8 + simple-invaders/src/assets/blipjoy1.pcx | Bin 0 -> 374 bytes simple-invaders/src/assets/blipjoy2.pcx | Bin 0 -> 350 bytes simple-invaders/src/assets/ferris1.pcx | Bin 0 -> 329 bytes simple-invaders/src/assets/ferris2.pcx | Bin 0 -> 383 bytes simple-invaders/src/assets/player1.pcx | Bin 0 -> 452 bytes simple-invaders/src/assets/player2.pcx | Bin 0 -> 458 bytes simple-invaders/src/assets/shield.pcx | Bin 0 -> 350 bytes simple-invaders/src/lib.rs | 512 ++++++++++++++++++++++++ 12 files changed, 555 insertions(+), 47 deletions(-) create mode 100644 simple-invaders/Cargo.toml create mode 100644 simple-invaders/src/assets/blipjoy1.pcx create mode 100644 simple-invaders/src/assets/blipjoy2.pcx create mode 100644 simple-invaders/src/assets/ferris1.pcx create mode 100644 simple-invaders/src/assets/ferris2.pcx create mode 100644 simple-invaders/src/assets/player1.pcx create mode 100644 simple-invaders/src/assets/player2.pcx create mode 100644 simple-invaders/src/assets/shield.pcx create mode 100644 simple-invaders/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 52a1c66..3664f68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -672,6 +672,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pcx" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -684,6 +692,7 @@ dependencies = [ "env_logger 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "pixels-mocks 0.1.0", + "simple-invaders 0.1.0", "vk-shader-macros 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "wgpu 0.3.0 (git+https://github.com/gfx-rs/wgpu-rs?rev=697393df4793e1a58578209885036114adfb9213)", "winit 0.20.0-alpha3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -906,6 +915,13 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "simple-invaders" +version = "0.1.0" +dependencies = [ + "pcx 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "slab" version = "0.4.2" @@ -1410,6 +1426,7 @@ dependencies = [ "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +"checksum pcx 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1b00b062973776578e7863f8395f86e821760d827384f1ad9371d0893c87481a" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea" "checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8" diff --git a/Cargo.toml b/Cargo.toml index 9f77552..f236087 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,11 @@ wgpu = { git = "https://github.com/gfx-rs/wgpu-rs", rev = "697393df4793e1a585782 env_logger = "0.7" log = { version = "0.4", features = ["release_max_level_warn"] } pixels-mocks = { path = "pixels-mocks" } +simple-invaders = { path = "simple-invaders" } winit = "0.20.0-alpha3" [workspace] members = [ "pixels-mocks", + "simple-invaders", ] diff --git a/examples/invaders/main.rs b/examples/invaders/main.rs index d70d541..eafa1c6 100644 --- a/examples/invaders/main.rs +++ b/examples/invaders/main.rs @@ -1,54 +1,21 @@ use pixels::{Error, Pixels, SurfaceTexture}; +use simple_invaders::{World, SCREEN_HEIGHT, SCREEN_WIDTH}; use winit::event; use winit::event_loop::{ControlFlow, EventLoop}; -fn scale_pixel_ferris(width: u32, height: u32) -> Vec { - let mut px = Vec::new(); - - const FERRIS_WIDTH: u32 = 11; - const FERRIS_HEIGHT: u32 = 5; - #[rustfmt::skip] - const FERRIS: [u8; (FERRIS_WIDTH * FERRIS_HEIGHT) as usize] = [ - 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, - 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, - 0, 0, 1, 1, 2, 1, 2, 1, 1, 0, 0, - 0, 1, 3, 1, 1, 2, 1, 1, 3, 1, 0, - 0, 0, 1, 3, 0, 0, 0, 3, 1, 0, 0, - ]; - - let scale = width / FERRIS_WIDTH; - let top = (height - scale * FERRIS_HEIGHT) / 2; - let bottom = height - top - 1; - - for y in 0..height { - for x in 0..width { - let rgba = if y < top || y >= bottom || x / scale >= FERRIS_WIDTH { - [0xdd, 0xba, 0xdc, 0xff] - } else { - let i = x / scale + (y - top) / scale * FERRIS_WIDTH; - - match FERRIS[i as usize] { - 0 => [0xdd, 0xba, 0xdc, 0xff], - 1 => [0xf7, 0x4c, 0x00, 0xff], - 2 => [0x00, 0x00, 0x00, 0xff], - 3 => [0xa5, 0x2b, 0x00, 0xff], - _ => unreachable!(), - } - }; - - px.extend_from_slice(&rgba); - } - } - - px -} - fn main() -> Result<(), Error> { env_logger::init(); let event_loop = EventLoop::new(); let (window, surface, width, height) = { - let window = winit::window::Window::new(&event_loop).unwrap(); + let scale = 3.0; + let width = SCREEN_WIDTH as f64 * scale; + let height = SCREEN_HEIGHT as f64 * scale; + + let window = winit::window::WindowBuilder::new() + .with_inner_size(winit::dpi::LogicalSize::new(width, height)) + .build(&event_loop) + .unwrap(); let surface = wgpu::Surface::create(&window); let size = window.inner_size().to_physical(window.hidpi_factor()); @@ -56,9 +23,8 @@ fn main() -> Result<(), Error> { }; let surface_texture = SurfaceTexture::new(width, height, &surface); - let mut fb = Pixels::new(320, 240, surface_texture)?; - - let ferris = scale_pixel_ferris(320, 240); + let mut fb = Pixels::new(224, 256, surface_texture)?; + let mut invaders = World::new(); event_loop.run(move |event, _, control_flow| match event { event::Event::WindowEvent { event, .. } => match event { @@ -72,10 +38,13 @@ fn main() -> Result<(), Error> { .. } | event::WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - event::WindowEvent::RedrawRequested => fb.render(&ferris), + event::WindowEvent::RedrawRequested => fb.render(invaders.draw()), _ => (), }, - event::Event::EventsCleared => window.request_redraw(), + event::Event::EventsCleared => { + invaders.update(); + window.request_redraw(); + } _ => (), }); } diff --git a/simple-invaders/Cargo.toml b/simple-invaders/Cargo.toml new file mode 100644 index 0000000..27c24e8 --- /dev/null +++ b/simple-invaders/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "simple-invaders" +version = "0.1.0" +authors = ["Jay Oster "] +edition = "2018" + +[dependencies] +pcx = "0.2" diff --git a/simple-invaders/src/assets/blipjoy1.pcx b/simple-invaders/src/assets/blipjoy1.pcx new file mode 100644 index 0000000000000000000000000000000000000000..82a3bd33c15a1e9c2f1a00b10b8ad77707e1eac6 GIT binary patch literal 374 zcmb7;feL^i3`9>tD!Mdg`}r65h421Bz0&A|M; p!h$jdk?K(E7;mQ__BbN#h&tlaLgn+=x{043*Sb)$p^mnGYbWDqiO~Q6 literal 0 HcmV?d00001 diff --git a/simple-invaders/src/assets/blipjoy2.pcx b/simple-invaders/src/assets/blipjoy2.pcx new file mode 100644 index 0000000000000000000000000000000000000000..9a429997e01795a7d37f64d29d0d5a5f1f943e59 GIT binary patch literal 350 zcmb7;;R=8-2!wA$RC}^sJM~vD<<1H!!eENWIlqI|vCLpCn6R?DYp5vQp*yf6THQD= lg}Fi>D%c$2R6*tgu|SZyNb`IV*>=7M8S}p>Kcxrtbq)U8g_N(~kl9x6?(Mbx@zelrQo5I?#Z3(v%A*&V}z>lWRD14Ico95z;!d`?=2pZ3y%&|oi0J~>3 literal 0 HcmV?d00001 diff --git a/simple-invaders/src/assets/ferris2.pcx b/simple-invaders/src/assets/ferris2.pcx new file mode 100644 index 0000000000000000000000000000000000000000..43bdd9a773d93545c90652a3a174be5dabd7b1d6 GIT binary patch literal 383 zcmb79(F%Yt2)q$Rj6S|!nlk#V?NX>7EShti$DN{iPAeE03wFNYm^P&0rn{julveq# p3<*_nhXHZst%W);A^S_54F?YXa|yWhLHdO literal 0 HcmV?d00001 diff --git a/simple-invaders/src/assets/player1.pcx b/simple-invaders/src/assets/player1.pcx new file mode 100644 index 0000000000000000000000000000000000000000..5613de6b768ab18325b6ccd7ef635f009e201c7b GIT binary patch literal 452 zcmcJJ!3uyN5JaCu5MAoC`fVlk64D=S+bNYWJT$ZWU|0rr9?gVmQE%#Q4J)R*^b{ga nB7ZQjOCYBwh^I}*AR;cZWD>-)tNL2q@olCaWd-TBHFh~2f| literal 0 HcmV?d00001 diff --git a/simple-invaders/src/assets/player2.pcx b/simple-invaders/src/assets/player2.pcx new file mode 100644 index 0000000000000000000000000000000000000000..c307aa82268f6f3be659cd7b549984059a0478fe GIT binary patch literal 458 zcmcJKy$XOZ41}+UATIh?zIKC67b(8c869i~sdg}zd_&;!GxlzP#bjDc&C$(p&8BRK s5Jvn##Y#oV0A)a(f{lWbNW@d^I0aAp;7<9_@J!~a(@w}kuN|+A10A-yW&i*H literal 0 HcmV?d00001 diff --git a/simple-invaders/src/assets/shield.pcx b/simple-invaders/src/assets/shield.pcx new file mode 100644 index 0000000000000000000000000000000000000000..f8e61e72ca78524ed6674ecaebe1709207f12977 GIT binary patch literal 350 zcmb8p!3sc76olblQnIn)xxJEmkt9j-I^*ulLdi_$+fALS$VyTgYP!_l$y>7Dqh~NP rX~rE{HR29z8gL`KB5vSNz~vNQcj>*8mh_h!wG-tA9R#_(PI|dFpqGuw literal 0 HcmV?d00001 diff --git a/simple-invaders/src/lib.rs b/simple-invaders/src/lib.rs new file mode 100644 index 0000000..513ed97 --- /dev/null +++ b/simple-invaders/src/lib.rs @@ -0,0 +1,512 @@ +use std::collections::HashMap; +use std::io::Cursor; +use std::rc::Rc; + +type CachedSprite = (usize, usize, Rc>); + +// Invader positioning +const START: Point = Point::new(24, 60); +const GRID: Point = Point::new(16, 16); + +// Screen handling +pub const SCREEN_WIDTH: usize = 224; +pub const SCREEN_HEIGHT: usize = 256; + +#[derive(Debug)] +pub struct World { + invaders: Invaders, + lasers: Vec, + shields: Vec, + player: Player, + cannons: Vec, + score: u32, + assets: Assets, + screen: Vec, +} + +/// A list of assets loaded into memory. +#[derive(Debug)] +struct Assets { + // sounds: TODO + sprites: HashMap, +} + +/// A tiny position vector +#[derive(Debug, Default, Eq, PartialEq)] +struct Point { + x: usize, + y: usize, +} + +/// A collection of invaders. +#[derive(Debug)] +struct Invaders { + grid: Vec>>, + stepper: Stepper, + bounds: Bounds, +} + +/// Everything you ever wanted to know about Invaders +#[derive(Debug)] +struct Invader { + sprite: SpriteRef, + pos: Point, + score: u32, +} + +/// The stepper will linerly walk through the 2D vector of invaders, updating their state along the +/// way. +#[derive(Debug)] +struct Stepper { + row: usize, + col: usize, +} + +/// Creates a boundary around the live invaders. +/// +/// Used for collision detection and minor optimizations. +#[derive(Debug)] +struct Bounds { + left: usize, + right: usize, + bottom: usize, +} + +/// The player entity. +#[derive(Debug)] +struct Player { + sprite: SpriteRef, + pos: Point, +} + +/// The shield entity. +#[derive(Debug)] +struct Shield { + // Shield sprite is not referenced because we want to deform it when it gets shot + sprite: Sprite, + pos: Point, +} + +/// The laser entity. +#[derive(Debug)] +struct Laser { + sprite: SpriteRef, + pos: Point, +} + +/// The cannon entity. +#[derive(Debug)] +struct Cannon { + sprite: SpriteRef, + pos: Point, +} + +/// Sprites can be drawn and animated. +#[derive(Debug)] +struct Sprite { + width: usize, + height: usize, + pixels: Vec, + frame: String, +} + +/// SpriteRefs can be drawn and animated. +/// +/// They reference their pixel data (instead of owning it). +#[derive(Debug)] +struct SpriteRef { + width: usize, + height: usize, + pixels: Rc>, + frame: String, +} + +trait Sprites { + fn width(&self) -> usize; + fn height(&self) -> usize; + fn pixels(&self) -> &[u8]; + fn frame(&self) -> &str; +} + +impl World { + pub fn new() -> World { + // Load assets first + let assets = load_assets(); + + let invaders = Invaders { + grid: make_invader_grid(&assets), + stepper: Stepper::default(), + bounds: Bounds::default(), + }; + let player = Player { + sprite: SpriteRef::new(&assets, "player1"), + pos: Point::new(80, 216), + }; + let shields = (0..5) + .map(|i| Shield { + sprite: Sprite::new(&assets, "shield"), + pos: Point::new(i * 45 + 32, 192), + }) + .collect(); + + // Create a screen with the correct size + let mut screen = Vec::new(); + screen.resize_with(SCREEN_WIDTH * SCREEN_HEIGHT * 4, Default::default); + + World { + invaders, + lasers: Vec::new(), + shields, + player, + cannons: Vec::new(), + score: 0, + assets, + screen, + } + } + + pub fn update(&mut self) { + // Update the next invader + let row = self.invaders.stepper.row; + let col = self.invaders.stepper.col; + + // Animate the invader + if let Some(invader) = &mut self.invaders.grid[row][col] { + invader.sprite.frame = match invader.sprite.frame.as_ref() { + "blipjoy1" => { + invader.sprite.pixels = self.assets.sprites.get("blipjoy2").unwrap().2.clone(); + "blipjoy2".into() + } + "blipjoy2" => { + invader.sprite.pixels = self.assets.sprites.get("blipjoy1").unwrap().2.clone(); + "blipjoy1".into() + } + "ferris1" => { + invader.sprite.pixels = self.assets.sprites.get("ferris2").unwrap().2.clone(); + "ferris2".into() + } + "ferris2" => { + invader.sprite.pixels = self.assets.sprites.get("ferris1").unwrap().2.clone(); + "ferris1".into() + } + _ => unreachable!(), + }; + } + + // Find the next invader + self.invaders.stepper.col += 1; + if self.invaders.stepper.col >= 11 { + self.invaders.stepper.col = 0; + if self.invaders.stepper.row == 0 { + self.invaders.stepper.row = 4; + } else { + self.invaders.stepper.row -= 1; + } + } + } + + pub fn draw(&mut self) -> &[u8] { + // Clear the screen + self.clear(); + + // Draw the invaders + for row in &self.invaders.grid { + for col in row { + if let Some(invader) = col { + blit(&mut self.screen, &invader.pos, &invader.sprite); + } + } + } + + &self.screen + } + + fn clear(&mut self) { + for (i, byte) in self.screen.iter_mut().enumerate() { + *byte = if i % 4 == 3 { 255 } else { 0 }; + } + } +} + +impl Point { + const fn new(x: usize, y: usize) -> Point { + Point { x, y } + } +} + +impl std::ops::Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self::new(self.x + other.x, self.y + other.y) + } +} + +impl std::ops::Mul for Point { + type Output = Self; + + fn mul(self, other: Self) -> Self { + Self::new(self.x * other.x, self.y * other.y) + } +} + +impl Default for Stepper { + fn default() -> Self { + Self { row: 4, col: 0 } + } +} + +impl Default for Bounds { + fn default() -> Self { + Self { + left: START.x, + right: START.x + 11 * GRID.x, + bottom: START.y + 5 * GRID.y, + } + } +} + +impl Sprite { + fn new(assets: &Assets, name: &str) -> Sprite { + let cached_sprite = assets.sprites.get(name).unwrap(); + Sprite { + width: cached_sprite.0, + height: cached_sprite.1, + pixels: cached_sprite.2.to_vec(), + frame: name.into(), + } + } +} + +impl Sprites for Sprite { + fn width(&self) -> usize { + self.width + } + + fn height(&self) -> usize { + self.height + } + + fn pixels(&self) -> &[u8] { + &self.pixels + } + + fn frame(&self) -> &str { + &self.frame + } +} + +impl SpriteRef { + fn new(assets: &Assets, name: &str) -> SpriteRef { + let cached_sprite = assets.sprites.get(name).unwrap(); + SpriteRef { + width: cached_sprite.0, + height: cached_sprite.1, + pixels: cached_sprite.2.clone(), + frame: name.into(), + } + } +} + +impl Sprites for SpriteRef { + fn width(&self) -> usize { + self.width + } + + fn height(&self) -> usize { + self.height + } + + fn pixels(&self) -> &[u8] { + &self.pixels + } + + fn frame(&self) -> &str { + &self.frame + } +} + +/// Load all static assets into an `Assets` structure +fn load_assets() -> Assets { + let mut sprites = HashMap::new(); + + sprites.insert( + "blipjoy1".into(), + load_pcx(include_bytes!("assets/blipjoy1.pcx")), + ); + sprites.insert( + "blipjoy2".into(), + load_pcx(include_bytes!("assets/blipjoy2.pcx")), + ); + sprites.insert( + "ferris1".into(), + load_pcx(include_bytes!("assets/ferris1.pcx")), + ); + sprites.insert( + "ferris2".into(), + load_pcx(include_bytes!("assets/ferris2.pcx")), + ); + sprites.insert( + "player1".into(), + load_pcx(include_bytes!("assets/player1.pcx")), + ); + sprites.insert( + "player2".into(), + load_pcx(include_bytes!("assets/player2.pcx")), + ); + sprites.insert( + "shield".into(), + load_pcx(include_bytes!("assets/shield.pcx")), + ); + // sprites.insert("laser1".into(), load_pcx(include_bytes!("assets/laser1.pcx"))); + // sprites.insert("laser2".into(), load_pcx(include_bytes!("assets/laser2.pcx"))); + + Assets { sprites } +} + +/// Convert PCX data to raw pixels +fn load_pcx(pcx: &[u8]) -> CachedSprite { + let mut reader = pcx::Reader::new(Cursor::new(pcx)).unwrap(); + let width = reader.width() as usize; + let height = reader.height() as usize; + let mut result = Vec::new(); + + if reader.is_paletted() { + // Read the raw pixel data + let mut buffer = Vec::new(); + buffer.resize_with(width * height, Default::default); + for y in 0..height { + let a = y as usize * width; + let b = a + width; + reader.next_row_paletted(&mut buffer[a..b]).unwrap(); + } + + // Read the pallete + let mut palette = Vec::new(); + let palette_length = reader.palette_length().unwrap() as usize; + palette.resize_with(palette_length * 3, Default::default); + reader.read_palette(&mut palette).unwrap(); + + // Copy to result with an alpha component + let pixels = buffer + .into_iter() + .map(|pal| { + let i = pal as usize * 3; + &palette[i..i + 3] + }) + .flatten() + .cloned() + .collect::>(); + result.extend_from_slice(&pixels); + } else { + for _ in 0..height { + // Read the raw pixel data + let mut buffer = Vec::new(); + buffer.resize_with(width * 3, Default::default); + reader.next_row_rgb(&mut buffer[..]).unwrap(); + + // Copy to result with an alpha component + let pixels = buffer + .chunks(3) + .map(|rgb| { + let mut rgb = rgb.to_vec(); + rgb.push(255); + rgb + }) + .flatten() + .collect::>(); + result.extend_from_slice(&pixels); + } + } + + (width, height, Rc::new(result)) +} + +/// Create a grid of invaders. +fn make_invader_grid(assets: &Assets) -> Vec>> { + const BLIPJOY_OFFSET: Point = Point::new(3, 4); + const FERRIS_OFFSET: Point = Point::new(3, 5); + + (0..1) + .map(|y| { + (0..11) + .map(|x| { + Some(Invader { + sprite: SpriteRef::new(assets, "blipjoy1"), + pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID, + score: 10, + }) + }) + .collect() + }) + .chain((1..3).map(|y| { + (0..11) + .map(|x| { + Some(Invader { + sprite: SpriteRef::new(assets, "ferris1"), + pos: START + FERRIS_OFFSET + Point::new(x, y) * GRID, + score: 10, + }) + }) + .collect() + })) + .chain((3..5).map(|y| { + (0..11) + .map(|x| { + Some(Invader { + // TODO: Need a third invader + sprite: SpriteRef::new(assets, "blipjoy1"), + pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID, + score: 10, + }) + }) + .collect() + })) + .collect() +} + +fn blit(screen: &mut [u8], dest: &Point, sprite: &S) +where + S: Sprites, +{ + let pixels = sprite.pixels(); + let width = sprite.width() * 4; + + let mut s = 0; + for y in 0..sprite.height() { + let i = dest.x * 4 + dest.y * SCREEN_WIDTH * 4 + y * SCREEN_WIDTH * 4; + screen[i..i + width].copy_from_slice(&pixels[s..s + width]); + s += width; + } +} + +#[cfg(test)] +mod tests { + use crate::*; + + #[test] + fn test_pcx() { + let pixels = load_pcx(include_bytes!("assets/blipjoy1.pcx")); + let expected = vec![ + 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, + 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, + 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, + 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, + 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, + 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, + 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, + 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, + 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, + 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, + 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, + 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, + ]; + + assert_eq!(pixels.0, 10, "Width differs"); + assert_eq!(pixels.1, 8, "Height differs"); + assert_eq!(Rc::try_unwrap(pixels.2).unwrap(), expected, "Pixels differ"); + } +}