//! An example demonstrating sRGB color spaces. //! //! sRGB is one of those things that's a bit subtle, a bit obscure, //! and easy to get wrong, but worth knowing because it crops up //! from time to time and makes things mysteriously "look wrong" in //! some cases. It also is sort of overloaded to do two things at //! once. The first is what you will find [Wikipedia talking about](https://en.wikipedia.org/wiki/Srgb), //! which is "how do you prove that 'green' on my monitor is the same //! as 'green' on your monitor?". That part we can safely ignore, //! since it's a job for your monitor manufacturer. //! //! The other part is [gamma //! correction](https://en.wikipedia.org/wiki/Gamma_correction) which //! deals with the fact that the response of the human visual system //! is non-linear, or in non-science talk, if you make a pixel twice //! as bright, it doesn't *look* twice as bright. To make something //! *look* twice as bright, you have to make it about 2^2.2 times //! brighter. The exact math for how to fiddle the numbers to make //! things Look Nice is defined as part of the sRGB standard, so often //! a color scheme that complies with this gamma correction process is //! just called "sRGB". //! //! In a perfect world, we don't ever have to worry about this. //! Images are generally all stored in the sRGB color space (or //! something similar to it), monitors all display in the sRGB color //! space, and our graphics drivers know how to do whatever is //! necessary to make the two line up. The problem comes because we //! are programmers, and have to be able to poke things instead of //! just using pre-loaded assets. So the question is: if you do //! `Color::new(0.5, 0.0, 0.0, 1.0)` and `Color::new(1.0, 0.0, 0.0, 1.0)`, //! will the second color LOOK twice as bright as the first one? //! //! So we have to know what color space we are talking about when we //! say `Color`! Are we talking about linear, "real" color where your //! pixel puts out twice as many photons for the second color as the //! first? Or are we talking about sRGB color, where the pixel //! actually LOOKS twice as bright? And if we want to, say, write a //! shader that does math to these colors, AND to colors that come //! from images that use the sRGB color space, how do we make sure //! everything matches? To make it even worse, the sRGB conversion //! done by graphics drivers is toggle-able, and can be set on a //! per-render-target or per-texture basis, so it's possible for //! things to get REAL mixed up in subtle ways. //! //! The Right Answer, as far as I know, is this: All colors that a //! human specifies or touches are sRGB-encoded, so a number that is //! twice as big then . We do our color math in shaders, and (if we //! set our render target to be an sRGB texture, which ggez always //! does) the graphics driver will turn the linear colors we specify //! into sRGB colors. So if we do `vec4 x = vec4(0.25, 0.0, 0.0, 1.0);`, //! assigning one pixel `x` and another `x * 2` will make the //! second one *look* twice as bright as the first. //! //! BUT, this process also has to be done on INPUT as well; if we pass //! the shader a value taken from an image file, that image file is in //! sRGB color. The graphics driver must THEN convert the sRGB color into //! a linear color when passing it to the shader, so that if we get the //! color and call it `x`, assigning one output pixel `x` and another //! `x * 2` again makes the second one LOOK twice as bright as the first. //! Then it converts the value back on the way out. //! //! ggez should handle all of this for you. `graphics::Color` is //! explicitly a sRGB-corrected color, all textures including the //! final render target are sRGB-enabled, and when you provide //! a linear color to something like `graphics::Mesh` it turns it //! into sRGB for you to match everything else. The purpose of this //! example is to show that this actually *works* correctly! extern crate good_web_game as ggez; use ggez::event; use ggez::graphics::{self, Color, DrawParam}; use ggez::miniquad; use ggez::{Context, GameResult}; use glam::*; /// This is a nice aqua test color that will look a lot brighter /// than it should if we mess something up. /// See https://github.com/ggez/ggez/issues/209 for examples. const AQUA: graphics::Color = graphics::Color::new(0.0078, 0.7647, 0.6039, 1.0); struct MainState { demo_mesh: graphics::Mesh, square_mesh: graphics::Mesh, demo_image: graphics::Image, demo_text: graphics::Text, demo_spritebatch: graphics::spritebatch::SpriteBatch, } impl MainState { fn new(ctx: &mut Context, quad_ctx: &mut miniquad::GraphicsContext) -> GameResult { let demo_mesh = graphics::Mesh::new_circle( ctx, quad_ctx, graphics::DrawMode::fill(), Vec2::new(0.0, 0.0), 100.0, 2.0, AQUA, )?; let square_mesh = graphics::Mesh::new_rectangle( ctx, quad_ctx, graphics::DrawMode::fill(), graphics::Rect::new(0.0, 0.0, 400.0, 400.0), Color::WHITE, )?; let demo_image = graphics::Image::solid(ctx, quad_ctx, 200, AQUA)?; let demo_text = graphics::Text::new(graphics::TextFragment { text: "-".to_string(), color: Some(AQUA), font: Some(graphics::Font::default()), scale: Some(graphics::PxScale::from(300.0)), }); let demo_spritebatch = graphics::spritebatch::SpriteBatch::new(demo_image.clone()); let s = MainState { demo_mesh, square_mesh, demo_image, demo_text, demo_spritebatch, }; Ok(s) } } impl event::EventHandler for MainState { fn update( &mut self, _ctx: &mut Context, _quad_ctx: &mut miniquad::GraphicsContext, ) -> GameResult { Ok(()) } fn draw(&mut self, ctx: &mut Context, quad_ctx: &mut miniquad::GraphicsContext) -> GameResult { graphics::clear(ctx, quad_ctx, AQUA); // Draw a white square so we can see things graphics::draw( ctx, quad_ctx, &self.square_mesh, DrawParam::default().dest(Vec2::new(200.0, 100.0)), )?; // Draw things partially over the white square so we can see // where they are; they SHOULD be the same color as the // background. // mesh graphics::draw( ctx, quad_ctx, &self.demo_mesh, DrawParam::default().dest(Vec2::new(150.0, 200.0)), )?; // image graphics::draw( ctx, quad_ctx, &self.demo_image, DrawParam::default().dest(Vec2::new(450.0, 200.0)), )?; // text graphics::draw( ctx, quad_ctx, &self.demo_text, DrawParam::default().dest(Vec2::new(150.0, 135.0)), )?; // spritebatch self.demo_spritebatch.add( DrawParam::default() .dest(Vec2::new(250.0, 350.0)) .scale(Vec2::new(0.25, 0.25)), ); self.demo_spritebatch.add( DrawParam::default() .dest(Vec2::new(250.0, 425.0)) .scale(Vec2::new(0.1, 0.1)), ); graphics::draw( ctx, quad_ctx, &self.demo_spritebatch, DrawParam::default().dest(Vec2::new(0.0, 0.0)), )?; self.demo_spritebatch.clear(); graphics::present(ctx, quad_ctx)?; Ok(()) } } pub fn main() -> GameResult { use std::env; use std::path; let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { let mut path = path::PathBuf::from(manifest_dir); path.push("resources"); path } else { path::PathBuf::from("./resources") }; ggez::start( ggez::conf::Conf::default() .cache(Some(include_bytes!("resources.tar"))) .physical_root_dir(Some(resource_dir)), |mut context, quad_ctx| Box::new(MainState::new(&mut context, quad_ctx).unwrap()), ) }