1 | // Aseprite |
2 | // Copyright (c) 2018-2019 Igara Studio S.A. |
3 | // |
4 | // This program is distributed under the terms of |
5 | // the End-User License Agreement for Aseprite. |
6 | // |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/console.h" |
13 | #include "app/context.h" |
14 | #include "app/doc.h" |
15 | #include "app/file/file.h" |
16 | #include "app/file/file_format.h" |
17 | #include "app/file/format_options.h" |
18 | #include "app/pref/preferences.h" |
19 | #include "base/convert_to.h" |
20 | #include "base/cfile.h" |
21 | #include "base/file_handle.h" |
22 | #include "base/string.h" |
23 | #include "doc/doc.h" |
24 | #include "ui/window.h" |
25 | |
26 | #include "css_options.xml.h" |
27 | |
28 | |
29 | namespace app { |
30 | |
31 | using namespace base; |
32 | |
33 | class CssFormat : public FileFormat { |
34 | class CssOptions : public FormatOptions { |
35 | public: |
36 | CssOptions() : pixelScale(1), gutterSize(0), |
37 | generateHtml(false), |
38 | withVars(false) { } |
39 | int pixelScale; |
40 | int gutterSize; |
41 | bool generateHtml; |
42 | bool withVars; |
43 | }; |
44 | |
45 | const char* onGetName() const override { |
46 | return "css" ; |
47 | } |
48 | |
49 | void onGetExtensions(base::paths& exts) const override { |
50 | exts.push_back("css" ); |
51 | } |
52 | |
53 | dio::FileFormat onGetDioFormat() const override { |
54 | return dio::FileFormat::CSS_STYLE; |
55 | } |
56 | |
57 | int onGetFlags() const override { |
58 | return |
59 | FILE_SUPPORT_SAVE | |
60 | FILE_SUPPORT_RGB | |
61 | FILE_SUPPORT_RGBA | |
62 | FILE_SUPPORT_GRAY | |
63 | FILE_SUPPORT_GRAYA | |
64 | FILE_SUPPORT_INDEXED | |
65 | FILE_SUPPORT_SEQUENCES | |
66 | FILE_SUPPORT_GET_FORMAT_OPTIONS | |
67 | FILE_SUPPORT_PALETTE_WITH_ALPHA; |
68 | } |
69 | |
70 | bool onLoad(FileOp* fop) override; |
71 | #ifdef ENABLE_SAVE |
72 | bool onSave(FileOp* fop) override; |
73 | #endif |
74 | FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) override; |
75 | }; |
76 | |
77 | FileFormat *CreateCssFormat() |
78 | { |
79 | return new CssFormat; |
80 | } |
81 | |
82 | bool CssFormat::onLoad(FileOp* fop) |
83 | { |
84 | return false; |
85 | } |
86 | |
87 | #ifdef ENABLE_SAVE |
88 | |
89 | bool CssFormat::onSave(FileOp* fop) |
90 | { |
91 | const ImageRef image = fop->sequenceImage(); |
92 | int x, y, c, r, g, b, a, alpha; |
93 | const auto css_options = std::static_pointer_cast<CssOptions>(fop->formatOptions()); |
94 | FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb" )); |
95 | FILE* f = handle.get(); |
96 | auto print_color = [f](int r, int g, int b, int a) { |
97 | if (a == 255) { |
98 | fprintf(f, "#%02X%02X%02X" , r, g, b); |
99 | } |
100 | else { |
101 | fprintf(f, "rgba(%d, %d, %d, %d)" , r, g, b, a); |
102 | } |
103 | }; |
104 | auto print_shadow_color = [f, css_options, print_color](int x, int y, int r, |
105 | int g, int b, int a, |
106 | bool comma = true) { |
107 | fprintf(f, comma?",\n" :"\n" ); |
108 | if (css_options->withVars) { |
109 | fprintf(f, "\tcalc(%d*var(--shadow-mult)) calc(%d*var(--shadow-mult)) var(--blur) var(--spread) " , |
110 | x, y); |
111 | } |
112 | else { |
113 | int x_loc = x * (css_options->pixelScale + css_options->gutterSize); |
114 | int y_loc = y * (css_options->pixelScale + css_options->gutterSize); |
115 | fprintf(f, "%dpx %dpx " , x_loc, y_loc); |
116 | } |
117 | print_color(r, g, b, a); |
118 | }; |
119 | auto print_shadow_index = [f, css_options](int x, int y, int i, bool comma=true) { |
120 | fprintf(f, comma?",\n" :"\n" ); |
121 | fprintf(f, "\tcalc(%d*var(--shadow-mult)) calc(%d*var(--shadow-mult)) var(--blur) var(--spread) var(--color-%d)" , |
122 | x, y, i); |
123 | }; |
124 | if (css_options->withVars) { |
125 | fprintf(f, ":root {\n" |
126 | "\t--blur: 0px;\n" |
127 | "\t--spread: 0px;\n" |
128 | "\t--pixel-size: %dpx;\n" |
129 | "\t--gutter-size: %dpx;\n" , |
130 | css_options->pixelScale, |
131 | css_options->gutterSize); |
132 | fprintf(f, "\t--shadow-mult: calc(var(--gutter-size) + var(--pixel-size));\n" ); |
133 | if (image->pixelFormat() == IMAGE_INDEXED) { |
134 | for (y = 0; y < 256; y++) { |
135 | fop->sequenceGetColor(y, &r, &g, &b); |
136 | fop->sequenceGetAlpha(y, &a); |
137 | fprintf(f, "\t--color-%d: " , y); |
138 | print_color(r, g, b, a); |
139 | fprintf(f, ";\n" ); |
140 | } |
141 | } |
142 | fprintf(f, "}\n\n" ); |
143 | } |
144 | |
145 | fprintf(f, ".pixel-art {\n" ); |
146 | fprintf(f, "\tposition: relative;\n" ); |
147 | fprintf(f, "\ttop: 0;\n" ); |
148 | fprintf(f, "\tleft: 0;\n" ); |
149 | if (css_options->withVars) { |
150 | fprintf(f, "\theight: var(--pixel-size);\n" ); |
151 | fprintf(f, "\twidth: var(--pixel-size);\n" ); |
152 | } |
153 | else { |
154 | fprintf(f, "\theight: %dpx;\n" , css_options->pixelScale); |
155 | fprintf(f, "\twidth: %dpx;\n" , css_options->pixelScale); |
156 | } |
157 | fprintf(f, "\tbox-shadow:\n" ); |
158 | int num_printed_pixels = 0; |
159 | switch (image->pixelFormat()) { |
160 | case IMAGE_RGB: { |
161 | for (y=0; y<image->height(); y++) { |
162 | for (x=0; x<image->width(); x++) { |
163 | c = get_pixel_fast<RgbTraits>(image.get(), x, y); |
164 | alpha = rgba_geta(c); |
165 | if (alpha != 0x00) { |
166 | print_shadow_color(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c), |
167 | alpha, num_printed_pixels>0); |
168 | num_printed_pixels ++; |
169 | } |
170 | } |
171 | fop->setProgress((float)y / (float)(image->height())); |
172 | } |
173 | break; |
174 | } |
175 | case IMAGE_GRAYSCALE: { |
176 | for (y=0; y<image->height(); y++) { |
177 | for (x=0; x<image->width(); x++) { |
178 | c = get_pixel_fast<GrayscaleTraits>(image.get(), x, y); |
179 | auto v = graya_getv(c); |
180 | alpha = graya_geta(c); |
181 | if (alpha != 0x00) { |
182 | print_shadow_color(x, y, v, v, v, alpha, num_printed_pixels>0); |
183 | num_printed_pixels ++; |
184 | } |
185 | } |
186 | fop->setProgress((float)y / (float)(image->height())); |
187 | } |
188 | break; |
189 | } |
190 | case IMAGE_INDEXED: { |
191 | unsigned char image_palette[256][4]; |
192 | for (y=0; !css_options->withVars && y<256; y++) { |
193 | fop->sequenceGetColor(y, &r, &g, &b); |
194 | image_palette[y][0] = r; |
195 | image_palette[y][1] = g; |
196 | image_palette[y][2] = b; |
197 | fop->sequenceGetAlpha(y, &a); |
198 | image_palette[y][3] = a; |
199 | } |
200 | color_t mask_color = -1; |
201 | if (fop->document()->sprite()->backgroundLayer() == NULL || |
202 | !fop->document()->sprite()->backgroundLayer()->isVisible()) { |
203 | mask_color = fop->document()->sprite()->transparentColor(); |
204 | } |
205 | for (y=0; y<image->height(); y++) { |
206 | for (x=0; x<image->width(); x++) { |
207 | c = get_pixel_fast<IndexedTraits>(image.get(), x, y); |
208 | if (c != mask_color) { |
209 | if (css_options->withVars) { |
210 | print_shadow_index(x, y, c, num_printed_pixels>0); |
211 | } |
212 | else { |
213 | print_shadow_color(x, y, |
214 | image_palette[c][0] & 0xff, |
215 | image_palette[c][1] & 0xff, |
216 | image_palette[c][2] & 0xff, |
217 | image_palette[c][3] & 0xff, |
218 | num_printed_pixels>0); |
219 | } |
220 | num_printed_pixels ++; |
221 | } |
222 | } |
223 | fop->setProgress((float)y / (float)(image->height())); |
224 | } |
225 | break; |
226 | } |
227 | } |
228 | fprintf(f, ";\n}\n" ); |
229 | if (ferror(f)) { |
230 | fop->setError("Error writing file.\n" ); |
231 | return false; |
232 | } |
233 | if (css_options->generateHtml) { |
234 | std::string html_filepath = fop->filename() + ".html" ; |
235 | FileHandle handle(open_file_with_exception_sync_on_close(html_filepath, "wb" )); |
236 | FILE* h = handle.get(); |
237 | fprintf(h, |
238 | "<html><head><link rel=\"stylesheet\" media=\"all\" " |
239 | "href=\"%s\"></head><body><div " |
240 | "class=\"pixel-art\"></div></body></html>" , |
241 | fop->filename().c_str()); |
242 | if (ferror(h)) { |
243 | fop->setError("Error writing html file.\n" ); |
244 | return false; |
245 | } |
246 | } |
247 | return true; |
248 | } |
249 | |
250 | #endif |
251 | |
252 | // Shows the CSS configuration dialog. |
253 | FormatOptionsPtr CssFormat::onAskUserForFormatOptions(FileOp* fop) |
254 | { |
255 | auto opts = fop->formatOptionsOfDocument<CssOptions>(); |
256 | |
257 | #ifdef ENABLE_UI |
258 | if (fop->context() && fop->context()->isUIAvailable()) { |
259 | try { |
260 | auto &pref = Preferences::instance(); |
261 | |
262 | if (pref.isSet(pref.css.pixelScale)) |
263 | opts->pixelScale = pref.css.pixelScale(); |
264 | |
265 | if (pref.isSet(pref.css.withVars)) |
266 | opts->withVars = pref.css.withVars(); |
267 | |
268 | if (pref.isSet(pref.css.generateHtml)) |
269 | opts->generateHtml = pref.css.generateHtml(); |
270 | |
271 | if (pref.css.showAlert()) { |
272 | app::gen::CssOptions win; |
273 | win.pixelScale()->setTextf("%d" , opts->pixelScale); |
274 | win.withVars()->setSelected(opts->withVars); |
275 | win.generateHtml()->setSelected(opts->generateHtml); |
276 | win.openWindowInForeground(); |
277 | |
278 | if (win.closer() == win.ok()) { |
279 | pref.css.showAlert(!win.dontShow()->isSelected()); |
280 | pref.css.pixelScale((int)win.pixelScale()->textInt()); |
281 | pref.css.withVars(win.withVars()->isSelected()); |
282 | pref.css.generateHtml(win.generateHtml()->isSelected()); |
283 | |
284 | opts->generateHtml = pref.css.generateHtml(); |
285 | opts->withVars = pref.css.withVars(); |
286 | opts->pixelScale = pref.css.pixelScale(); |
287 | } |
288 | else { |
289 | opts.reset(); |
290 | } |
291 | } |
292 | } |
293 | catch (std::exception &e) { |
294 | Console::showException(e); |
295 | return std::shared_ptr<CssOptions>(nullptr); |
296 | } |
297 | } |
298 | |
299 | #endif |
300 | return opts; |
301 | } |
302 | |
303 | } // namespace app |
304 | |