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
39namespace app {
40
41using namespace app::skin;
42using namespace gfx;
43
44namespace {
45
46void 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
80gfx::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
88gfx::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
96void 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
103void 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
111void 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
158void 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
201void 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
245void 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
287void 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
314void 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