forked from ggez/ggez
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcolorspace.rs
162 lines (150 loc) · 6.5 KB
/
colorspace.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! 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!
use ggez;
use ggez::event;
use ggez::graphics::{self, DrawParam};
use ggez::nalgebra as na;
use ggez::{Context, GameResult};
/// 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,
}
impl MainState {
fn new(ctx: &mut Context) -> GameResult<MainState> {
let demo_mesh = graphics::Mesh::new_circle(
ctx,
graphics::DrawMode::fill(),
na::Point2::new(0.0, 0.0),
100.0,
2.0,
AQUA,
)?;
let square_mesh = graphics::Mesh::new_rectangle(
ctx,
graphics::DrawMode::fill(),
graphics::Rect::new(0.0, 0.0, 400.0, 400.0),
graphics::WHITE,
)?;
let demo_image = graphics::Image::solid(ctx, 200, AQUA)?;
let s = MainState {
demo_mesh,
square_mesh,
demo_image,
};
Ok(s)
}
}
impl event::EventHandler for MainState {
fn update(&mut self, _ctx: &mut Context) -> GameResult {
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, AQUA);
// Draw a white square so we can see things
graphics::draw(
ctx,
&self.square_mesh,
DrawParam::default().dest(na::Point2::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.
graphics::draw(
ctx,
&self.demo_mesh,
DrawParam::default().dest(na::Point2::new(150.0, 200.0)),
)?;
graphics::draw(
ctx,
&self.demo_image,
DrawParam::default().dest(na::Point2::new(450.0, 200.0)),
)?;
graphics::present(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")
};
let cb = ggez::ContextBuilder::new("super_simple", "ggez").add_resource_path(resource_dir);
let (ctx, event_loop) = &mut cb.build()?;
let state = &mut MainState::new(ctx)?;
event::run(ctx, event_loop, state)
}