hecrj / coffee

An opinionated 2D game engine for Rust
https://docs.rs/coffee
MIT License
1.09k stars 55 forks source link

Exporting as Image #89

Closed vinceniko closed 5 years ago

vinceniko commented 5 years ago

This is probably outside of the scope of the game engine, but is it possible to use the current target or frame as an image type from the Image crate? My goal is essentially to save the frame as an image file. Thanks

hecrj commented 5 years ago

This will be possible after we merge #80. The PR introduces a Canvas::read_pixels which returns a DynamicImage from the image crate, which you could then save as you wish.

If you need it right now, you could try using the test/graphics branch (there are minor breaking changes). I could also try to backport it and release a new patch version, but I'd prefer to avoid it if possible (the feature is still not tested well enough).

vinceniko commented 5 years ago

This is great work! When do you think you’ll merge it into master?

hecrj commented 5 years ago

I can try to merge it in a bit! It should be a matter of resolving conflicts and writing docs for the new Panel widget.

hecrj commented 5 years ago

Done! The changes should be now in master. Let me know if you have any issues!

EDIT: By the way, the current CHANGELOG is outdated. I will proceed to update it, so you at least have a guide to migrate.

vinceniko commented 5 years ago

Awesome, thanks! I won’t get a chance to test until some time, but will definitely let you know if there are issues

vinceniko commented 5 years ago

Would you provide some documentation on how to draw a mesh to a canvas within the Game::draw method? I'm not sure if its a bug, but I can't seem to save an image that has the content of the mesh, i.e. the image always comes out as solid black.

let width = frame.width() as u16;
            let height = frame.height() as u16;
            let gpu = frame.gpu();

            let mut canvas = Canvas::new(gpu, width, height).unwrap();
            mesh.draw(&mut canvas.as_target(gpu));

            let image = canvas.read_pixels(gpu);
            image.to_rgb().save("test.png").unwrap();

^This is my current code.

hecrj commented 5 years ago

Oh, that should work. I think I know what is going on: read_pixels needs to also flush the current draw operations (it isn't right now).

Could you try drawing on the canvas in one frame, and then reading the pixels and saving the image during the next one (i.e. in different draw calls)?

hecrj commented 5 years ago

I have been able to reproduce the issue and fix it in #92.

Let me know if it works, and we will merge it!

EDIT: By the way, remember to clear the canvas before drawing! You can use Target::clear. I wonder if we should automatically clear it on creation...

vinceniko commented 5 years ago

Awesome! My code seems to work now! And I believe it does clear on creation (happens every frame) since I don't explicitly call clear and no artifacts carry over between frames.

However, would it be more performant to create and draw to one canvas instance which would be used repeatedly across frame calls? I don't particularly need the performance for image exporting since the event occurs rarely, but I'm still curious.

hecrj commented 5 years ago

However, would it be more performant to create and draw to one canvas instance which would be used repeatedly across frame calls?

Are you exporting multiple frames in a row? In that case, yes. It would avoid allocating/deallocating a new texture every frame. However, if you want to keep the canvas size in sync, you will need to worry about recreating it whenever the window is resized. Also, be aware that exporting every frame can quickly perform a lot of write operations.

If you are exporting a single frame rarely as part of a UI interaction, creating the canvas on demand just to save it afterwards should be perfectly fine.