1 | // Aseprite |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/modules/gfx.h" |
13 | |
14 | #include "app/app.h" |
15 | #include "app/color_spaces.h" |
16 | #include "app/color_utils.h" |
17 | #include "app/console.h" |
18 | #include "app/modules/editors.h" |
19 | #include "app/modules/gui.h" |
20 | #include "app/modules/palettes.h" |
21 | #include "app/site.h" |
22 | #include "app/ui/editor/editor.h" |
23 | #include "app/ui/skin/skin_theme.h" |
24 | #include "app/util/conversion_to_surface.h" |
25 | #include "doc/blend_funcs.h" |
26 | #include "doc/image.h" |
27 | #include "doc/palette.h" |
28 | #include "gfx/color.h" |
29 | #include "gfx/point.h" |
30 | #include "gfx/rect.h" |
31 | #include "os/surface.h" |
32 | #include "os/system.h" |
33 | #include "ui/intern.h" |
34 | #include "ui/system.h" |
35 | #include "ui/theme.h" |
36 | |
37 | #include <algorithm> |
38 | |
39 | namespace app { |
40 | |
41 | using namespace app::skin; |
42 | using namespace gfx; |
43 | |
44 | namespace { |
45 | |
46 | void draw_checkered_grid(ui::Graphics* g, |
47 | const gfx::Rect& rc, |
48 | const gfx::Size& tile, |
49 | const gfx::Color c1, |
50 | const gfx::Color c2) |
51 | { |
52 | if (tile.w < 1 || tile.h < 1) |
53 | return; |
54 | |
55 | int x, y, u, v; |
56 | |
57 | u = 0; |
58 | v = 0; |
59 | for (y=rc.y; y<rc.y2()-tile.h; y+=tile.h) { |
60 | for (x=rc.x; x<rc.x2()-tile.w; x+=tile.w) |
61 | g->fillRect(((u++)&1)? c1: c2, gfx::Rect(x, y, tile.w, tile.h)); |
62 | |
63 | if (x < rc.x2()) |
64 | g->fillRect(((u++)&1)? c1: c2, gfx::Rect(x, y, rc.x2()-x, tile.h)); |
65 | |
66 | u = (++v); |
67 | } |
68 | |
69 | if (y < rc.y2()) { |
70 | for (x=rc.x; x<rc.x2()-tile.w; x+=tile.w) |
71 | g->fillRect(((u++)&1)? c1: c2, gfx::Rect(x, y, tile.w, rc.y2()-y)); |
72 | |
73 | if (x < rc.x2()) |
74 | g->fillRect(((u++)&1)? c1: c2, gfx::Rect(x, y, rc.x2()-x, rc.y2()-y)); |
75 | } |
76 | } |
77 | |
78 | } // anonymous namespace |
79 | |
80 | gfx::Color grid_color1() |
81 | { |
82 | if (ui::is_ui_thread() && current_editor) |
83 | return color_utils::color_for_ui(current_editor->docPref().bg.color1()); |
84 | else |
85 | return gfx::rgba(128, 128, 128); |
86 | } |
87 | |
88 | gfx::Color grid_color2() |
89 | { |
90 | if (ui::is_ui_thread() && current_editor) |
91 | return color_utils::color_for_ui(current_editor->docPref().bg.color2()); |
92 | else |
93 | return gfx::rgba(192, 192, 192); |
94 | } |
95 | |
96 | void draw_checkered_grid(ui::Graphics* g, |
97 | const gfx::Rect& rc, |
98 | const gfx::Size& tile) |
99 | { |
100 | draw_checkered_grid(g, rc, tile, grid_color1(), grid_color2()); |
101 | } |
102 | |
103 | void draw_checkered_grid(ui::Graphics* g, |
104 | const gfx::Rect& rc, |
105 | const gfx::Size& tile, |
106 | DocumentPreferences& docPref) |
107 | { |
108 | draw_checkered_grid(g, rc, tile, grid_color1(), grid_color2()); |
109 | } |
110 | |
111 | void draw_color(ui::Graphics* g, |
112 | const Rect& rc, |
113 | const app::Color& _color, |
114 | const doc::ColorMode colorMode) |
115 | { |
116 | if (rc.w < 1 || rc.h < 1) |
117 | return; |
118 | |
119 | app::Color color = _color; |
120 | const int alpha = color.getAlpha(); |
121 | |
122 | // Color space conversion |
123 | auto convertColor = convert_from_current_to_screen_color_space(); |
124 | |
125 | if (alpha < 255) { |
126 | if (rc.w == rc.h) |
127 | draw_checkered_grid(g, rc, gfx::Size(rc.w/2, rc.h/2)); |
128 | else |
129 | draw_checkered_grid(g, rc, gfx::Size(rc.w/4, rc.h/2)); |
130 | } |
131 | |
132 | if (alpha > 0) { |
133 | if (colorMode == doc::ColorMode::GRAYSCALE) { |
134 | color = app::Color::fromGray( |
135 | color.getGray(), |
136 | color.getAlpha()); |
137 | } |
138 | |
139 | if (color.getType() == app::Color::IndexType) { |
140 | int index = color.getIndex(); |
141 | |
142 | if (index >= 0 && index < get_current_palette()->size()) { |
143 | g->fillRect(convertColor(color_utils::color_for_ui(color)), rc); |
144 | } |
145 | else { |
146 | g->fillRect(gfx::rgba(0, 0, 0), rc); |
147 | g->drawLine(gfx::rgba(255, 255, 255), |
148 | gfx::Point(rc.x+rc.w-2, rc.y+1), |
149 | gfx::Point(rc.x+1, rc.y+rc.h-2)); |
150 | } |
151 | } |
152 | else { |
153 | g->fillRect(convertColor(color_utils::color_for_ui(color)), rc); |
154 | } |
155 | } |
156 | } |
157 | |
158 | void draw_color_button(ui::Graphics* g, |
159 | const Rect& rc, |
160 | const app::Color& color, |
161 | const doc::ColorMode colorMode, |
162 | const bool hot, |
163 | const bool drag) |
164 | { |
165 | auto theme = SkinTheme::instance(); |
166 | ASSERT(theme); |
167 | if (!theme) |
168 | return; |
169 | |
170 | int scale = ui::guiscale(); |
171 | |
172 | // Draw background (the color) |
173 | draw_color(g, |
174 | Rect(rc.x+1*scale, |
175 | rc.y+1*scale, |
176 | rc.w-2*scale, |
177 | rc.h-2*scale), |
178 | color, |
179 | colorMode); |
180 | |
181 | // Draw opaque border |
182 | theme->drawRect( |
183 | g, rc, |
184 | theme->parts.colorbar0()->bitmapNW(), |
185 | theme->parts.colorbar0()->bitmapN(), |
186 | theme->parts.colorbar1()->bitmapNE(), |
187 | theme->parts.colorbar1()->bitmapE(), |
188 | theme->parts.colorbar3()->bitmapSE(), |
189 | theme->parts.colorbar2()->bitmapS(), |
190 | theme->parts.colorbar2()->bitmapSW(), |
191 | theme->parts.colorbar0()->bitmapW()); |
192 | |
193 | // Draw hot |
194 | if (hot) { |
195 | theme->drawRect( |
196 | g, gfx::Rect(rc.x, rc.y, rc.w, rc.h-1 - 1*scale), |
197 | theme->parts.colorbarSelection().get()); |
198 | } |
199 | } |
200 | |
201 | void draw_tile(ui::Graphics* g, |
202 | const Rect& rc, |
203 | const Site& site, |
204 | doc::tile_t tile) |
205 | { |
206 | if (rc.w < 1 || rc.h < 1) |
207 | return; |
208 | |
209 | draw_checkered_grid(g, rc, gfx::Size(rc.w/2, rc.h/2)); |
210 | |
211 | if (tile == doc::notile) |
212 | return; |
213 | |
214 | doc::Tileset* ts = site.tileset(); |
215 | if (!ts) |
216 | return; |
217 | |
218 | doc::tile_index ti = doc::tile_geti(tile); |
219 | if (ti < 0 || ti >= ts->size()) |
220 | return; |
221 | |
222 | doc::ImageRef tileImage = ts->get(ti); |
223 | if (!tileImage) |
224 | return; |
225 | |
226 | const int w = tileImage->width(); |
227 | const int h = tileImage->height(); |
228 | os::SurfaceRef surface = os::instance()->makeRgbaSurface(w, h); |
229 | convert_image_to_surface(tileImage.get(), get_current_palette(), |
230 | surface.get(), 0, 0, 0, 0, w, h); |
231 | |
232 | ui::Paint paint; |
233 | paint.blendMode(os::BlendMode::SrcOver); |
234 | |
235 | os::Sampling sampling; |
236 | if (w > rc.w && h > rc.h) { |
237 | sampling = os::Sampling(os::Sampling::Filter::Linear, |
238 | os::Sampling::Mipmap::Nearest); |
239 | } |
240 | |
241 | g->drawSurface(surface.get(), gfx::Rect(0, 0, w, h), rc, |
242 | os::Sampling(), &paint); |
243 | } |
244 | |
245 | void draw_tile_button(ui::Graphics* g, |
246 | const gfx::Rect& rc, |
247 | const Site& site, |
248 | doc::tile_t tile, |
249 | const bool hot, |
250 | const bool drag) |
251 | { |
252 | auto theme = SkinTheme::instance(); |
253 | ASSERT(theme); |
254 | if (!theme) |
255 | return; |
256 | |
257 | int scale = ui::guiscale(); |
258 | |
259 | // Draw background (the tile) |
260 | draw_tile(g, |
261 | Rect(rc.x+1*scale, |
262 | rc.y+1*scale, |
263 | rc.w-2*scale, |
264 | rc.h-2*scale), |
265 | site, tile); |
266 | |
267 | // Draw opaque border |
268 | theme->drawRect( |
269 | g, rc, |
270 | theme->parts.colorbar0()->bitmapNW(), |
271 | theme->parts.colorbar0()->bitmapN(), |
272 | theme->parts.colorbar1()->bitmapNE(), |
273 | theme->parts.colorbar1()->bitmapE(), |
274 | theme->parts.colorbar3()->bitmapSE(), |
275 | theme->parts.colorbar2()->bitmapS(), |
276 | theme->parts.colorbar2()->bitmapSW(), |
277 | theme->parts.colorbar0()->bitmapW()); |
278 | |
279 | // Draw hot |
280 | if (hot) { |
281 | theme->drawRect( |
282 | g, gfx::Rect(rc.x, rc.y, rc.w, rc.h-1 - 1*scale), |
283 | theme->parts.colorbarSelection().get()); |
284 | } |
285 | } |
286 | |
287 | void draw_alpha_slider(ui::Graphics* g, |
288 | const gfx::Rect& rc, |
289 | const app::Color& color) |
290 | { |
291 | const int xmax = std::max(1, rc.w-1); |
292 | const doc::color_t c = |
293 | (color.getType() != app::Color::MaskType ? |
294 | doc::rgba(color.getRed(), |
295 | color.getGreen(), |
296 | color.getBlue(), 255): 0); |
297 | |
298 | for (int x=0; x<rc.w; ++x) { |
299 | const int a = (255 * x / xmax); |
300 | const doc::color_t c1 = doc::rgba_blender_normal(grid_color1(), c, a); |
301 | const doc::color_t c2 = doc::rgba_blender_normal(grid_color2(), c, a); |
302 | const int mid = rc.h/2; |
303 | const int odd = (x / rc.h) & 1; |
304 | g->drawVLine( |
305 | app::color_utils::color_for_ui(app::Color::fromImage(IMAGE_RGB, odd ? c2: c1)), |
306 | rc.x+x, rc.y, mid); |
307 | g->drawVLine( |
308 | app::color_utils::color_for_ui(app::Color::fromImage(IMAGE_RGB, odd ? c1: c2)), |
309 | rc.x+x, rc.y+mid, rc.h-mid); |
310 | } |
311 | } |
312 | |
313 | // TODO this code is exactly the same as draw_alpha_slider() with a ui::Graphics |
314 | void draw_alpha_slider(os::Surface* s, |
315 | const gfx::Rect& rc, |
316 | const app::Color& color) |
317 | { |
318 | const int xmax = std::max(1, rc.w-1); |
319 | const doc::color_t c = |
320 | (color.getType() != app::Color::MaskType ? |
321 | doc::rgba(color.getRed(), |
322 | color.getGreen(), |
323 | color.getBlue(), 255): 0); |
324 | |
325 | os::Paint paint; |
326 | for (int x=0; x<rc.w; ++x) { |
327 | const int a = (255 * x / xmax); |
328 | const doc::color_t c1 = doc::rgba_blender_normal(grid_color1(), c, a); |
329 | const doc::color_t c2 = doc::rgba_blender_normal(grid_color2(), c, a); |
330 | const int mid = rc.h/2; |
331 | const int odd = (x / rc.h) & 1; |
332 | |
333 | paint.color(app::color_utils::color_for_ui(app::Color::fromImage(IMAGE_RGB, odd ? c2: c1))); |
334 | s->drawRect(gfx::Rect(rc.x+x, rc.y, 1, mid), paint); |
335 | |
336 | paint.color(app::color_utils::color_for_ui(app::Color::fromImage(IMAGE_RGB, odd ? c1: c2))); |
337 | s->drawRect(gfx::Rect(rc.x+x, rc.y+mid, 1, rc.h-mid), paint); |
338 | } |
339 | } |
340 | |
341 | } // namespace app |
342 | |