Skip to main content

Image

Struct Image 

Source
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

Source

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}
Source

pub fn new_with(width: u32, height: u32, limits: &Limits) -> Result<Self>

A transparent image within limits.

Source

pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self>

An image of pixels, which must hold width * height * 4 bytes.

Source

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}
Source

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}
Source

pub fn pixels(&self) -> &[u8] ⓘ

The RGBA8 bytes, width * height * 4 of them.

Source

pub fn into_pixels(self) -> Vec<u8> ⓘ

Source

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}
Source

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}
Source

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}
Source

pub fn to_png(&self) -> Vec<u8> ⓘ

Encodes the image as a PNG file. See png::encode.

Source§

impl Image

Source

pub fn over_from(&mut self, source: &Image, x: i64, y: i64)

Draw source-over at signed coordinates, clipping to this image.

Source

pub fn scaled_with(&self, scale: u32, limits: &Limits) -> Result<Image>

Nearest-neighbour integer scaling within explicit image limits.

Trait Implementations§

Source§

impl Clone for Image

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Image

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Image

Source§

impl PartialEq for Image

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.