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
29namespace app {
30
31using namespace base;
32
33class 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
77FileFormat *CreateCssFormat()
78{
79 return new CssFormat;
80}
81
82bool CssFormat::onLoad(FileOp* fop)
83{
84 return false;
85}
86
87#ifdef ENABLE_SAVE
88
89bool 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.
253FormatOptionsPtr 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