1// Aseprite UI Library
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "ui/graphics.h"
13
14#include "base/string.h"
15#include "gfx/clip.h"
16#include "gfx/matrix.h"
17#include "gfx/path.h"
18#include "gfx/point.h"
19#include "gfx/rect.h"
20#include "gfx/region.h"
21#include "gfx/size.h"
22#include "os/draw_text.h"
23#include "os/font.h"
24#include "os/sampling.h"
25#include "os/surface.h"
26#include "os/system.h"
27#include "os/window.h"
28#include "ui/display.h"
29#include "ui/scale.h"
30#include "ui/theme.h"
31
32#include <algorithm>
33#include <cctype>
34#include <string>
35
36namespace ui {
37
38Graphics::Graphics(Display* display, const os::SurfaceRef& surface, int dx, int dy)
39 : m_display(display)
40 , m_surface(surface)
41 , m_dx(dx)
42 , m_dy(dy)
43{
44}
45
46Graphics::~Graphics()
47{
48 // If we were drawing in the screen surface, we mark these regions
49 // as dirty for the final flip.
50 if (m_display)
51 m_display->dirtyRect(m_dirtyBounds);
52}
53
54int Graphics::width() const
55{
56 return m_surface->width();
57}
58
59int Graphics::height() const
60{
61 return m_surface->height();
62}
63
64int Graphics::getSaveCount() const
65{
66 return m_surface->getSaveCount();
67}
68
69gfx::Rect Graphics::getClipBounds() const
70{
71 return m_surface->getClipBounds().offset(-m_dx, -m_dy);
72}
73
74void Graphics::saveClip()
75{
76 m_surface->saveClip();
77}
78
79void Graphics::restoreClip()
80{
81 m_surface->restoreClip();
82}
83
84bool Graphics::clipRect(const gfx::Rect& rc)
85{
86 return m_surface->clipRect(gfx::Rect(rc).offset(m_dx, m_dy));
87}
88
89void Graphics::save()
90{
91 m_surface->save();
92}
93
94void Graphics::concat(const gfx::Matrix& matrix)
95{
96 m_surface->concat(matrix);
97}
98
99void Graphics::setMatrix(const gfx::Matrix& matrix)
100{
101 m_surface->setMatrix(matrix);
102}
103
104void Graphics::resetMatrix()
105{
106 m_surface->resetMatrix();
107}
108
109void Graphics::restore()
110{
111 m_surface->restore();
112}
113
114gfx::Matrix Graphics::matrix() const
115{
116 return m_surface->matrix();
117}
118
119gfx::Color Graphics::getPixel(int x, int y)
120{
121 os::SurfaceLock lock(m_surface.get());
122 return m_surface->getPixel(m_dx+x, m_dy+y);
123}
124
125void Graphics::putPixel(gfx::Color color, int x, int y)
126{
127 dirty(gfx::Rect(m_dx+x, m_dy+y, 1, 1));
128
129 os::SurfaceLock lock(m_surface.get());
130 m_surface->putPixel(color, m_dx+x, m_dy+y);
131}
132
133void Graphics::drawHLine(int x, int y, int w, const Paint& paint)
134{
135 dirty(gfx::Rect(m_dx+x, m_dy+y, w, 1));
136
137 os::SurfaceLock lock(m_surface.get());
138 m_surface->drawRect(gfx::Rect(m_dx+x, m_dy+y, w, 1), paint);
139}
140
141void Graphics::drawHLine(gfx::Color color, int x, int y, int w)
142{
143 dirty(gfx::Rect(m_dx+x, m_dy+y, w, 1));
144
145 os::SurfaceLock lock(m_surface.get());
146 os::Paint paint;
147 paint.color(color);
148 m_surface->drawRect(gfx::Rect(m_dx+x, m_dy+y, w, 1), paint);
149}
150
151void Graphics::drawVLine(int x, int y, int h, const Paint& paint)
152{
153 dirty(gfx::Rect(m_dx+x, m_dy+y, 1, h));
154
155 os::SurfaceLock lock(m_surface.get());
156 m_surface->drawRect(gfx::Rect(m_dx+x, m_dy+y, 1, h), paint);
157}
158
159void Graphics::drawVLine(gfx::Color color, int x, int y, int h)
160{
161 dirty(gfx::Rect(m_dx+x, m_dy+y, 1, h));
162
163 os::SurfaceLock lock(m_surface.get());
164 os::Paint paint;
165 paint.color(color);
166 m_surface->drawRect(gfx::Rect(m_dx+x, m_dy+y, 1, h), paint);
167}
168
169void Graphics::drawLine(gfx::Color color, const gfx::Point& _a, const gfx::Point& _b)
170{
171 gfx::Point a(m_dx+_a.x, m_dy+_a.y);
172 gfx::Point b(m_dx+_b.x, m_dy+_b.y);
173 dirty(gfx::Rect(a, b));
174
175 os::SurfaceLock lock(m_surface.get());
176 os::Paint paint;
177 paint.color(color);
178 m_surface->drawLine(a, b, paint);
179}
180
181void Graphics::drawPath(gfx::Path& path, const Paint& paint)
182{
183 os::SurfaceLock lock(m_surface.get());
184
185 auto m = matrix();
186 save();
187 setMatrix(gfx::Matrix::MakeTrans(m_dx, m_dy));
188 concat(m);
189
190 m_surface->drawPath(path, paint);
191
192 dirty(matrix().mapRect(path.bounds()).inflate(1, 1));
193 restore();
194}
195
196void Graphics::drawRect(const gfx::Rect& rcOrig, const Paint& paint)
197{
198 gfx::Rect rc(rcOrig);
199 rc.offset(m_dx, m_dy);
200 dirty(rc);
201
202 os::SurfaceLock lock(m_surface.get());
203 m_surface->drawRect(rc, paint);
204}
205
206void Graphics::drawRect(gfx::Color color, const gfx::Rect& rcOrig)
207{
208 gfx::Rect rc(rcOrig);
209 rc.offset(m_dx, m_dy);
210 dirty(rc);
211
212 os::SurfaceLock lock(m_surface.get());
213 os::Paint paint;
214 paint.color(color);
215 paint.style(os::Paint::Stroke);
216 m_surface->drawRect(rc, paint);
217}
218
219void Graphics::fillRect(gfx::Color color, const gfx::Rect& rcOrig)
220{
221 gfx::Rect rc(rcOrig);
222 rc.offset(m_dx, m_dy);
223 dirty(rc);
224
225 os::SurfaceLock lock(m_surface.get());
226 os::Paint paint;
227 paint.color(color);
228 paint.style(os::Paint::Fill);
229 m_surface->drawRect(rc, paint);
230}
231
232void Graphics::fillRegion(gfx::Color color, const gfx::Region& rgn)
233{
234 for (gfx::Region::iterator it=rgn.begin(), end=rgn.end(); it!=end; ++it)
235 fillRect(color, *it);
236}
237
238void Graphics::fillAreaBetweenRects(gfx::Color color,
239 const gfx::Rect& outer, const gfx::Rect& inner)
240{
241 if (!outer.intersects(inner))
242 fillRect(color, outer);
243 else {
244 gfx::Region rgn(outer);
245 rgn.createSubtraction(rgn, gfx::Region(inner));
246 fillRegion(color, rgn);
247 }
248}
249
250void Graphics::drawSurface(os::Surface* surface, int x, int y)
251{
252 dirty(gfx::Rect(m_dx+x, m_dy+y, surface->width(), surface->height()));
253
254 os::SurfaceLock lockSrc(surface);
255 os::SurfaceLock lockDst(m_surface.get());
256 m_surface->drawSurface(surface, m_dx+x, m_dy+y);
257}
258
259void Graphics::drawSurface(os::Surface* surface,
260 const gfx::Rect& srcRect,
261 const gfx::Rect& dstRect,
262 const os::Sampling& sampling,
263 const ui::Paint* paint)
264{
265 dirty(gfx::Rect(m_dx+dstRect.x, m_dy+dstRect.y,
266 dstRect.w, dstRect.h));
267
268 os::SurfaceLock lockSrc(surface);
269 os::SurfaceLock lockDst(m_surface.get());
270 m_surface->drawSurface(
271 surface,
272 srcRect,
273 gfx::Rect(dstRect).offset(m_dx, m_dy),
274 sampling,
275 paint);
276}
277
278void Graphics::drawRgbaSurface(os::Surface* surface, int x, int y)
279{
280 dirty(gfx::Rect(m_dx+x, m_dy+y, surface->width(), surface->height()));
281
282 os::SurfaceLock lockSrc(surface);
283 os::SurfaceLock lockDst(m_surface.get());
284 m_surface->drawRgbaSurface(surface, m_dx+x, m_dy+y);
285}
286
287void Graphics::drawRgbaSurface(os::Surface* surface, int srcx, int srcy, int dstx, int dsty, int w, int h)
288{
289 dirty(gfx::Rect(m_dx+dstx, m_dy+dsty, w, h));
290
291 os::SurfaceLock lockSrc(surface);
292 os::SurfaceLock lockDst(m_surface.get());
293 m_surface->drawRgbaSurface(surface, srcx, srcy, m_dx+dstx, m_dy+dsty, w, h);
294}
295
296void Graphics::drawColoredRgbaSurface(os::Surface* surface, gfx::Color color, int x, int y)
297{
298 dirty(gfx::Rect(m_dx+x, m_dy+y, surface->width(), surface->height()));
299
300 os::SurfaceLock lockSrc(surface);
301 os::SurfaceLock lockDst(m_surface.get());
302 m_surface->drawColoredRgbaSurface(surface, color, gfx::ColorNone,
303 gfx::Clip(m_dx+x, m_dy+y, 0, 0, surface->width(), surface->height()));
304}
305
306void Graphics::drawColoredRgbaSurface(os::Surface* surface, gfx::Color color,
307 int srcx, int srcy, int dstx, int dsty, int w, int h)
308{
309 dirty(gfx::Rect(m_dx+dstx, m_dy+dsty, w, h));
310
311 os::SurfaceLock lockSrc(surface);
312 os::SurfaceLock lockDst(m_surface.get());
313 m_surface->drawColoredRgbaSurface(surface, color, gfx::ColorNone,
314 gfx::Clip(m_dx+dstx, m_dy+dsty, srcx, srcy, w, h));
315}
316
317void Graphics::drawSurfaceNine(os::Surface* surface,
318 const gfx::Rect& src,
319 const gfx::Rect& center,
320 const gfx::Rect& dst,
321 const bool drawCenter,
322 const ui::Paint* paint)
323{
324 gfx::Rect displacedDst(m_dx+dst.x, m_dy+dst.y, dst.w, dst.h);
325 dirty(displacedDst);
326
327 os::SurfaceLock lockSrc(surface);
328 os::SurfaceLock lockDst(m_surface.get());
329 m_surface->drawSurfaceNine(surface, src, center, displacedDst, drawCenter, paint);
330}
331
332void Graphics::blit(os::Surface* srcSurface, int srcx, int srcy, int dstx, int dsty, int w, int h)
333{
334 dirty(gfx::Rect(m_dx+dstx, m_dy+dsty, w, h));
335
336 os::SurfaceLock lockSrc(srcSurface);
337 os::SurfaceLock lockDst(m_surface.get());
338 srcSurface->blitTo(m_surface.get(), srcx, srcy, m_dx+dstx, m_dy+dsty, w, h);
339}
340
341void Graphics::setFont(const os::FontRef& font)
342{
343 m_font = font;
344}
345
346void Graphics::drawText(const std::string& str,
347 gfx::Color fg, gfx::Color bg,
348 const gfx::Point& origPt,
349 os::DrawTextDelegate* delegate)
350{
351 gfx::Point pt(m_dx+origPt.x, m_dy+origPt.y);
352
353 os::SurfaceLock lock(m_surface.get());
354 gfx::Rect textBounds =
355 os::draw_text(m_surface.get(), m_font.get(), str, fg, bg, pt.x, pt.y, delegate);
356
357 dirty(gfx::Rect(pt.x, pt.y, textBounds.w, textBounds.h));
358}
359
360namespace {
361
362class DrawUITextDelegate : public os::DrawTextDelegate {
363public:
364 DrawUITextDelegate(os::Surface* surface,
365 os::Font* font, const int mnemonic)
366 : m_surface(surface)
367 , m_font(font)
368 , m_mnemonic(std::tolower(mnemonic))
369 , m_underscoreColor(gfx::ColorNone) {
370 }
371
372 gfx::Rect bounds() const { return m_bounds; }
373
374 void preProcessChar(const int index,
375 const int codepoint,
376 gfx::Color& fg,
377 gfx::Color& bg,
378 const gfx::Rect& charBounds) override {
379 if (m_surface) {
380 if (m_mnemonic &&
381 // TODO use ICU library to lower unicode chars
382 std::tolower(codepoint) == m_mnemonic) {
383 m_underscoreColor = fg;
384 m_mnemonic = 0; // Just one time
385 }
386 else {
387 m_underscoreColor = gfx::ColorNone;
388 }
389 }
390 }
391
392 bool preDrawChar(const gfx::Rect& charBounds) override {
393 m_bounds |= charBounds;
394 return true;
395 }
396
397 void postDrawChar(const gfx::Rect& charBounds) override {
398 if (!gfx::is_transparent(m_underscoreColor)) {
399 // TODO underscore height = guiscale() should be configurable from ui::Theme
400 int dy = 0;
401 if (m_font->type() == os::FontType::FreeType) // TODO use other method to locate the underline
402 dy += guiscale();
403 gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy,
404 charBounds.w, guiscale());
405
406 os::Paint paint;
407 paint.color(m_underscoreColor);
408 paint.style(os::Paint::Fill);
409 m_surface->drawRect(underscoreBounds, paint);
410
411 m_bounds |= underscoreBounds;
412 }
413 }
414
415private:
416 os::Surface* m_surface;
417 os::Font* m_font;
418 int m_mnemonic;
419 gfx::Color m_underscoreColor;
420 gfx::Rect m_bounds;
421};
422
423}
424
425void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg,
426 const gfx::Point& pt, const int mnemonic)
427{
428 os::SurfaceLock lock(m_surface.get());
429 int x = m_dx+pt.x;
430 int y = m_dy+pt.y;
431
432 DrawUITextDelegate delegate(m_surface.get(), m_font.get(), mnemonic);
433 os::draw_text(m_surface.get(), m_font.get(), str,
434 fg, bg, x, y, &delegate);
435
436 dirty(delegate.bounds());
437}
438
439void Graphics::drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg,
440 const gfx::Rect& rc, const int align)
441{
442 doUIStringAlgorithm(str, fg, bg, rc, align, true);
443}
444
445gfx::Size Graphics::measureUIText(const std::string& str)
446{
447 return gfx::Size(
448 Graphics::measureUITextLength(str, m_font.get()),
449 m_font->height());
450}
451
452// static
453int Graphics::measureUITextLength(const std::string& str, os::Font* font)
454{
455 DrawUITextDelegate delegate(nullptr, font, 0);
456 os::draw_text(nullptr, font, str,
457 gfx::ColorNone, gfx::ColorNone, 0, 0,
458 &delegate);
459 return delegate.bounds().w;
460}
461
462gfx::Size Graphics::fitString(const std::string& str, int maxWidth, int align)
463{
464 return doUIStringAlgorithm(str, gfx::ColorNone, gfx::ColorNone,
465 gfx::Rect(0, 0, maxWidth, 0), align, false);
466}
467
468gfx::Size Graphics::doUIStringAlgorithm(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, int align, bool draw)
469{
470 gfx::Point pt(0, rc.y);
471
472 if ((align & (MIDDLE | BOTTOM)) != 0) {
473 gfx::Size preSize = doUIStringAlgorithm(str, gfx::ColorNone, gfx::ColorNone, rc, 0, false);
474 if (align & MIDDLE)
475 pt.y = rc.y + rc.h/2 - preSize.h/2;
476 else if (align & BOTTOM)
477 pt.y = rc.y + rc.h - preSize.h;
478 }
479
480 gfx::Size calculatedSize(0, 0);
481 std::size_t beg, end, newBeg;
482 std::string line;
483 int lineSeparation = 2*guiscale();
484
485 // Draw line-by-line
486 for (beg=end=0; end != std::string::npos && beg<str.size(); ) {
487 pt.x = rc.x;
488
489 // Without word-wrap
490 if ((align & (WORDWRAP | CHARWRAP)) == 0) {
491 end = str.find('\n', beg);
492 if (end != std::string::npos)
493 newBeg = end+1;
494 else
495 newBeg = std::string::npos;
496 }
497 // With char-wrap
498 else if ((align & CHARWRAP) == CHARWRAP) {
499 for (end=beg+1; end<str.size(); ++end) {
500 // If we are out of the available width (rc.w) using the new "end"
501 if ((rc.w > 0) &&
502 (m_font->textLength(str.substr(beg, end-beg).c_str()) > rc.w)) {
503 if (end > beg+1)
504 --end;
505 break;
506 }
507 }
508 newBeg = end;
509 }
510 // With word-wrap
511 else {
512 std::size_t old_end = std::string::npos;
513 for (std::size_t new_word_beg=beg;;) {
514 end = str.find_first_of(" \n", new_word_beg);
515
516 // If we have already a word to print (old_end != npos), and
517 // we are out of the available width (rc.w) using the new "end"
518 if ((old_end != std::string::npos) &&
519 (rc.w > 0) &&
520 (pt.x+m_font->textLength(str.substr(beg, end-beg).c_str()) > rc.w)) {
521 // We go back to the "old_end" and paint from "beg" to "end"
522 end = old_end;
523 break;
524 }
525 // If we have more words to print...
526 else if (end != std::string::npos) {
527 // Force line break, now we have to paint from "beg" to "end"
528 if (str[end] == '\n')
529 break;
530
531 // White-space, this is a beginning of a new word.
532 new_word_beg = end+1;
533 }
534 // We are in the end of text
535 else
536 break;
537
538 old_end = end;
539 }
540 newBeg = end+1;
541 }
542
543 // Get the entire line to be painted
544 if (end != std::string::npos)
545 line = str.substr(beg, end-beg);
546 else
547 line = str.substr(beg);
548
549 gfx::Size lineSize(
550 m_font->textLength(line.c_str()),
551 m_font->height()+lineSeparation);
552 calculatedSize.w = std::max(calculatedSize.w, lineSize.w);
553
554 // Render the text
555 if (draw) {
556 int xout;
557 if ((align & CENTER) == CENTER)
558 xout = pt.x + rc.w/2 - lineSize.w/2;
559 else if ((align & RIGHT) == RIGHT)
560 xout = pt.x + rc.w - lineSize.w;
561 else
562 xout = pt.x;
563
564 drawText(line, fg, bg, gfx::Point(xout, pt.y));
565
566 if (!gfx::is_transparent(bg))
567 fillAreaBetweenRects(bg,
568 gfx::Rect(rc.x, pt.y, rc.w, lineSize.h),
569 gfx::Rect(xout, pt.y, lineSize.w, lineSize.h));
570 }
571
572 pt.y += lineSize.h;
573 calculatedSize.h += lineSize.h;
574 beg = newBeg;
575
576 if (pt.y+lineSize.h >= rc.y2())
577 break;
578 }
579
580 if (calculatedSize.h > 0)
581 calculatedSize.h -= lineSeparation;
582
583 // Fill bottom area
584 if (draw && !gfx::is_transparent(bg)) {
585 if (pt.y < rc.y+rc.h)
586 fillRect(bg, gfx::Rect(rc.x, pt.y, rc.w, rc.y+rc.h-pt.y));
587 }
588
589 return calculatedSize;
590}
591
592void Graphics::dirty(const gfx::Rect& bounds)
593{
594 gfx::Rect rc = m_surface->getClipBounds();
595 rc = rc.createIntersection(bounds);
596 if (!rc.isEmpty())
597 m_dirtyBounds |= rc;
598}
599
600//////////////////////////////////////////////////////////////////////
601// ScreenGraphics
602
603ScreenGraphics::ScreenGraphics(Display* display)
604 : Graphics(display, AddRef(display->surface()), 0, 0)
605{
606 setFont(AddRef(get_theme()->getDefaultFont()));
607}
608
609ScreenGraphics::~ScreenGraphics()
610{
611}
612
613} // namespace ui
614