pub struct Image { /* private fields */ }Expand description
An RGBA8 image: sRGB-encoded, not premultiplied, tightly packed rows, top row first.
That is the layout that GPU texture uploads and most engines take, such as Diamon’s
TextureStore::register.
Implementations§
Source§impl Image
impl Image
Sourcepub fn new(width: u32, height: u32) -> Result<Self>
pub fn new(width: u32, height: u32) -> Result<Self>
A transparent image. Both sides must be at least 1 and within the default
Limits.
Examples found in repository?
examples/night_shift.rs (line 767)
766fn scale(image: &Image, n: u32) -> spritebox::Result<Image> {
767 let mut out = Image::new(image.width() * n, image.height() * n)?;
768 for y in 0..out.height() {
769 for x in 0..out.width() {
770 out.set_pixel(x, y, image.pixel(x / n, y / n).unwrap());
771 }
772 }
773 Ok(out)
774}
775fn effects(frame: i32) -> Canvas {
776 let mut c = Canvas::new(W, H, '.');
777 // Periodic rain; its entire state returns after exactly 96 frames.
778 for i in 0..225u32 {
779 let y = (pick(i * 29 + 2, 384) + frame * 4) % 384 - 42;
780 let x = (pick(i * 29 + 1, W) - y / 3).rem_euclid(W);
781 let len = 3 + pick(i * 29 + 3, 6);
782 c.line(
783 x,
784 y,
785 x - len / 3,
786 y + len,
787 if i % 7 == 0 { 't' } else { 's' },
788 );
789 }
790 // Broken puddle rings rather than a full, distracting reflection mirror.
791 for i in 0..27u32 {
792 let phase = (frame + pick(i + 401, 32)) % 32;
793 let x = 96 + pick(i + 500, 326);
794 let y = 225 + pick(i + 600, 56);
795 if phase < 16 {
796 let r = phase / 4 + 1;
797 c.line(x - r, y, x + r, y, if phase < 8 { 'g' } else { 'f' });
798 c.put(x - r - 1, y - 1, 'f');
799 c.put(x + r + 1, y - 1, 'f');
800 }
801 }
802 // Slow wisps escaping the restaurant pot; offsets wrap out of sight.
803 for i in 0..5 {
804 let t = (frame + i * 19) % FRAMES;
805 let y = 234 - t / 5;
806 let x = 389 + ((t as f64 * 0.15).sin() * 3.) as i32;
807 c.ellipse(x, y, 1 + t / 32, 2, 'u');
808 }
809 // Lantern light and a subtle heartbeat in the cyan sign.
810 c.ellipse(321, 219, 10, 13, 'v');
811 if frame % 48 < 43 {
812 c.rect(76, 91, 1, 75, 'w');
813 c.rect(98, 91, 2, 75, 'w');
814 }
815 c.rect(12, 284, 112, 1, 'g');
816 c.text(13, 289, "NIGHT SHIFT", 'p', 1);
817 c.text(407, 289, "02:17", 'h', 1);
818 c
819}
820
821fn reflection(frame: i32) -> Canvas {
822 let mut c = Canvas::new(W, H, '.');
823 let x = (frame * 8 + 396) % 768 - 280;
824 for row in 0..7 {
825 let yy = 197 + row * 7;
826 for xx in (x.max(98)..(x + 260).min(417)).step_by(11) {
827 c.rect(
828 xx,
829 yy,
830 3 + row,
831 1,
832 if row < 2 {
833 'x'
834 } else if row < 4 {
835 'y'
836 } else {
837 'z'
838 },
839 );
840 }
841 }
842 c
843}
844
845fn render_frame(
846 frame: i32,
847 city: &Image,
848 train: &Image,
849 street: &Image,
850 palette: &str,
851) -> Result<Image, Box<dyn std::error::Error>> {
852 let mut image = city.clone();
853 overlay(
854 &mut image,
855 &render_canvas(&reflection(frame), palette)?,
856 0,
857 0,
858 );
859 overlay(&mut image, train, (frame * 8 + 396) % 768 - 280, 145);
860 overlay(&mut image, street, 0, 0);
861 overlay(&mut image, &render_canvas(&effects(frame), palette)?, 0, 0);
862 Ok(image)
863}
864fn source_header() -> String {
865 let mut s = String::from(
866 "; NIGHT SHIFT / an original spritebox scene.\n; All shapes are authored procedurally, without a reference bitmap.\npalette night\n",
867 );
868 for (key, value) in PALETTE {
869 writeln!(s, " {key} {value}").unwrap();
870 }
871 s
872}
873fn render_canvas(canvas: &Canvas, palette: &str) -> Result<Image, Box<dyn std::error::Error>> {
874 let mut s = palette.to_string();
875 canvas.source("effect", &mut s);
876 let d = Document::parse(&s)?;
877 Ok(d.sprite("effect").unwrap().render())
878}
879
880fn main() -> Result<(), Box<dyn std::error::Error>> {
881 let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
882 let out = root.join("out/night-shift");
883 let frames = out.join("frames");
884 std::fs::create_dir_all(&frames)?;
885 std::fs::create_dir_all(root.join("examples/night_shift"))?;
886 let palette = source_header();
887 let mut source = palette.clone();
888 background().source("city", &mut source);
889 train().source("last_train", &mut source);
890 frontage().source("street", &mut source);
891 // A clean, editable hero frame uses spritebox's native layer composition.
892 let hero_x = 116;
893 let mut train_hero = Canvas::new(W, H, '.');
894 let train_pixels = train();
895 for y in 0..train_pixels.h {
896 for x in 0..train_pixels.w {
897 train_hero.put(x + hero_x, y + 145, train_pixels.get(x, y));
898 }
899 }
900 train_hero.source("train_at_station", &mut source);
901 reflection(0).source("reflections", &mut source);
902 effects(0).source("rain", &mut source);
903 source.push_str("\nsprite night_shift 480x300 palette=night\n layer city = city\n layer reflections = reflections\n layer train = train_at_station\n layer street = street\n layer atmosphere = rain\n");
904 let art_path = root.join("examples/night_shift/night_shift.spritebox");
905 std::fs::write(&art_path, &source)?;
906 let doc = Document::parse(&source)?;
907 let city = doc.sprite("city").unwrap().render();
908 let train = doc.sprite("last_train").unwrap().render();
909 let street = doc.sprite("street").unwrap().render();
910 let hero = doc.sprite("night_shift").unwrap().render();
911 let first = render_frame(0, &city, &train, &street, &palette)?;
912 if hero != first {
913 return Err("The editable layered sprite differs from animation frame zero".into());
914 }
915 if first != render_frame(FRAMES, &city, &train, &street, &palette)? {
916 return Err("The animation does not close seamlessly".into());
917 }
918 png::write_file(out.join("night-shift-native.png"), &hero)?;
919 png::write_file(out.join("night-shift.png"), &scale(&hero, 3)?)?;
920 let mut sheet = Image::new(W as u32 * 2, H as u32 * 2)?;
921 for f in 0..FRAMES {
922 let image = render_frame(f, &city, &train, &street, &palette)?;
923 png::write_file(frames.join(format!("{f:03}.png")), &scale(&image, 3)?)?;
924 if f == 0 {
925 png::write_file(out.join("night-shift.png"), &scale(&image, 3)?)?;
926 png::write_file(out.join("night-shift-native.png"), &image)?;
927 }
928 if f % 24 == 0 {
929 sheet.copy_from(&image, i64::from((f / 24) % 2 * W), i64::from((f / 48) * H));
930 println!("Rendered {f}/{FRAMES} frames");
931 }
932 }
933 png::write_file(out.join("contact-sheet.png"), &sheet)?;
934 if png::read_file(out.join("night-shift-native.png"))? != hero {
935 return Err("PNG round-trip mismatch".into());
936 }
937 // Keep a transparent sheet of useful original assets as well as the scene.
938 png::write_file(out.join("train.png"), &scale(&train, 4)?)?;
939 println!("Art: {}\nFrames: {}", art_path.display(), frames.display());
940 Ok(())
941}Sourcepub fn new_with(width: u32, height: u32, limits: &Limits) -> Result<Self>
pub fn new_with(width: u32, height: u32, limits: &Limits) -> Result<Self>
A transparent image within limits.
Sourcepub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self>
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self>
An image of pixels, which must hold width * height * 4 bytes.
Sourcepub fn width(&self) -> u32
pub fn width(&self) -> u32
Examples found in repository?
examples/night_shift.rs (line 753)
747fn overlay(image: &mut Image, source: &Image, x: i32, y: i32) {
748 for sy in 0..source.height() as i32 {
749 let yy = sy + y;
750 if yy < 0 || yy >= image.height() as i32 {
751 continue;
752 }
753 for sx in 0..source.width() as i32 {
754 let xx = sx + x;
755 if xx < 0 || xx >= image.width() as i32 {
756 continue;
757 }
758 let col = source.pixel(sx as u32, sy as u32).unwrap();
759 if col.a > 0 {
760 let under = image.pixel(xx as u32, yy as u32).unwrap();
761 image.set_pixel(xx as u32, yy as u32, col.over(under));
762 }
763 }
764 }
765}
766fn scale(image: &Image, n: u32) -> spritebox::Result<Image> {
767 let mut out = Image::new(image.width() * n, image.height() * n)?;
768 for y in 0..out.height() {
769 for x in 0..out.width() {
770 out.set_pixel(x, y, image.pixel(x / n, y / n).unwrap());
771 }
772 }
773 Ok(out)
774}Sourcepub fn height(&self) -> u32
pub fn height(&self) -> u32
Examples found in repository?
examples/night_shift.rs (line 748)
747fn overlay(image: &mut Image, source: &Image, x: i32, y: i32) {
748 for sy in 0..source.height() as i32 {
749 let yy = sy + y;
750 if yy < 0 || yy >= image.height() as i32 {
751 continue;
752 }
753 for sx in 0..source.width() as i32 {
754 let xx = sx + x;
755 if xx < 0 || xx >= image.width() as i32 {
756 continue;
757 }
758 let col = source.pixel(sx as u32, sy as u32).unwrap();
759 if col.a > 0 {
760 let under = image.pixel(xx as u32, yy as u32).unwrap();
761 image.set_pixel(xx as u32, yy as u32, col.over(under));
762 }
763 }
764 }
765}
766fn scale(image: &Image, n: u32) -> spritebox::Result<Image> {
767 let mut out = Image::new(image.width() * n, image.height() * n)?;
768 for y in 0..out.height() {
769 for x in 0..out.width() {
770 out.set_pixel(x, y, image.pixel(x / n, y / n).unwrap());
771 }
772 }
773 Ok(out)
774}pub fn into_pixels(self) -> Vec<u8> ⓘ
Sourcepub fn pixel(&self, x: u32, y: u32) -> Option<Color>
pub fn pixel(&self, x: u32, y: u32) -> Option<Color>
The color at (x, y), or None outside the image.
Examples found in repository?
examples/night_shift.rs (line 758)
747fn overlay(image: &mut Image, source: &Image, x: i32, y: i32) {
748 for sy in 0..source.height() as i32 {
749 let yy = sy + y;
750 if yy < 0 || yy >= image.height() as i32 {
751 continue;
752 }
753 for sx in 0..source.width() as i32 {
754 let xx = sx + x;
755 if xx < 0 || xx >= image.width() as i32 {
756 continue;
757 }
758 let col = source.pixel(sx as u32, sy as u32).unwrap();
759 if col.a > 0 {
760 let under = image.pixel(xx as u32, yy as u32).unwrap();
761 image.set_pixel(xx as u32, yy as u32, col.over(under));
762 }
763 }
764 }
765}
766fn scale(image: &Image, n: u32) -> spritebox::Result<Image> {
767 let mut out = Image::new(image.width() * n, image.height() * n)?;
768 for y in 0..out.height() {
769 for x in 0..out.width() {
770 out.set_pixel(x, y, image.pixel(x / n, y / n).unwrap());
771 }
772 }
773 Ok(out)
774}Sourcepub fn set_pixel(&mut self, x: u32, y: u32, color: Color)
pub fn set_pixel(&mut self, x: u32, y: u32, color: Color)
Sets the color at (x, y). Outside the image, does nothing.
Examples found in repository?
examples/night_shift.rs (line 761)
747fn overlay(image: &mut Image, source: &Image, x: i32, y: i32) {
748 for sy in 0..source.height() as i32 {
749 let yy = sy + y;
750 if yy < 0 || yy >= image.height() as i32 {
751 continue;
752 }
753 for sx in 0..source.width() as i32 {
754 let xx = sx + x;
755 if xx < 0 || xx >= image.width() as i32 {
756 continue;
757 }
758 let col = source.pixel(sx as u32, sy as u32).unwrap();
759 if col.a > 0 {
760 let under = image.pixel(xx as u32, yy as u32).unwrap();
761 image.set_pixel(xx as u32, yy as u32, col.over(under));
762 }
763 }
764 }
765}
766fn scale(image: &Image, n: u32) -> spritebox::Result<Image> {
767 let mut out = Image::new(image.width() * n, image.height() * n)?;
768 for y in 0..out.height() {
769 for x in 0..out.width() {
770 out.set_pixel(x, y, image.pixel(x / n, y / n).unwrap());
771 }
772 }
773 Ok(out)
774}Sourcepub fn copy_from(&mut self, source: &Image, x: i64, y: i64)
pub fn copy_from(&mut self, source: &Image, x: i64, y: i64)
Copies source with its top-left corner at (x, y), replacing pixels and
clipping to this image.
Examples found in repository?
examples/night_shift.rs (line 929)
880fn main() -> Result<(), Box<dyn std::error::Error>> {
881 let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
882 let out = root.join("out/night-shift");
883 let frames = out.join("frames");
884 std::fs::create_dir_all(&frames)?;
885 std::fs::create_dir_all(root.join("examples/night_shift"))?;
886 let palette = source_header();
887 let mut source = palette.clone();
888 background().source("city", &mut source);
889 train().source("last_train", &mut source);
890 frontage().source("street", &mut source);
891 // A clean, editable hero frame uses spritebox's native layer composition.
892 let hero_x = 116;
893 let mut train_hero = Canvas::new(W, H, '.');
894 let train_pixels = train();
895 for y in 0..train_pixels.h {
896 for x in 0..train_pixels.w {
897 train_hero.put(x + hero_x, y + 145, train_pixels.get(x, y));
898 }
899 }
900 train_hero.source("train_at_station", &mut source);
901 reflection(0).source("reflections", &mut source);
902 effects(0).source("rain", &mut source);
903 source.push_str("\nsprite night_shift 480x300 palette=night\n layer city = city\n layer reflections = reflections\n layer train = train_at_station\n layer street = street\n layer atmosphere = rain\n");
904 let art_path = root.join("examples/night_shift/night_shift.spritebox");
905 std::fs::write(&art_path, &source)?;
906 let doc = Document::parse(&source)?;
907 let city = doc.sprite("city").unwrap().render();
908 let train = doc.sprite("last_train").unwrap().render();
909 let street = doc.sprite("street").unwrap().render();
910 let hero = doc.sprite("night_shift").unwrap().render();
911 let first = render_frame(0, &city, &train, &street, &palette)?;
912 if hero != first {
913 return Err("The editable layered sprite differs from animation frame zero".into());
914 }
915 if first != render_frame(FRAMES, &city, &train, &street, &palette)? {
916 return Err("The animation does not close seamlessly".into());
917 }
918 png::write_file(out.join("night-shift-native.png"), &hero)?;
919 png::write_file(out.join("night-shift.png"), &scale(&hero, 3)?)?;
920 let mut sheet = Image::new(W as u32 * 2, H as u32 * 2)?;
921 for f in 0..FRAMES {
922 let image = render_frame(f, &city, &train, &street, &palette)?;
923 png::write_file(frames.join(format!("{f:03}.png")), &scale(&image, 3)?)?;
924 if f == 0 {
925 png::write_file(out.join("night-shift.png"), &scale(&image, 3)?)?;
926 png::write_file(out.join("night-shift-native.png"), &image)?;
927 }
928 if f % 24 == 0 {
929 sheet.copy_from(&image, i64::from((f / 24) % 2 * W), i64::from((f / 48) * H));
930 println!("Rendered {f}/{FRAMES} frames");
931 }
932 }
933 png::write_file(out.join("contact-sheet.png"), &sheet)?;
934 if png::read_file(out.join("night-shift-native.png"))? != hero {
935 return Err("PNG round-trip mismatch".into());
936 }
937 // Keep a transparent sheet of useful original assets as well as the scene.
938 png::write_file(out.join("train.png"), &scale(&train, 4)?)?;
939 println!("Art: {}\nFrames: {}", art_path.display(), frames.display());
940 Ok(())
941}Trait Implementations§
impl Eq for Image
impl StructuralPartialEq for Image
Auto Trait Implementations§
impl Freeze for Image
impl RefUnwindSafe for Image
impl Send for Image
impl Sync for Image
impl Unpin for Image
impl UnsafeUnpin for Image
impl UnwindSafe for Image
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more