mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-16 04:13:50 +00:00
Merge branch 'feature-css-export' into css-export
This commit is contained in:
commit
e813773445
@ -344,6 +344,12 @@
|
||||
<option id="bits_per_pixel" type="int" default="0" />
|
||||
<option id="compress" type="bool" default="true" />
|
||||
</section>
|
||||
<section id="css">
|
||||
<option id="show_alert" type="bool" default="true" />
|
||||
<option id="pixel_scale" type="int" default="1" />
|
||||
<option id="with_vars" type="bool" default="true" />
|
||||
<option id="generate_html" type="bool" default="false" />
|
||||
</section>
|
||||
<section id="webp">
|
||||
<option id="show_alert" type="bool" default="true" />
|
||||
<option id="loop" type="bool" default="true" />
|
||||
|
@ -1474,6 +1474,12 @@ title = TGA Options
|
||||
bits_per_pixel = Bits Per Pixel
|
||||
compress = Compress
|
||||
|
||||
[css_options]
|
||||
title = CSS Options
|
||||
pixel_scale = Pixel Scale
|
||||
with_vars = Use CSS3 Variables
|
||||
generate_html = Generate Sample HTML File
|
||||
|
||||
[timeline_conf]
|
||||
position = Position:
|
||||
left = &Left
|
||||
|
25
data/widgets/css_options.xml
Normal file
25
data/widgets/css_options.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2018 by Igara Studio S.A. -->
|
||||
<gui>
|
||||
<window id="css_options" text="@.title">
|
||||
<grid columns="2">
|
||||
<label text="@.pixel_scale" />
|
||||
<expr id="pixel_scale" magnet="true" cell_align="horizontal"/>
|
||||
|
||||
<check text="@.with_vars" id="with_vars" cell_hspan="2" />
|
||||
|
||||
<check text="@.generate_html" id="generate_html" cell_hspan="2" />
|
||||
|
||||
<separator horizontal="true" cell_hspan="2" />
|
||||
|
||||
<hbox cell_hspan="2">
|
||||
<check text="@general.dont_show" id="dont_show" tooltip="@general.dont_show_tooltip" />
|
||||
<boxfiller />
|
||||
</hbox>
|
||||
<box horizontal="true" homogeneous="true" cell_hspan="2" cell_align="right">
|
||||
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
||||
<button text="@general.cancel" closewindow="true" />
|
||||
</box>
|
||||
</grid>
|
||||
</window>
|
||||
</gui>
|
@ -444,6 +444,7 @@
|
||||
<check id="jpeg_options_alert" text="!jpeg" pref="jpeg.show_alert" />
|
||||
<check id="svg_options_alert" text="!svg" pref="svg.show_alert" />
|
||||
<check id="tga_options_alert" text="!tga" pref="tga.show_alert" />
|
||||
<check id="css_options_alert" text="!css" pref="css.show_alert" />
|
||||
</hbox>
|
||||
<separator horizontal="true" />
|
||||
<hbox>
|
||||
|
@ -132,6 +132,7 @@ set(file_formats
|
||||
file/pcx_format.cpp
|
||||
file/png_format.cpp
|
||||
file/svg_format.cpp
|
||||
file/css_format.cpp
|
||||
file/tga_format.cpp)
|
||||
if(WITH_WEBP_SUPPORT)
|
||||
list(APPEND file_formats file/webp_format.cpp)
|
||||
|
@ -990,6 +990,7 @@ private:
|
||||
gifOptionsAlert()->resetWithDefaultValue();
|
||||
jpegOptionsAlert()->resetWithDefaultValue();
|
||||
svgOptionsAlert()->resetWithDefaultValue();
|
||||
cssOptionsAlert()->resetWithDefaultValue();
|
||||
tgaOptionsAlert()->resetWithDefaultValue();
|
||||
}
|
||||
|
||||
|
299
src/app/file/css_format.cpp
Normal file
299
src/app/file/css_format.cpp
Normal file
@ -0,0 +1,299 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_format.h"
|
||||
#include "app/file/format_options.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/cfile.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/string.h"
|
||||
#include "doc/doc.h"
|
||||
#include "ui/window.h"
|
||||
|
||||
#include "css_options.xml.h"
|
||||
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace base;
|
||||
|
||||
class CssFormat : public FileFormat {
|
||||
class CssOptions : public FormatOptions {
|
||||
public:
|
||||
CssOptions(): pixelScale(1), gutterSize(0),
|
||||
generateHtml(false), withVars(true) {}
|
||||
int pixelScale;
|
||||
int gutterSize;
|
||||
bool generateHtml;
|
||||
bool withVars;
|
||||
};
|
||||
|
||||
const char* onGetName() const override {
|
||||
return "css";
|
||||
}
|
||||
|
||||
void onGetExtensions(base::paths& exts) const override {
|
||||
exts.push_back("css");
|
||||
}
|
||||
|
||||
dio::FileFormat onGetDioFormat() const override {
|
||||
return dio::FileFormat::CSS_STYLE;
|
||||
}
|
||||
|
||||
int onGetFlags() const override {
|
||||
return
|
||||
FILE_SUPPORT_SAVE |
|
||||
FILE_SUPPORT_RGB |
|
||||
FILE_SUPPORT_RGBA |
|
||||
FILE_SUPPORT_GRAY |
|
||||
FILE_SUPPORT_GRAYA |
|
||||
FILE_SUPPORT_INDEXED |
|
||||
FILE_SUPPORT_SEQUENCES |
|
||||
FILE_SUPPORT_GET_FORMAT_OPTIONS |
|
||||
FILE_SUPPORT_PALETTE_WITH_ALPHA;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop) override;
|
||||
#ifdef ENABLE_SAVE
|
||||
bool onSave(FileOp* fop) override;
|
||||
#endif
|
||||
FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) override;
|
||||
};
|
||||
|
||||
FileFormat *CreateCssFormat()
|
||||
{
|
||||
return new CssFormat;
|
||||
}
|
||||
|
||||
bool CssFormat::onLoad(FileOp *fop) { return false; }
|
||||
|
||||
#ifdef ENABLE_SAVE
|
||||
|
||||
bool CssFormat::onSave(FileOp *fop)
|
||||
{
|
||||
const Image* image = fop->sequenceImage();
|
||||
int x, y, c, r, g, b, a, alpha;
|
||||
const auto css_options = std::static_pointer_cast<CssOptions>(fop->formatOptions());
|
||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||
FILE* f = handle.get();
|
||||
auto print_color = [f](int r, int g, int b, int a) {
|
||||
if (a == 255) {
|
||||
fprintf(f, "#%02X%02X%02X", r, g, b);
|
||||
} else {
|
||||
fprintf(f, "rgba(%d, %d, %d, %d)", r, g, b, a);
|
||||
}
|
||||
};
|
||||
auto print_shadow_color = [f, css_options, print_color](int x, int y, int r,
|
||||
int g, int b, int a,
|
||||
bool comma = true) {
|
||||
fprintf(f, comma?",\n":"\n");
|
||||
if (css_options->withVars) {
|
||||
fprintf(f, "\tcalc(%d*var(--shadow-mult)) calc(%d*var(--shadow-mult)) var(--blur) var(--spread) ",
|
||||
x, y);
|
||||
}
|
||||
else {
|
||||
int x_loc = x * (css_options->pixelScale + css_options->gutterSize);
|
||||
int y_loc = y * (css_options->pixelScale + css_options->gutterSize);
|
||||
fprintf(f, "%dpx %dpx ", x_loc, y_loc);
|
||||
}
|
||||
print_color(r, g, b, a);
|
||||
};
|
||||
auto print_shadow_index = [f, css_options](int x, int y, int i, bool comma=true) {
|
||||
fprintf(f, comma?",\n":"\n");
|
||||
fprintf(f, "\tcalc(%d*var(--shadow-mult)) calc(%d*var(--shadow-mult)) var(--blur) var(--spread) var(--color-%d)",
|
||||
x, y, i);
|
||||
};
|
||||
if (css_options->withVars) {
|
||||
fprintf(f, ":root {\n\t--blur: 0px;\n\t--spread: 0px;\n\t--pixel-size: %dpx;\n\t--gutter-size: %dpx;\n",
|
||||
css_options->pixelScale, css_options->gutterSize);
|
||||
fprintf(f, "\t--shadow-mult: calc(var(--gutter-size) + var(--pixel-size));\n");
|
||||
if (image->pixelFormat() == IMAGE_INDEXED) {
|
||||
for (y = 0; y < 256; y++) {
|
||||
fop->sequenceGetColor(y, &r, &g, &b);
|
||||
fop->sequenceGetAlpha(y, &a);
|
||||
fprintf(f, "\t--color-%d: ", y);
|
||||
print_color(r, g, b, a);
|
||||
fprintf(f, ";\n");
|
||||
}
|
||||
}
|
||||
fprintf(f, "}\n\n");
|
||||
}
|
||||
|
||||
fprintf(f, ".pixel-art {\n");
|
||||
fprintf(f, "\tposition: relative;\n");
|
||||
fprintf(f, "\ttop: 0;\n");
|
||||
fprintf(f, "\tleft: 0;\n");
|
||||
if (css_options->withVars) {
|
||||
fprintf(f, "\theight: var(--pixel-size);\n");
|
||||
fprintf(f, "\twidth: var(--pixel-size);\n");
|
||||
}
|
||||
else {
|
||||
fprintf(f, "\theight: %dpx;\n", css_options->pixelScale);
|
||||
fprintf(f, "\twidth: %dpx;\n", css_options->pixelScale);
|
||||
}
|
||||
fprintf(f, "\tbox-shadow:\n");
|
||||
int num_printed_pixels = 0;
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_RGB: {
|
||||
for (y=0; y<image->height(); y++) {
|
||||
for (x=0; x<image->width(); x++) {
|
||||
c = get_pixel_fast<RgbTraits>(image, x, y);
|
||||
alpha = rgba_geta(c);
|
||||
if (alpha != 0x00) {
|
||||
print_shadow_color(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c),
|
||||
alpha, num_printed_pixels>0);
|
||||
num_printed_pixels ++;
|
||||
}
|
||||
}
|
||||
fop->setProgress((float)y / (float)(image->height()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IMAGE_GRAYSCALE: {
|
||||
for (y=0; y<image->height(); y++) {
|
||||
for (x=0; x<image->width(); x++) {
|
||||
c = get_pixel_fast<GrayscaleTraits>(image, x, y);
|
||||
auto v = graya_getv(c);
|
||||
alpha = graya_geta(c);
|
||||
if (alpha != 0x00) {
|
||||
print_shadow_color(x, y, v, v, v, alpha, num_printed_pixels>0);
|
||||
num_printed_pixels ++;
|
||||
}
|
||||
}
|
||||
fop->setProgress((float)y / (float)(image->height()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IMAGE_INDEXED: {
|
||||
unsigned char image_palette[256][4];
|
||||
for (y=0; !css_options->withVars && y<256; y++) {
|
||||
fop->sequenceGetColor(y, &r, &g, &b);
|
||||
image_palette[y][0] = r;
|
||||
image_palette[y][1] = g;
|
||||
image_palette[y][2] = b;
|
||||
fop->sequenceGetAlpha(y, &a);
|
||||
image_palette[y][3] = a;
|
||||
}
|
||||
color_t mask_color = -1;
|
||||
if (fop->document()->sprite()->backgroundLayer() == NULL ||
|
||||
!fop->document()->sprite()->backgroundLayer()->isVisible()) {
|
||||
mask_color = fop->document()->sprite()->transparentColor();
|
||||
}
|
||||
for (y=0; y<image->height(); y++) {
|
||||
for (x=0; x<image->width(); x++) {
|
||||
c = get_pixel_fast<IndexedTraits>(image, x, y);
|
||||
if (c != mask_color) {
|
||||
if (css_options->withVars) {
|
||||
print_shadow_index(x, y, c, num_printed_pixels>0);
|
||||
}
|
||||
else {
|
||||
print_shadow_color(x, y,
|
||||
image_palette[c][0] & 0xff,
|
||||
image_palette[c][1] & 0xff,
|
||||
image_palette[c][2] & 0xff,
|
||||
image_palette[c][3] & 0xff,
|
||||
num_printed_pixels>0);
|
||||
}
|
||||
num_printed_pixels ++;
|
||||
}
|
||||
}
|
||||
fop->setProgress((float)y / (float)(image->height()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(f, ";\n}\n");
|
||||
if (ferror(f)) {
|
||||
fop->setError("Error writing file.\n");
|
||||
return false;
|
||||
}
|
||||
if (css_options->generateHtml) {
|
||||
std::string html_filepath = fop->filename() + ".html";
|
||||
FileHandle handle(open_file_with_exception_sync_on_close(html_filepath, "wb"));
|
||||
FILE* h = handle.get();
|
||||
fprintf(h,
|
||||
"<html><head><link rel=\"stylesheet\" media=\"all\" "
|
||||
"href=\"%s\"></head><body><div "
|
||||
"class=\"pixel-art\"></div></body></html>",
|
||||
fop->filename().c_str());
|
||||
if (ferror(h)) {
|
||||
fop->setError("Error writing html file.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Shows the CSS configuration dialog.
|
||||
FormatOptionsPtr CssFormat::onAskUserForFormatOptions(FileOp *fop)
|
||||
{
|
||||
auto opts = fop->formatOptionsOfDocument<CssOptions>();
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
if (fop->context() && fop->context()->isUIAvailable())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto &pref = Preferences::instance();
|
||||
|
||||
if (pref.isSet(pref.css.pixelScale))
|
||||
opts->pixelScale = pref.css.pixelScale();
|
||||
|
||||
if (pref.isSet(pref.css.withVars))
|
||||
opts->withVars = pref.css.withVars();
|
||||
|
||||
if (pref.isSet(pref.css.generateHtml))
|
||||
opts->generateHtml = pref.css.generateHtml();
|
||||
|
||||
if (pref.css.showAlert())
|
||||
{
|
||||
app::gen::CssOptions win;
|
||||
win.pixelScale()->setTextf("%d", opts->pixelScale);
|
||||
win.withVars()->setSelected(opts->withVars);
|
||||
win.generateHtml()->setSelected(opts->generateHtml);
|
||||
win.openWindowInForeground();
|
||||
|
||||
if (win.closer() == win.ok())
|
||||
{
|
||||
pref.css.showAlert(!win.dontShow()->isSelected());
|
||||
pref.css.pixelScale((int)win.pixelScale()->textInt());
|
||||
pref.css.withVars(win.withVars()->isSelected());
|
||||
pref.css.generateHtml(win.generateHtml()->isSelected());
|
||||
|
||||
opts->generateHtml = pref.css.generateHtml();
|
||||
opts->withVars = pref.css.withVars();
|
||||
opts->pixelScale = pref.css.pixelScale();
|
||||
}
|
||||
else
|
||||
{
|
||||
opts.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
Console::showException(e);
|
||||
return std::shared_ptr<CssOptions>(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
return opts;
|
||||
}
|
||||
|
||||
} // namespace app
|
@ -30,6 +30,7 @@ extern FileFormat* CreatePcxFormat();
|
||||
extern FileFormat* CreatePngFormat();
|
||||
extern FileFormat* CreateSvgFormat();
|
||||
extern FileFormat* CreateTgaFormat();
|
||||
extern FileFormat* CreateCssFormat();
|
||||
|
||||
#ifdef ASEPRITE_WITH_WEBP_SUPPORT
|
||||
extern FileFormat* CreateWebPFormat();
|
||||
@ -65,6 +66,7 @@ FileFormatsManager::FileFormatsManager()
|
||||
registerFormat(CreatePngFormat());
|
||||
registerFormat(CreateSvgFormat());
|
||||
registerFormat(CreateTgaFormat());
|
||||
registerFormat(CreateCssFormat());
|
||||
|
||||
#ifdef ASEPRITE_WITH_WEBP_SUPPORT
|
||||
registerFormat(CreateWebPFormat());
|
||||
|
@ -141,6 +141,9 @@ FileFormat detect_format_by_file_extension(const std::string& filename)
|
||||
if (ext == "tga")
|
||||
return FileFormat::TARGA_IMAGE;
|
||||
|
||||
if (ext == "css")
|
||||
return FileFormat::CSS_STYLE;
|
||||
|
||||
if (ext == "webp")
|
||||
return FileFormat::WEBP_ANIMATION;
|
||||
|
||||
|
@ -31,6 +31,7 @@ enum class FileFormat {
|
||||
SVG_IMAGE,
|
||||
TARGA_IMAGE,
|
||||
WEBP_ANIMATION,
|
||||
CSS_STYLE,
|
||||
};
|
||||
|
||||
} // namespace dio
|
||||
|
Loading…
Reference in New Issue
Block a user