Skip to content

Commit

Permalink
Merge pull request ggez#754 from Wumpf/fix-screenshots
Browse files Browse the repository at this point in the history
Fixed upside-down screenshot & report error for antialiased Screenshot
  • Loading branch information
icefoxen authored Apr 3, 2020
2 parents 89425ce + 6faf916 commit 84935d7
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 24 deletions.
14 changes: 11 additions & 3 deletions src/graphics/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,18 @@ impl Image {
let reader = gfx.factory.read_mapping(&dl_buffer)?;

// intermediary buffer to avoid casting.
// Apparently this also at one point made the screenshot upside-down,
// but no longer?
let mut data = Vec::with_capacity(self.width as usize * self.height as usize * 4);
data.extend(reader.into_iter().flatten());
// Assuming OpenGL backend whose typical readback option (glReadPixels) has origin at bottom left.
// Image formats on the other hand usually deal with top right.
for y in (0..self.height as usize).rev() {
data.extend(
reader
.iter()
.skip(y * self.width as usize)
.take(self.width as usize)
.flatten(),
);
}
Ok(data)
}

Expand Down
9 changes: 5 additions & 4 deletions src/graphics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,14 +504,16 @@ pub fn present(ctx: &mut Context) -> GameResult<()> {
/// Take a screenshot by outputting the current render surface
/// (screen or selected canvas) to an `Image`.
pub fn screenshot(ctx: &mut Context) -> GameResult<Image> {
// TODO LATER: This makes the screenshot upside-down form some reason...
// Probably because all our images are upside down, for coordinate reasons!
// How can we fix it?
use gfx::memory::Bind;
let debug_id = DebugId::get(ctx);

let gfx = &mut ctx.gfx_context;
let (w, h, _depth, aa) = gfx.data.out.get_dimensions();
if aa != gfx_core::texture::AaMode::Single {
// Details see https://github.com/ggez/ggez/issues/751
return Err(GameError::RenderError("Can't take screenshots of anti aliased textures.\n(since neither copying or resolving them is supported right now)".to_string()));
}

let surface_format = gfx.color_format();
let gfx::format::Format(surface_type, channel_type) = surface_format;

Expand Down Expand Up @@ -1059,5 +1061,4 @@ mod tests {
assert_relative_eq!(real, expected);
}
}

}
107 changes: 93 additions & 14 deletions src/tests/graphics.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::graphics::Color;
use crate::tests;
use crate::*;
use cgmath::Point2;

// use std::path;

Expand All @@ -12,30 +14,107 @@ fn image_encode() {
.unwrap();
}

#[test]
fn save_screenshot() {
let (c, _e) = &mut tests::make_context();
graphics::clear(c, graphics::Color::new(0.1, 0.2, 0.3, 1.0));
// Draw a triangle on it so you can see which way is right-side up.
let tri_mesh = graphics::Mesh::new_polygon(
fn get_rgba_sample(rgba_buf: &[u8], width: usize, sample_pos: Point2<f32>) -> (u8, u8, u8, u8) {
(
rgba_buf[(width * sample_pos.y as usize + sample_pos.x as usize) * 4 + 0],
rgba_buf[(width * sample_pos.y as usize + sample_pos.x as usize) * 4 + 1],
rgba_buf[(width * sample_pos.y as usize + sample_pos.x as usize) * 4 + 2],
rgba_buf[(width * sample_pos.y as usize + sample_pos.x as usize) * 4 + 3],
)
}

fn save_screenshot_test(c: &mut Context) {
graphics::clear(c, Color::new(0.1, 0.2, 0.3, 1.0));

let width = graphics::drawable_size(c).0;
let height = graphics::drawable_size(c).1;

let topleft = graphics::DrawParam::new()
.color(graphics::WHITE)
.dest(Point2::new(0.0, 0.0));
let topright = graphics::DrawParam::new()
.color(Color::new(1.0, 0.0, 0.0, 1.0))
.dest(Point2::new(width / 2.0, 0.0));
let bottomleft = graphics::DrawParam::new()
.color(Color::new(0.0, 1.0, 0.0, 1.0))
.dest(Point2::new(0.0, height / 2.0));
let bottomright = graphics::DrawParam::new()
.color(Color::new(0.0, 0.0, 1.0, 1.0))
.dest(Point2::new(width / 2.0, height / 2.0));

let rect = graphics::Mesh::new_rectangle(
c,
graphics::DrawMode::stroke(10.0),
&[
graphics::Point2::new(100.0, 100.0),
graphics::Point2::new(100.0, 200.0),
graphics::Point2::new(200.0, 100.0),
],
graphics::DrawMode::fill(),
graphics::types::Rect {
x: 0.0,
y: 0.0,
w: width / 2.0,
h: height / 2.0,
},
graphics::WHITE,
)
.unwrap();
graphics::draw(c, &tri_mesh, graphics::DrawParam::default()).unwrap();
graphics::present(c).unwrap();

graphics::draw(c, &rect, topleft).unwrap();
graphics::draw(c, &rect, topright).unwrap();
graphics::draw(c, &rect, bottomleft).unwrap();
graphics::draw(c, &rect, bottomright).unwrap();

// Don't do graphics::present(c) since calling it once (!) would mean that the result of our draw operation
// went to the front buffer and the active screen texture is actually empty.
c.gfx_context.encoder.flush(&mut *c.gfx_context.device);

let screenshot = graphics::screenshot(c).unwrap();

// Check if screenshot has right general properties
assert_eq!(width as u16, screenshot.width);
assert_eq!(height as u16, screenshot.height);
assert_eq!(None, screenshot.blend_mode);

// Image comparision or rendered output is hard, but we *know* that top left should be white.
// So take a samples in the middle of each rectangle we drew and compare.
// Note that we only use fully saturated colors to avoid any issues with color spaces.
let rgba_buf = screenshot.to_rgba8(c).unwrap();
let half_rect = cgmath::Vector2::new(width / 4.0, height / 4.0);
let width = width as usize;
assert_eq!(
topleft.color.to_rgba(),
get_rgba_sample(&rgba_buf, width, Point2::from(topleft.dest) + half_rect)
);
assert_eq!(
topright.color.to_rgba(),
get_rgba_sample(&rgba_buf, width, Point2::from(topright.dest) + half_rect)
);
assert_eq!(
bottomleft.color.to_rgba(),
get_rgba_sample(&rgba_buf, width, Point2::from(bottomleft.dest) + half_rect)
);
assert_eq!(
bottomright.color.to_rgba(),
get_rgba_sample(&rgba_buf, width, Point2::from(bottomright.dest) + half_rect)
);

// save screenshot (no check, just to see if it doesn't crash)
screenshot
.encode(c, graphics::ImageFormat::Png, "/screenshot_test.png")
.unwrap();
}

#[test]
fn save_screenshot() {
let (c, _e) = &mut tests::make_context();
save_screenshot_test(c);
}

// Not supported, see https://github.com/ggez/ggez/issues/751
// #[test]
// fn save_screenshot_with_antialiasing() {
// let cb = ContextBuilder::new("ggez_unit_tests", "ggez")
// .window_setup(conf::WindowSetup::default().samples(conf::NumSamples::Eight));
// let (c, _e) = &mut tests::make_context_from_contextbuilder(cb);
// save_screenshot_test(c);
// }

#[test]
fn load_images() {
let (c, _e) = &mut tests::make_context();
Expand Down
10 changes: 7 additions & 3 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ mod graphics;
mod mesh;
mod text;

/// Make a basic `Context` with sane defaults.
pub fn make_context() -> (Context, event::EventsLoop) {
let mut cb = ContextBuilder::new("ggez_unit_tests", "ggez");
pub fn make_context_from_contextbuilder(mut cb: ContextBuilder) -> (Context, event::EventsLoop) {
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let mut path = path::PathBuf::from(manifest_dir);
path.push("resources");
cb = cb.add_resource_path(path);
}
cb.build().unwrap()
}

/// Make a basic `Context` with sane defaults.
pub fn make_context() -> (Context, event::EventsLoop) {
let cb = ContextBuilder::new("ggez_unit_tests", "ggez");
make_context_from_contextbuilder(cb)
}

0 comments on commit 84935d7

Please sign in to comment.