1 | // Aseprite |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2016-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #define COLSEL_TRACE(...) |
9 | |
10 | #ifdef HAVE_CONFIG_H |
11 | #include "config.h" |
12 | #endif |
13 | |
14 | #include "app/ui/color_selector.h" |
15 | |
16 | #include "app/app.h" |
17 | #include "app/color_spaces.h" |
18 | #include "app/color_utils.h" |
19 | #include "app/modules/gfx.h" |
20 | #include "app/pref/preferences.h" |
21 | #include "app/ui/skin/skin_theme.h" |
22 | #include "app/ui/status_bar.h" |
23 | #include "app/util/shader_helpers.h" |
24 | #include "base/concurrent_queue.h" |
25 | #include "base/scoped_value.h" |
26 | #include "base/thread.h" |
27 | #include "os/surface.h" |
28 | #include "os/system.h" |
29 | #include "ui/manager.h" |
30 | #include "ui/message.h" |
31 | #include "ui/paint_event.h" |
32 | #include "ui/register_message.h" |
33 | #include "ui/scale.h" |
34 | #include "ui/size_hint_event.h" |
35 | #include "ui/system.h" |
36 | |
37 | #include <algorithm> |
38 | #include <cmath> |
39 | #include <condition_variable> |
40 | #include <cstdio> |
41 | #include <thread> |
42 | |
43 | #if SK_ENABLE_SKSL |
44 | #include "os/skia/skia_surface.h" |
45 | |
46 | #include "include/core/SkCanvas.h" |
47 | #include "include/effects/SkRuntimeEffect.h" |
48 | #endif |
49 | |
50 | namespace app { |
51 | |
52 | using namespace app::skin; |
53 | using namespace ui; |
54 | |
55 | // TODO This logic could be used to redraw any widget: |
56 | // 1. We send a onPaintSurfaceInBgThread() to paint the widget on a |
57 | // offscreen buffer |
58 | // 2. When the painting is done, we flip the buffer onto the screen |
59 | // 3. If we receive another onPaint() we can cancel the background |
60 | // painting and start another onPaintSurfaceInBgThread() |
61 | // |
62 | // An alternative (better) way: |
63 | // 1. Create an alternative ui::Graphics implementation that generates |
64 | // a list commands for the render thread |
65 | // 2. Widgets can still use the same onPaint() |
66 | // 3. The background threads consume the list of commands and render |
67 | // the screen. |
68 | // |
69 | // The bad side is that is harder to invalidate the commands that will |
70 | // render an old state of the widget. So the render thread should |
71 | // start caring about invalidating old commands (outdated regions) or |
72 | // cleaning the queue if it gets too big. |
73 | // |
74 | class ColorSelector::Painter { |
75 | public: |
76 | Painter() : m_canvas(nullptr) { |
77 | } |
78 | |
79 | ~Painter() { |
80 | ASSERT(!m_canvas); |
81 | } |
82 | |
83 | void addRef() { |
84 | assert_ui_thread(); |
85 | |
86 | if (m_ref == 0) |
87 | m_paintingThread = std::thread([this]{ paintingProc(); }); |
88 | |
89 | ++m_ref; |
90 | } |
91 | |
92 | void releaseRef() { |
93 | assert_ui_thread(); |
94 | |
95 | --m_ref; |
96 | if (m_ref == 0) { |
97 | { |
98 | std::unique_lock<std::mutex> lock(m_mutex); |
99 | stopCurrentPainting(lock); |
100 | |
101 | m_killing = true; |
102 | m_paintingCV.notify_one(); |
103 | } |
104 | |
105 | m_paintingThread.join(); |
106 | if (m_canvas) |
107 | m_canvas.reset(); |
108 | } |
109 | } |
110 | |
111 | os::Surface* getCanvas(int w, int h, gfx::Color bgColor) { |
112 | assert_ui_thread(); |
113 | |
114 | auto activeCS = get_current_color_space(); |
115 | |
116 | if (!m_canvas || |
117 | m_canvas->width() != w || |
118 | m_canvas->height() != h || |
119 | m_canvas->colorSpace() != activeCS) { |
120 | std::unique_lock<std::mutex> lock(m_mutex); |
121 | stopCurrentPainting(lock); |
122 | |
123 | os::SurfaceRef oldCanvas = m_canvas; |
124 | m_canvas = os::instance()->makeSurface(w, h, activeCS); |
125 | os::Paint paint; |
126 | paint.color(bgColor); |
127 | paint.style(os::Paint::Fill); |
128 | m_canvas->drawRect(gfx::Rect(0, 0, w, h), paint); |
129 | if (oldCanvas) { |
130 | m_canvas->drawSurface( |
131 | oldCanvas.get(), |
132 | gfx::Rect(0, 0, oldCanvas->width(), oldCanvas->height()), |
133 | gfx::Rect(0, 0, w, h), |
134 | os::Sampling(), |
135 | nullptr); |
136 | } |
137 | } |
138 | return m_canvas.get(); |
139 | } |
140 | |
141 | void startBgPainting(ColorSelector* colorSelector, |
142 | const gfx::Rect& mainBounds, |
143 | const gfx::Rect& bottomBarBounds, |
144 | const gfx::Rect& alphaBarBounds) { |
145 | assert_ui_thread(); |
146 | COLSEL_TRACE("COLSEL: startBgPainting for %p\n" , colorSelector); |
147 | |
148 | std::unique_lock<std::mutex> lock(m_mutex); |
149 | stopCurrentPainting(lock); |
150 | |
151 | m_colorSelector = colorSelector; |
152 | m_manager = colorSelector->manager(); |
153 | m_mainBounds = mainBounds; |
154 | m_bottomBarBounds = bottomBarBounds; |
155 | m_alphaBarBounds = alphaBarBounds; |
156 | |
157 | m_stopPainting = false; |
158 | m_paintingCV.notify_one(); |
159 | } |
160 | |
161 | private: |
162 | |
163 | void stopCurrentPainting(std::unique_lock<std::mutex>& lock) { |
164 | // Stop current |
165 | if (m_colorSelector) { |
166 | COLSEL_TRACE("COLSEL: stoppping painting of %p\n" , m_colorSelector); |
167 | |
168 | // TODO use another condition variable here |
169 | m_stopPainting = true; |
170 | m_waitStopCV.wait(lock); |
171 | } |
172 | |
173 | ASSERT(m_colorSelector == nullptr); |
174 | } |
175 | |
176 | void paintingProc() { |
177 | COLSEL_TRACE("COLSEL: paintingProc starts\n" ); |
178 | |
179 | std::unique_lock<std::mutex> lock(m_mutex); |
180 | while (true) { |
181 | m_paintingCV.wait(lock); |
182 | |
183 | if (m_killing) |
184 | break; |
185 | |
186 | auto colorSel = m_colorSelector; |
187 | if (!colorSel) |
188 | break; |
189 | |
190 | COLSEL_TRACE("COLSEL: starting painting in bg for %p\n" , colorSel); |
191 | |
192 | // Do the intesive painting in the background thread |
193 | { |
194 | lock.unlock(); |
195 | colorSel->onPaintSurfaceInBgThread( |
196 | m_canvas.get(), |
197 | m_mainBounds, |
198 | m_bottomBarBounds, |
199 | m_alphaBarBounds, |
200 | m_stopPainting); |
201 | lock.lock(); |
202 | } |
203 | |
204 | m_colorSelector = nullptr; |
205 | |
206 | if (m_stopPainting) { |
207 | COLSEL_TRACE("COLSEL: painting for %p stopped\n" ); |
208 | m_waitStopCV.notify_one(); |
209 | } |
210 | else { |
211 | COLSEL_TRACE("COLSEL: painting for %p done and sending message\n" ); |
212 | colorSel->m_paintFlags |= DoneFlag; |
213 | } |
214 | } |
215 | |
216 | COLSEL_TRACE("COLSEL: paintingProc ends\n" ); |
217 | } |
218 | |
219 | int m_ref = 0; |
220 | bool m_killing = false; |
221 | bool m_stopPainting = false; |
222 | std::mutex m_mutex; |
223 | std::condition_variable m_paintingCV; |
224 | std::condition_variable m_waitStopCV; |
225 | os::SurfaceRef m_canvas; |
226 | ColorSelector* m_colorSelector; |
227 | ui::Manager* m_manager; |
228 | gfx::Rect m_mainBounds; |
229 | gfx::Rect m_bottomBarBounds; |
230 | gfx::Rect m_alphaBarBounds; |
231 | std::thread m_paintingThread; |
232 | }; |
233 | |
234 | static ColorSelector::Painter painter; |
235 | |
236 | #if SK_ENABLE_SKSL |
237 | // static |
238 | sk_sp<SkRuntimeEffect> ColorSelector::m_alphaEffect; |
239 | #endif |
240 | |
241 | ColorSelector::ColorSelector() |
242 | : Widget(kGenericWidget) |
243 | , m_paintFlags(AllAreasFlag) |
244 | , m_lockColor(false) |
245 | , m_timer(100, this) |
246 | { |
247 | initTheme(); |
248 | painter.addRef(); |
249 | |
250 | m_appConn = App::instance() |
251 | ->ColorSpaceChange.connect( |
252 | &ColorSelector::updateColorSpace, this); |
253 | } |
254 | |
255 | ColorSelector::~ColorSelector() |
256 | { |
257 | painter.releaseRef(); |
258 | } |
259 | |
260 | void ColorSelector::selectColor(const app::Color& color) |
261 | { |
262 | if (m_lockColor) |
263 | return; |
264 | |
265 | if (m_color != color) |
266 | m_paintFlags |= onNeedsSurfaceRepaint(color); |
267 | |
268 | m_color = color; |
269 | invalidate(); |
270 | } |
271 | |
272 | app::Color ColorSelector::getColorByPosition(const gfx::Point& pos) |
273 | { |
274 | gfx::Rect rc = childrenBounds(); |
275 | if (rc.isEmpty()) |
276 | return app::Color::fromMask(); |
277 | |
278 | const int u = pos.x - rc.x; |
279 | const int umax = std::max(1, rc.w-1); |
280 | |
281 | const gfx::Rect bottomBarBounds = this->bottomBarBounds(); |
282 | if (( hasCapture() && m_capturedInBottom) || |
283 | (!hasCapture() && bottomBarBounds.contains(pos))) |
284 | return getBottomBarColor(u, umax); |
285 | |
286 | const gfx::Rect alphaBarBounds = this->alphaBarBounds(); |
287 | if (( hasCapture() && m_capturedInAlpha) || |
288 | (!hasCapture() && alphaBarBounds.contains(pos))) |
289 | return getAlphaBarColor(u, umax); |
290 | |
291 | const int v = pos.y - rc.y; |
292 | const int vmax = std::max(1, rc.h-bottomBarBounds.h-alphaBarBounds.h-1); |
293 | return getMainAreaColor(u, umax, |
294 | v, vmax); |
295 | } |
296 | |
297 | app::Color ColorSelector::getAlphaBarColor(const int u, const int umax) |
298 | { |
299 | int alpha = (255 * u / umax); |
300 | app::Color color = m_color; |
301 | color.setAlpha(std::clamp(alpha, 0, 255)); |
302 | return color; |
303 | } |
304 | |
305 | void ColorSelector::onSizeHint(SizeHintEvent& ev) |
306 | { |
307 | ev.setSizeHint(gfx::Size(32*ui::guiscale(), 32*ui::guiscale())); |
308 | } |
309 | |
310 | bool ColorSelector::onProcessMessage(ui::Message* msg) |
311 | { |
312 | switch (msg->type()) { |
313 | |
314 | case kMouseDownMessage: |
315 | if (manager()->getCapture()) |
316 | break; |
317 | |
318 | captureMouse(); |
319 | [[fallthrough]]; |
320 | |
321 | case kMouseMoveMessage: { |
322 | MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg); |
323 | const gfx::Point pos = mouseMsg->position(); |
324 | |
325 | if (msg->type() == kMouseDownMessage) { |
326 | m_capturedInBottom = bottomBarBounds().contains(pos); |
327 | m_capturedInAlpha = alphaBarBounds().contains(pos); |
328 | m_capturedInMain = (hasCapture() && |
329 | !m_capturedInMain && |
330 | !m_capturedInBottom); |
331 | } |
332 | |
333 | app::Color color = getColorByPosition(pos); |
334 | if (color != app::Color::fromMask()) { |
335 | base::ScopedValue<bool> switcher(m_lockColor, subColorPicked(), false); |
336 | |
337 | StatusBar::instance()->showColor(0, color); |
338 | if (hasCapture()) |
339 | ColorChange(color, mouseMsg->button()); |
340 | } |
341 | break; |
342 | } |
343 | |
344 | case kMouseUpMessage: |
345 | if (hasCapture()) { |
346 | m_capturedInBottom = false; |
347 | m_capturedInAlpha = false; |
348 | m_capturedInMain = false; |
349 | releaseMouse(); |
350 | } |
351 | return true; |
352 | |
353 | case kSetCursorMessage: { |
354 | MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg); |
355 | app::Color color = getColorByPosition(mouseMsg->position()); |
356 | if (color.getType() != app::Color::MaskType) { |
357 | auto theme = skin::SkinTheme::get(this); |
358 | ui::set_mouse_cursor(kCustomCursor, theme->cursors.eyedropper()); |
359 | return true; |
360 | } |
361 | break; |
362 | } |
363 | |
364 | case kMouseWheelMessage: |
365 | if (!hasCapture()) { |
366 | double scale = 1.0; |
367 | if (msg->shiftPressed() || |
368 | msg->ctrlPressed() || |
369 | msg->altPressed()) { |
370 | scale = 15.0; |
371 | } |
372 | |
373 | double newHue = m_color.getHsvHue() |
374 | + scale*(+ static_cast<MouseMessage*>(msg)->wheelDelta().x |
375 | - static_cast<MouseMessage*>(msg)->wheelDelta().y); |
376 | |
377 | while (newHue < 0.0) |
378 | newHue += 360.0; |
379 | newHue = std::fmod(newHue, 360.0); |
380 | |
381 | if (newHue != m_color.getHsvHue()) { |
382 | app::Color newColor = |
383 | app::Color::fromHsv( |
384 | newHue, |
385 | m_color.getHsvSaturation(), |
386 | m_color.getHsvValue(), |
387 | getCurrentAlphaForNewColor()); |
388 | |
389 | ColorChange(newColor, kButtonNone); |
390 | } |
391 | } |
392 | break; |
393 | |
394 | case kTimerMessage: |
395 | if (m_paintFlags & DoneFlag) { |
396 | m_timer.stop(); |
397 | invalidate(); |
398 | return true; |
399 | } |
400 | break; |
401 | |
402 | } |
403 | |
404 | return Widget::onProcessMessage(msg); |
405 | } |
406 | |
407 | void ColorSelector::onInitTheme(ui::InitThemeEvent& ev) |
408 | { |
409 | auto theme = SkinTheme::get(this); |
410 | |
411 | Widget::onInitTheme(ev); |
412 | setBorder(theme->calcBorder(this, theme->styles.editorView())); |
413 | } |
414 | |
415 | void ColorSelector::onResize(ui::ResizeEvent& ev) |
416 | { |
417 | Widget::onResize(ev); |
418 | |
419 | // We'll need to redraw the whole surface again with the new widget |
420 | // size. |
421 | m_paintFlags = AllAreasFlag; |
422 | } |
423 | |
424 | void ColorSelector::onPaint(ui::PaintEvent& ev) |
425 | { |
426 | ui::Graphics* g = ev.graphics(); |
427 | auto theme = SkinTheme::get(this); |
428 | |
429 | theme->drawRect(g, clientBounds(), |
430 | theme->parts.editorNormal().get(), |
431 | false); // Do not fill the center |
432 | |
433 | gfx::Rect rc = clientChildrenBounds(); |
434 | if (rc.isEmpty()) |
435 | return; |
436 | |
437 | gfx::Rect bottomBarBounds = this->bottomBarBounds(); |
438 | gfx::Rect alphaBarBounds = this->alphaBarBounds(); |
439 | |
440 | os::Surface* painterSurface = nullptr; |
441 | |
442 | #if SK_ENABLE_SKSL // Paint with shaders |
443 | if (buildEffects()) { |
444 | SkCanvas* canvas; |
445 | bool isSRGB; |
446 | // TODO compare both color spaces |
447 | if ((!get_current_color_space() || |
448 | get_current_color_space()->isSRGB()) && |
449 | (!g->getInternalSurface()->colorSpace() || |
450 | g->getInternalSurface()->colorSpace()->isSRGB())) { |
451 | // We can render directly in the ui::Graphics surface |
452 | canvas = &static_cast<os::SkiaSurface*>(g->getInternalSurface())->canvas(); |
453 | isSRGB = true; |
454 | } |
455 | else { |
456 | // We'll paint in the ColorSelector::Painter canvas, and so we |
457 | // can convert color spaces. |
458 | painterSurface = painter.getCanvas(rc.w, rc.h, theme->colors.workspace()); |
459 | canvas = &static_cast<os::SkiaSurface*>(painterSurface)->canvas(); |
460 | isSRGB = false; |
461 | } |
462 | |
463 | canvas->save(); |
464 | { |
465 | SkPaint p; |
466 | p.setStyle(SkPaint::kFill_Style); |
467 | |
468 | // Main area |
469 | gfx::Rect rc2(0, 0, rc.w, std::max(1, rc.h-bottomBarBounds.h-alphaBarBounds.h)); |
470 | |
471 | SkRuntimeShaderBuilder builder1(m_mainEffect); |
472 | builder1.uniform("iRes" ) = SkV3{float(rc2.w), float(rc2.h), 0.0f}; |
473 | setShaderParams(builder1, true); |
474 | p.setShader(builder1.makeShader()); |
475 | |
476 | if (isSRGB) |
477 | canvas->translate(rc.x+g->getInternalDeltaX(), |
478 | rc.y+g->getInternalDeltaY()); |
479 | |
480 | canvas->drawRect(SkRect::MakeXYWH(0, 0, rc2.w, rc2.h), p); |
481 | |
482 | // Bottom bar |
483 | canvas->translate(0.0, rc2.h); |
484 | rc2.h = bottomBarBounds.h; |
485 | |
486 | SkRuntimeShaderBuilder builder2(m_bottomEffect); |
487 | builder2.uniform("iRes" ) = SkV3{float(rc2.w), float(rc2.h), 0.0f}; |
488 | setShaderParams(builder2, false); |
489 | p.setShader(builder2.makeShader()); |
490 | |
491 | canvas->drawRect(SkRect::MakeXYWH(0, 0, rc2.w, rc2.h), p); |
492 | |
493 | // Alpha bar |
494 | canvas->translate(0.0, rc2.h); |
495 | rc2.h = alphaBarBounds.h; |
496 | |
497 | SkRuntimeShaderBuilder builder3(m_alphaEffect); |
498 | builder3.uniform("iRes" ) = SkV3{float(rc2.w), float(rc2.h), 0.0f}; |
499 | builder3.uniform("iColor" ) = appColor_to_SkV4(m_color); |
500 | builder3.uniform("iBg1" ) = gfxColor_to_SkV4(grid_color1()); |
501 | builder3.uniform("iBg2" ) = gfxColor_to_SkV4(grid_color2()); |
502 | p.setShader(builder3.makeShader()); |
503 | |
504 | canvas->drawRect(SkRect::MakeXYWH(0, 0, rc2.w, rc2.h), p); |
505 | } |
506 | canvas->restore(); |
507 | |
508 | // We already painted all areas |
509 | m_paintFlags = 0; |
510 | } |
511 | else |
512 | #endif // SK_ENABLE_SKSL |
513 | { |
514 | painterSurface = painter.getCanvas(rc.w, rc.h, theme->colors.workspace()); |
515 | } |
516 | |
517 | if (painterSurface) |
518 | g->drawSurface(painterSurface, rc.x, rc.y); |
519 | |
520 | rc.h -= bottomBarBounds.h + alphaBarBounds.h; |
521 | onPaintMainArea(g, rc); |
522 | |
523 | if (!bottomBarBounds.isEmpty()) { |
524 | bottomBarBounds.offset(-bounds().origin()); |
525 | onPaintBottomBar(g, bottomBarBounds); |
526 | } |
527 | |
528 | if (!alphaBarBounds.isEmpty()) { |
529 | alphaBarBounds.offset(-bounds().origin()); |
530 | onPaintAlphaBar(g, alphaBarBounds); |
531 | } |
532 | |
533 | if ((m_paintFlags & AllAreasFlag) != 0) { |
534 | m_paintFlags &= ~DoneFlag; |
535 | m_timer.start(); |
536 | |
537 | gfx::Point d = -rc.origin(); |
538 | rc.offset(d); |
539 | if (!bottomBarBounds.isEmpty()) bottomBarBounds.offset(d); |
540 | if (!alphaBarBounds.isEmpty()) alphaBarBounds.offset(d); |
541 | painter.startBgPainting(this, rc, bottomBarBounds, alphaBarBounds); |
542 | } |
543 | } |
544 | |
545 | void ColorSelector::onPaintAlphaBar(ui::Graphics* g, const gfx::Rect& rc) |
546 | { |
547 | const double lit = m_color.getHslLightness(); |
548 | const int alpha = m_color.getAlpha(); |
549 | const gfx::Point pos(rc.x + int(rc.w * alpha / 255), |
550 | rc.y + rc.h/2); |
551 | paintColorIndicator(g, pos, lit < 0.5); |
552 | } |
553 | |
554 | void ColorSelector::onPaintSurfaceInBgThread( |
555 | os::Surface* s, |
556 | const gfx::Rect& main, |
557 | const gfx::Rect& bottom, |
558 | const gfx::Rect& alpha, |
559 | bool& stop) |
560 | { |
561 | if ((m_paintFlags & AlphaBarFlag) && !alpha.isEmpty()) { |
562 | draw_alpha_slider(s, alpha, m_color); |
563 | if (stop) |
564 | return; |
565 | m_paintFlags ^= AlphaBarFlag; |
566 | } |
567 | } |
568 | |
569 | int ColorSelector::onNeedsSurfaceRepaint(const app::Color& newColor) |
570 | { |
571 | return (m_color.getRed() != newColor.getRed() || |
572 | m_color.getGreen() != newColor.getGreen() || |
573 | m_color.getBlue() != newColor.getBlue() ? AlphaBarFlag: 0); |
574 | } |
575 | |
576 | void ColorSelector::paintColorIndicator(ui::Graphics* g, |
577 | const gfx::Point& pos, |
578 | const bool white) |
579 | { |
580 | auto theme = SkinTheme::get(this); |
581 | os::Surface* icon = theme->parts.colorWheelIndicator()->bitmap(0); |
582 | |
583 | g->drawColoredRgbaSurface( |
584 | icon, |
585 | white ? gfx::rgba(255, 255, 255): gfx::rgba(0, 0, 0), |
586 | pos.x-icon->width()/2, |
587 | pos.y-icon->height()/2); |
588 | } |
589 | |
590 | int ColorSelector::getCurrentAlphaForNewColor() const |
591 | { |
592 | if (m_color.getType() != Color::MaskType) |
593 | return m_color.getAlpha(); |
594 | else |
595 | return 255; |
596 | } |
597 | |
598 | gfx::Rect ColorSelector::bottomBarBounds() const |
599 | { |
600 | auto theme = SkinTheme::get(this); |
601 | const gfx::Rect rc = childrenBounds(); |
602 | const int size = theme->dimensions.colorSelectorBarSize(); |
603 | if (rc.h > 2*size) { |
604 | if (rc.h > 3*size) // Alpha bar is visible too |
605 | return gfx::Rect(rc.x, rc.y2()-size*2, rc.w, size); |
606 | else |
607 | return gfx::Rect(rc.x, rc.y2()-size, rc.w, size); |
608 | } |
609 | else |
610 | return gfx::Rect(); |
611 | } |
612 | |
613 | gfx::Rect ColorSelector::alphaBarBounds() const |
614 | { |
615 | auto theme = SkinTheme::get(this); |
616 | const gfx::Rect rc = childrenBounds(); |
617 | const int size = theme->dimensions.colorSelectorBarSize(); |
618 | if (rc.h > 3*size) |
619 | return gfx::Rect(rc.x, rc.y2()-size, rc.w, size); |
620 | else |
621 | return gfx::Rect(); |
622 | } |
623 | |
624 | void ColorSelector::updateColorSpace() |
625 | { |
626 | m_paintFlags |= AllAreasFlag; |
627 | invalidate(); |
628 | } |
629 | |
630 | #if SK_ENABLE_SKSL |
631 | |
632 | // static |
633 | const char* ColorSelector::getAlphaBarShader() |
634 | { |
635 | return R"( |
636 | uniform half3 iRes; |
637 | uniform half4 iColor, iBg1, iBg2; |
638 | |
639 | half4 main(vec2 fragcoord) { |
640 | vec2 d = (fragcoord.xy / iRes.xy); |
641 | half4 p = (mod((fragcoord.x / iRes.y) + floor(d.y+0.5), 2.0) > 1.0) ? iBg2: iBg1; |
642 | half4 q = iColor.rgb1; |
643 | float a = d.x; |
644 | return (1.0-a)*p + a*q; |
645 | } |
646 | )" ; |
647 | } |
648 | |
649 | bool ColorSelector::buildEffects() |
650 | { |
651 | if (!Preferences::instance().experimental.useShadersForColorSelectors()) |
652 | return false; |
653 | |
654 | if (!m_mainEffect) { |
655 | if (const char* code = getMainAreaShader()) |
656 | m_mainEffect = buildEffect(code); |
657 | } |
658 | |
659 | if (!m_bottomEffect) { |
660 | if (const char* code = getBottomBarShader()) |
661 | m_bottomEffect = buildEffect(code); |
662 | } |
663 | |
664 | if (!m_alphaEffect) { |
665 | if (const char* code = getAlphaBarShader()) |
666 | m_alphaEffect = buildEffect(code); |
667 | } |
668 | |
669 | return (m_mainEffect && m_bottomEffect && m_alphaEffect); |
670 | } |
671 | |
672 | sk_sp<SkRuntimeEffect> ColorSelector::buildEffect(const char* code) |
673 | { |
674 | auto result = SkRuntimeEffect::MakeForShader(SkString(code)); |
675 | if (!result.errorText.isEmpty()) { |
676 | LOG(ERROR, "Shader error: %s\n" , result.errorText.c_str()); |
677 | std::printf("Shader error: %s\n" , result.errorText.c_str()); |
678 | return nullptr; |
679 | } |
680 | else { |
681 | return result.effect; |
682 | } |
683 | } |
684 | |
685 | void ColorSelector::resetBottomEffect() |
686 | { |
687 | m_bottomEffect.reset(); |
688 | } |
689 | |
690 | #endif // SK_ENABLE_SKSL |
691 | |
692 | } // namespace app |
693 | |