1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2017 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/ui/dithering_selector.h"
13
14#include "app/app.h"
15#include "app/extensions.h"
16#include "app/i18n/strings.h"
17#include "app/modules/palettes.h"
18#include "app/ui/skin/skin_theme.h"
19#include "app/util/conversion_to_surface.h"
20#include "doc/image.h"
21#include "doc/image_ref.h"
22#include "doc/primitives.h"
23#include "os/surface.h"
24#include "os/system.h"
25#include "render/dithering.h"
26#include "render/gradient.h"
27#include "render/quantization.h"
28#include "ui/graphics.h"
29#include "ui/listitem.h"
30#include "ui/paint_event.h"
31#include "ui/size_hint_event.h"
32
33#include <algorithm>
34
35namespace app {
36
37using namespace ui;
38
39namespace {
40
41class DitherItem : public ListItem {
42public:
43
44 DitherItem(render::DitheringAlgorithm algo,
45 const render::DitheringMatrix& matrix,
46 const std::string& text)
47 : ListItem(text)
48 , m_matrixOnly(false)
49 , m_dithering(algo, matrix)
50 , m_preview(nullptr)
51 , m_palId(0)
52 , m_palMods(0)
53 {
54 }
55
56 DitherItem(const render::DitheringMatrix& matrix,
57 const std::string& text)
58 : ListItem(text)
59 , m_matrixOnly(true)
60 , m_dithering(render::DitheringAlgorithm::None, matrix)
61 , m_preview(nullptr)
62 , m_palId(0)
63 , m_palMods(0)
64 {
65 }
66
67 render::DitheringAlgorithm algo() const {
68 return m_dithering.algorithm();
69 }
70
71 render::DitheringMatrix matrix() const {
72 return m_dithering.matrix();
73 }
74
75private:
76 os::Surface* preview() {
77 const doc::Palette* palette = get_current_palette();
78 ASSERT(palette);
79
80 if (m_preview) {
81 // Reuse the preview in case that the palette is exactly the same
82 if (palette->id() == m_palId &&
83 palette->getModifications() == m_palMods)
84 return m_preview.get();
85
86 // In other case regenerate the preview for the current palette
87 m_preview.reset();
88 }
89
90 const int w = 128, h = 16;
91 doc::ImageRef image1(doc::Image::create(doc::IMAGE_RGB, w, h));
92 render_rgba_linear_gradient(
93 image1.get(),
94 gfx::Point(0, 0),
95 gfx::Point(0, 0),
96 gfx::Point(w-1, 0),
97 doc::rgba(0, 0, 0, 255),
98 doc::rgba(255, 255, 255, 255),
99 (m_matrixOnly ? m_dithering.matrix():
100 render::DitheringMatrix()));
101
102 doc::ImageRef image2;
103 if (m_matrixOnly) {
104 image2 = image1;
105 }
106 else {
107 image2.reset(doc::Image::create(doc::IMAGE_INDEXED, w, h));
108 doc::clear_image(image2.get(), 0);
109 render::convert_pixel_format(
110 image1.get(), image2.get(), IMAGE_INDEXED,
111 m_dithering, nullptr, palette, true, -1, nullptr);
112 }
113
114 m_preview = os::instance()->makeRgbaSurface(w, h);
115 convert_image_to_surface(image2.get(), palette, m_preview.get(),
116 0, 0, 0, 0, w, h);
117
118 m_palId = palette->id();
119 m_palMods = palette->getModifications();
120 return m_preview.get();
121 }
122
123 void onSizeHint(SizeHintEvent& ev) override {
124 gfx::Size sz = textSize();
125
126 sz.w = std::max(sz.w, preview()->width()*guiscale()) + 4*guiscale();
127 sz.h += 6*guiscale() + preview()->height()*guiscale();
128
129 ev.setSizeHint(sz);
130 }
131
132 void onPaint(PaintEvent& ev) override {
133 Graphics* g = ev.graphics();
134 auto theme = skin::SkinTheme::get(this);
135
136 gfx::Color fg, bg;
137 if (isSelected()) {
138 fg = theme->colors.listitemSelectedText();
139 bg = theme->colors.listitemSelectedFace();
140 }
141 else {
142 fg = theme->colors.listitemNormalText();
143 bg = theme->colors.listitemNormalFace();
144 }
145
146 gfx::Rect rc = clientBounds();
147 g->fillRect(bg, rc);
148
149 gfx::Size textsz = textSize();
150 g->drawText(text(), fg, bg,
151 gfx::Point(rc.x+2*guiscale(),
152 rc.y+2*guiscale()));
153
154 ui::Paint paint;
155 paint.blendMode(os::BlendMode::SrcOver);
156
157 g->drawSurface(
158 preview(),
159 preview()->bounds(),
160 gfx::Rect(
161 rc.x+2*guiscale(),
162 rc.y+4*guiscale()+textsz.h,
163 preview()->width()*guiscale(),
164 preview()->height()*guiscale()),
165 os::Sampling(),
166 &paint);
167 }
168
169 bool m_matrixOnly;
170 render::Dithering m_dithering;
171 os::SurfaceRef m_preview;
172 doc::ObjectId m_palId;
173 int m_palMods;
174};
175
176} // anonymous namespace
177
178DitheringSelector::DitheringSelector(Type type)
179 : m_type(type)
180{
181 Extensions& extensions = App::instance()->extensions();
182
183 // If an extension with "ditheringMatrices" is disable/enable, we
184 // regenerate this DitheringSelector
185 m_extChanges =
186 extensions.DitheringMatricesChange.connect(
187 [this]{ regenerate(); });
188
189 setUseCustomWidget(true);
190 regenerate();
191}
192
193void DitheringSelector::onInitTheme(ui::InitThemeEvent& ev)
194{
195 ComboBox::onInitTheme(ev);
196 if (getItem(0))
197 setSizeHint(getItem(0)->sizeHint());
198}
199
200void DitheringSelector::regenerate()
201{
202 deleteAllItems();
203
204 Extensions& extensions = App::instance()->extensions();
205 auto ditheringMatrices = extensions.ditheringMatrices();
206
207 switch (m_type) {
208 case SelectBoth:
209 addItem(new DitherItem(render::DitheringAlgorithm::None,
210 render::DitheringMatrix(),
211 Strings::dithering_selector_no_dithering()));
212 for (const auto& it : ditheringMatrices) {
213 addItem(new DitherItem(
214 render::DitheringAlgorithm::Ordered,
215 it.matrix(),
216 Strings::dithering_selector_ordered_dithering() + it.name()));
217 }
218 for (const auto& it : ditheringMatrices) {
219 addItem(
220 new DitherItem(
221 render::DitheringAlgorithm::Old,
222 it.matrix(),
223 Strings::dithering_selector_old_dithering() + it.name()));
224 }
225 addItem(
226 new DitherItem(
227 render::DitheringAlgorithm::ErrorDiffusion,
228 render::DitheringMatrix(),
229 Strings::dithering_selector_floyd_steinberg()));
230 break;
231 case SelectMatrix:
232 addItem(new DitherItem(render::DitheringMatrix(),
233 Strings::dithering_selector_no_dithering()));
234 for (auto& it : ditheringMatrices)
235 addItem(new DitherItem(it.matrix(), it.name()));
236 break;
237 }
238
239 setSelectedItemIndex(0);
240 setSizeHint(getItem(0)->sizeHint());
241}
242
243render::DitheringAlgorithm DitheringSelector::ditheringAlgorithm()
244{
245 auto item = static_cast<DitherItem*>(getSelectedItem());
246 if (item)
247 return item->algo();
248 else
249 return render::DitheringAlgorithm::None;
250}
251
252render::DitheringMatrix DitheringSelector::ditheringMatrix()
253{
254 auto item = static_cast<DitherItem*>(getSelectedItem());
255 if (item)
256 return item->matrix();
257 else
258 return render::DitheringMatrix();
259}
260
261} // namespace app
262