Add support for animated webp files (fix #273)

This commit is contained in:
David Capello 2018-03-26 14:08:08 -03:00
parent b1823ab558
commit 6de103128b
8 changed files with 410 additions and 327 deletions

View File

@ -65,7 +65,6 @@ option(USE_SHARED_JPEGLIB "Use your installed copy of jpeglib" off)
option(USE_SHARED_ZLIB "Use your installed copy of zlib" off)
option(USE_SHARED_LIBPNG "Use your installed copy of libpng" off)
option(USE_SHARED_LIBLOADPNG "Use your installed copy of libloadpng" off)
option(USE_SHARED_LIBWEBP "Use your installed copy of libwebp" off)
option(USE_SHARED_TINYXML "Use your installed copy of tinyxml" off)
option(USE_SHARED_PIXMAN "Use your installed copy of pixman" off)
option(USE_SHARED_FREETYPE "Use shared FreeType library" off)
@ -221,21 +220,8 @@ add_definitions(-DPNG_NO_MMX_CODE) # Do not use MMX optimizations in PNG code
# libwebp
if(WITH_WEBP_SUPPORT)
if(USE_SHARED_LIBWEBP)
find_package(PkgConfig)
pkg_check_modules(WEBP libwebp)
if(NOT WEBP_FOUND)
message(FATAL_ERROR "libwebp not found")
endif()
else()
# Skia already includes webp library
if(NOT USE_SKIA_BACKEND)
set(WEBP_LIBRARIES webp)
else()
set(WEBP_LIBRARIES "")
endif()
set(WEBP_INCLUDE_DIR ${LIBWEBP_DIR}/src)
endif()
set(WEBP_LIBRARIES webp webpdemux webpmux)
set(WEBP_INCLUDE_DIR ${LIBWEBP_DIR}/src)
include_directories(${WEBP_INCLUDE_DIR})
endif()

View File

@ -276,6 +276,15 @@
<option id="show_alert" type="bool" default="true" />
<option id="quality" type="double" default="1.0" migrate="JPEG.Quality" />
</section>
<section id="webp">
<option id="show_alert" type="bool" default="true" />
<option id="loop" type="bool" default="true" />
<option id="type" type="int" default="0" />
<option id="quality" type="int" default="100" migrate="WEBP.Quality" />
<option id="compression" type="int" default="6" migrate="WEBP.Compression" />
<option id="image_hint" type="int" default="0" migrate="WEBP.ImageHint" />
<option id="image_preset" type="int" default="0" migrate="WEBP.ImagePreset" />
</section>
<section id="hue_saturation">
<option id="mode" type="HueSaturationMode" default="HueSaturationMode::HSL" />
</section>

View File

@ -1199,8 +1199,10 @@ color = Color:
[webp_options]
title = WebP Options
save_as = Save as:
animation_loop = Animation &Loop
type = Type:
simple_webp = Simple: Good Lossless Compression
lossless_webp = Lossless WebP
lossless_webp_tooltip = Save in simple WebP lossless format.
compression = Compression:
image_hint = Image Hint:
image_hint_default = Default
@ -1208,7 +1210,6 @@ image_hint_picture = Picture
image_hint_photo = Photo
image_hint_graph = Graph
lossy_webp = Lossy WebP
lossy_webp_tooltip = Save in simple WebP lossy format.
quality = Quality:
image_preset = Image Preset:
image_preset_default = Default

View File

@ -1,41 +1,52 @@
<!-- Aseprite -->
<!-- Copyright (C) 2016-2018 by David Capello -->
<!-- Copyright (C) 2015 by Gabriel Rauter -->
<!-- Copyright (C) 2016 by David Capello -->
<gui>
<window id="webp_options" text="@.title">
<vbox>
<label text="@.save_as" />
<radio group="1" text="@.lossless_webp" id="lossless" tooltip="@.lossless_webp_tooltip" />
<check text="@.animation_loop" id="loop" />
<hbox>
<label width="55" text="@.compression" />
<slider min="0" max="9" id="compression" cell_align="horizontal" width="128" />
</hbox>
<hbox>
<label width="55" text="@.image_hint" />
<combobox width="128" id="image_hint">
<listitem text="@.image_hint_default" value="0" />
<listitem text="@.image_hint_picture" value="1" />
<listitem text="@.image_hint_photo" value="2" />
<listitem text="@.image_hint_graph" value="3" />
<label text="@.type" />
<combobox id="type" cell_align="horizontal" cell_hspan="2">
<listitem text="@.simple_webp" value="0" />
<listitem text="@.lossless_webp" value="1" />
<listitem text="@.lossy_webp" value="2" />
</combobox>
</hbox>
<vbox id="lossless_options">
<hbox>
<label width="55" text="@.compression" />
<slider min="0" max="9" id="compression" cell_align="horizontal" width="128" />
</hbox>
<hbox>
<label width="55" text="@.image_hint" />
<combobox width="128" id="image_hint">
<listitem text="@.image_hint_default" value="0" />
<listitem text="@.image_hint_picture" value="1" />
<listitem text="@.image_hint_photo" value="2" />
<listitem text="@.image_hint_graph" value="3" />
</combobox>
</hbox>
</vbox>
<vbox id="lossy_options">
<hbox>
<label width="55" text="@.quality" />
<slider min="0" max="100" id="quality" cell_align="horizontal" width="128" />
</hbox>
<hbox>
<label width="55" text="@.image_preset" />
<combobox width="128" id="image_preset">
<listitem text="@.image_preset_default" value="0" />
<listitem text="@.image_preset_picture" value="1" />
<listitem text="@.image_preset_photo" value="2" />
<listitem text="@.image_preset_drawing" value="3" />
<listitem text="@.image_preset_icon" value="4" />
<listitem text="@.image_preset_text" value="5" />
</combobox>
</hbox>
</vbox>
<separator horizontal="true" />
<radio group="1" text="@.lossy_webp" id="lossy" tooltip="@.lossy_webp_tooltip" />
<hbox>
<label width="55" text="@.quality" />
<slider min="0" max="100" id="quality" cell_align="horizontal" width="128" />
</hbox>
<hbox>
<label width="55" text="@.image_preset" />
<combobox width="128" id="image_preset">
<listitem text="@.image_preset_default" value="0" />
<listitem text="@.image_preset_picture" value="1" />
<listitem text="@.image_preset_photo" value="2" />
<listitem text="@.image_preset_drawing" value="3" />
<listitem text="@.image_preset_icon" value="4" />
<listitem text="@.image_preset_text" value="5" />
</combobox>
</hbox>
<hbox>
<boxfiller />
<hbox homogeneous="true">

View File

@ -18,9 +18,12 @@
#include "app/file/format_options.h"
#include "app/file/webp_options.h"
#include "app/ini_file.h"
#include "base/file_handle.h"
#include "app/pref/preferences.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "render/render.h"
#include "webp_options.xml.h"
@ -29,9 +32,8 @@
#include <algorithm>
#include <map>
// Include webp libraries
#include <webp/decode.h>
#include <webp/encode.h>
#include <webp/demux.h>
#include <webp/mux.h>
namespace app {
@ -57,7 +59,7 @@ class WebPFormat : public FileFormat {
FILE_SUPPORT_SAVE |
FILE_SUPPORT_RGB |
FILE_SUPPORT_RGBA |
FILE_SUPPORT_SEQUENCES |
FILE_SUPPORT_FRAMES |
FILE_SUPPORT_GET_FORMAT_OPTIONS;
}
@ -65,8 +67,7 @@ class WebPFormat : public FileFormat {
#ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override;
#endif
base::SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) override;
base::SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) override;
};
FileFormat* CreateWebPFormat()
@ -106,71 +107,118 @@ bool WebPFormat::onLoad(FileOp* fop)
}
std::vector<uint8_t> buf(len);
uint8_t* data = &buf.front();
if (!fread(data, sizeof(uint8_t), len, fp)) {
if (fread(&buf[0], 1, buf.size(), fp) != buf.size()) {
fop->setError("Error moving the whole WebP file to memory\n");
return false;
}
WebPData webp_data;
WebPDataInit(&webp_data);
webp_data.bytes = &buf[0];
webp_data.size = buf.size();
WebPAnimDecoderOptions dec_options;
WebPAnimDecoderOptionsInit(&dec_options);
dec_options.color_mode = MODE_RGBA;
WebPAnimDecoder* dec = WebPAnimDecoderNew(&webp_data, &dec_options);
if (dec == nullptr) {
fop->setError("Error parsing WebP image\n");
return false;
}
WebPAnimInfo anim_info;
if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
fop->setError("Error getting global info about the WebP animation\n");
return false;
}
WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config)) {
fop->setError("WebP decoder cannot load this webp file version\n");
return false;
}
if (WebPGetFeatures(data, len, &config.input) != VP8_STATUS_OK) {
fop->setError("Bad bitstream in WebP file\n");
return false;
}
fop->sequenceSetHasAlpha(config.input.has_alpha != 0);
Image* image = fop->sequenceImage(IMAGE_RGB, config.input.width, config.input.height);
config.output.colorspace = MODE_RGBA;
config.output.u.RGBA.rgba = (uint8_t*)image->getPixelAddress(0, 0);
config.output.u.RGBA.stride = config.input.width * sizeof(uint32_t);
config.output.u.RGBA.size = config.input.width * config.input.height * sizeof(uint32_t);
config.output.is_external_memory = 1;
WebPIDecoder* idec = WebPIDecode(NULL, 0, &config);
if (idec == NULL) {
fop->setError("Error creating WebP decoder\n");
return false;
}
long bytes_remaining = len;
long bytes_read = std::max(4l, len/100l);
while (bytes_remaining > 0) {
VP8StatusCode status = WebPIAppend(idec, data, bytes_read);
if (status == VP8_STATUS_OK ||
status == VP8_STATUS_SUSPENDED) {
bytes_remaining -= bytes_read;
data += bytes_read;
if (bytes_remaining < bytes_read)
bytes_read = bytes_remaining;
fop->setProgress(1.0f - ((float)std::max(bytes_remaining, 0l)/(float)len));
WebPInitDecoderConfig(&config);
if (WebPGetFeatures(webp_data.bytes, webp_data.size, &config.input)) {
if (!fop->formatOptions()) {
base::SharedPtr<WebPOptions> opts(new WebPOptions());
WebPOptions::Type type = WebPOptions::Simple;
switch (config.input.format) {
case 0: type = WebPOptions::Simple; break;
case 1: type = WebPOptions::Lossy; break;
case 2: type = WebPOptions::Lossless; break;
}
opts->setType(type);
fop->setFormatOptions(opts);
}
else {
fop->setError("Error decoding WebP data: %s\n", getDecoderErrorMessage(status));
WebPIDelete(idec);
WebPFreeDecBuffer(&config.output);
}
else {
config.input.has_alpha = false;
}
const int w = anim_info.canvas_width;
const int h = anim_info.canvas_height;
Sprite* sprite = new Sprite(IMAGE_RGB, w, h, 256);
LayerImage* layer = new LayerImage(sprite);
sprite->root()->addLayer(layer);
sprite->setTotalFrames(anim_info.frame_count);
for (frame_t f=0; f<anim_info.frame_count; ++f) {
ImageRef image(Image::create(IMAGE_RGB, w, h));
Cel* cel = new Cel(f, image);
layer->addCel(cel);
}
bool has_alpha = config.input.has_alpha;
frame_t f = 0;
int prev_timestamp = 0;
while (WebPAnimDecoderHasMoreFrames(dec)) {
uint8_t* frame_rgba;
int frame_timestamp = 0;
if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &frame_timestamp)) {
fop->setError("Error loading WebP frame\n");
return false;
}
Cel* cel = layer->cel(f);
if (cel) {
memcpy(cel->image()->getPixelAddress(0, 0),
frame_rgba, h*w*sizeof(uint32_t));
if (!has_alpha) {
const uint32_t* src = (const uint32_t*)frame_rgba;
const uint32_t* src_end = src + w*h;
while (src < src_end) {
const uint8_t alpha = (*src >> 24) & 0xff;
if (alpha < 255) {
has_alpha = true;
break;
}
++src;
}
}
}
sprite->setFrameDuration(f, frame_timestamp - prev_timestamp);
prev_timestamp = frame_timestamp;
fop->setProgress(double(f) / double(anim_info.frame_count));
if (fop->isStop())
break;
}
if (!fop->formatOptions()) {
base::SharedPtr<WebPOptions> webPOptions(new WebPOptions());
webPOptions->setLossless(std::min(config.input.format - 1, 1));
fop->setFormatOptions(webPOptions);
++f;
}
WebPAnimDecoderReset(dec);
WebPIDelete(idec);
WebPFreeDecBuffer(&config.output);
if (!has_alpha)
layer->configureAsBackground();
WebPAnimDecoderDelete(dec);
// Don't use WebPDataClear because webp_data use a std::vector<> data.
//WebPDataClear(&webp_data);
if (fop->isStop())
return false;
fop->createDocument(sprite);
return true;
}
@ -179,145 +227,138 @@ bool WebPFormat::onLoad(FileOp* fop)
struct WriterData {
FILE* fp;
FileOp* fop;
frame_t f, n;
double progress;
WriterData(FILE* fp, FileOp* fop, frame_t f, frame_t n, double progress)
: fp(fp), fop(fop), f(f), n(n), progress(progress) { }
};
class ScopedWebPPicture {
public:
ScopedWebPPicture(WebPPicture& pic) : m_pic(pic) {
}
~ScopedWebPPicture() {
WebPPictureFree(&m_pic);
}
private:
WebPPicture& m_pic;
};
const char* getEncoderErrorMessage(WebPEncodingError errorCode) {
switch (errorCode) {
case VP8_ENC_OK: return ""; break;
case VP8_ENC_ERROR_OUT_OF_MEMORY: return "memory error allocating objects"; break;
case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY: return "memory error while flushing bits"; break;
case VP8_ENC_ERROR_NULL_PARAMETER: return "a pointer parameter is NULL"; break;
case VP8_ENC_ERROR_INVALID_CONFIGURATION: return "configuration is invalid"; break;
case VP8_ENC_ERROR_BAD_DIMENSION: return "picture has invalid width/height"; break;
case VP8_ENC_ERROR_PARTITION0_OVERFLOW: return "partition is bigger than 512k"; break;
case VP8_ENC_ERROR_PARTITION_OVERFLOW: return "partition is bigger than 16M"; break;
case VP8_ENC_ERROR_BAD_WRITE: return "error while flushing bytes"; break;
case VP8_ENC_ERROR_FILE_TOO_BIG: return "file is bigger than 4G"; break;
case VP8_ENC_ERROR_USER_ABORT: return "abort request by user"; break;
case VP8_ENC_ERROR_LAST: return "abort request by user"; break;
default: return ""; break;
}
}
#if WEBP_ENCODER_ABI_VERSION < 0x0203
#define MAX_LEVEL 9
// Mapping between -z level and -m / -q parameter settings.
static const struct {
uint8_t method_;
uint8_t quality_;
} kLosslessPresets[MAX_LEVEL + 1] = {
{ 0, 0 }, { 1, 20 }, { 2, 25 }, { 3, 30 }, { 3, 50 },
{ 4, 50 }, { 4, 75 }, { 4, 90 }, { 5, 90 }, { 6, 100 }
};
int WebPConfigLosslessPreset(WebPConfig* config, int level) {
if (config == NULL || level < 0 || level > MAX_LEVEL) return 0;
config->lossless = 1;
config->method = kLosslessPresets[level].method_;
config->quality = kLosslessPresets[level].quality_;
return 1;
}
#endif
static int ProgressReport(int percent, const WebPPicture* const pic)
static int progress_report(int percent, const WebPPicture* pic)
{
FileOp* fop = ((WriterData*)pic->custom_ptr)->fop;
fop->setProgress((double)percent/(double)100);
auto wd = (WriterData*)pic->user_data;
FileOp* fop = wd->fop;
double newProgress = (double(wd->f) + double(percent)/100.0) / double(wd->n);
wd->progress = MAX(wd->progress, newProgress);
wd->progress = MID(0.0, wd->progress, 1.0);
fop->setProgress(wd->progress);
if (fop->isStop())
return false;
else
return true;
}
static int FileWriter(const uint8_t* data, size_t data_size, const WebPPicture* const pic)
{
return (data_size ? (fwrite(data, data_size, 1, ((WriterData*)pic->custom_ptr)->fp) == 1) : 1);
}
bool WebPFormat::onSave(FileOp* fop)
{
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
FILE* fp = handle.get();
WriterData wd = { fp, fop };
const Sprite* sprite = fop->document()->sprite();
const int w = sprite->width();
const int h = sprite->height();
const Image* image = fop->sequenceImage();
if (image->width() > WEBP_MAX_DIMENSION ||
image->height() > WEBP_MAX_DIMENSION) {
if (w > WEBP_MAX_DIMENSION ||
h > WEBP_MAX_DIMENSION) {
fop->setError("WebP format cannot store %dx%d images. The maximum allowed size is %dx%d\n",
image->width(), image->height(),
WEBP_MAX_DIMENSION, WEBP_MAX_DIMENSION);
w, h, WEBP_MAX_DIMENSION, WEBP_MAX_DIMENSION);
return false;
}
base::SharedPtr<WebPOptions> webp_options = fop->formatOptions();
base::SharedPtr<WebPOptions> opts = fop->formatOptions();
WebPConfig config;
WebPConfigInit(&config);
if (webp_options->lossless()) {
if (!(WebPConfigInit(&config) &&
WebPConfigLosslessPreset(&config, webp_options->getMethod()))) {
fop->setError("Error in WebP configuration\n");
return false;
}
config.image_hint = webp_options->getImageHint();
}
else {
if (!WebPConfigPreset(&config, webp_options->getImagePreset(), static_cast<float>(webp_options->getQuality()))) {
fop->setError("Error in WebP configuration preset\n");
return false;
}
switch (opts->type()) {
case WebPOptions::Simple:
case WebPOptions::Lossless:
if (!WebPConfigLosslessPreset(&config,
opts->compression())) {
fop->setError("Error in WebP configuration\n");
return false;
}
config.image_hint = opts->imageHint();
break;
case WebPOptions::Lossy:
if (!WebPConfigPreset(&config,
opts->imagePreset(),
static_cast<float>(opts->quality()))) {
fop->setError("Error in WebP configuration preset\n");
return false;
}
break;
}
if (!WebPValidateConfig(&config)) {
fop->setError("Error validating WebP encoder configuration\n");
return false;
}
WebPAnimEncoderOptions enc_options;
WebPAnimEncoderOptionsInit(&enc_options);
enc_options.anim_params.loop_count =
(opts->loop() ? 0: // 0 = infinite
1); // 1 = loop once
ImageRef image(Image::create(IMAGE_RGB, w, h));
render::Render render;
WriterData wd(fp, fop, 0, sprite->totalFrames(), 0.0);
WebPPicture pic;
if (!WebPPictureInit(&pic)) {
fop->setError("Error encoding WebP picture, version mismatch\n");
return false;
}
pic.width = image->width();
pic.height = image->height();
if (webp_options->lossless()) {
pic.use_argb = true;
}
if (!WebPPictureAlloc(&pic)) {
fop->setError("Not enough memory to allocate a WebP picture\n");
return false;
}
ScopedWebPPicture scopedPic(pic); // Calls WebPPictureFree automatically
if (!WebPPictureImportRGBA(&pic, (uint8_t*)image->getPixelAddress(0, 0), image->width() * sizeof(uint32_t))) {
fop->setError("Error converting RGBA data into a WebP picture\n");
return false;
}
pic.writer = FileWriter;
pic.custom_ptr = &wd;
pic.progress_hook = ProgressReport;
if (!WebPEncode(&config, &pic)) {
fop->setError("Error encoding image into WebP: %s\n",
getEncoderErrorMessage(pic.error_code));
WebPPictureInit(&pic);
pic.width = w;
pic.height = h;
pic.use_argb = true;
pic.argb = (uint32_t*)image->getPixelAddress(0, 0);
pic.argb_stride = w;
pic.user_data = &wd;
pic.progress_hook = progress_report;
WebPAnimEncoder* enc = WebPAnimEncoderNew(sprite->width(),
sprite->height(),
&enc_options);
int timestamp_ms = 0;
for (frame_t f=0; f<sprite->totalFrames(); ++f) {
// Render the frame in the bitmap
render.renderSprite(image.get(), sprite, f);
// Switch R <-> B channels because WebPAnimEncoderAssemble()
// expects MODE_BGRA pictures.
{
LockImageBits<RgbTraits> bits(image.get(), Image::ReadWriteLock);
auto it = bits.begin(), end = bits.end();
for (; it != end; ++it) {
auto c = *it;
*it = rgba(rgba_getb(c), // Use blue in red channel
rgba_getg(c),
rgba_getr(c), // Use red in blue channel
rgba_geta(c));
}
}
if (!WebPAnimEncoderAdd(enc, &pic, timestamp_ms, &config)) {
if (!fop->isStop()) {
fop->setError("Error saving frame %d info\n", f);
return false;
}
else
return true;
}
timestamp_ms += sprite->frameDuration(f);
wd.f = f;
}
WebPAnimEncoderAdd(enc, nullptr, timestamp_ms, nullptr);
WebPData webp_data;
WebPDataInit(&webp_data);
WebPAnimEncoderAssemble(enc, &webp_data);
WebPAnimEncoderDelete(enc);
if (fwrite(webp_data.bytes, 1, webp_data.size, fp) != webp_data.size) {
fop->setError("Error saving content into file\n");
return false;
}
WebPDataClear(&webp_data);
return true;
}
@ -326,58 +367,101 @@ bool WebPFormat::onSave(FileOp* fop)
// Shows the WebP configuration dialog.
base::SharedPtr<FormatOptions> WebPFormat::onGetFormatOptions(FileOp* fop)
{
base::SharedPtr<WebPOptions> webp_options;
base::SharedPtr<WebPOptions> opts;
if (fop->document()->getFormatOptions())
webp_options = base::SharedPtr<WebPOptions>(fop->document()->getFormatOptions());
opts = base::SharedPtr<WebPOptions>(fop->document()->getFormatOptions());
if (!webp_options)
webp_options.reset(new WebPOptions);
if (!opts)
opts.reset(new WebPOptions);
// Non-interactive mode
if (!fop->context() ||
!fop->context()->isUIAvailable())
return webp_options;
return opts;
try {
// Configuration parameters
webp_options->setQuality(get_config_int("WEBP", "Quality", webp_options->getQuality()));
webp_options->setMethod(get_config_int("WEBP", "Compression", webp_options->getMethod()));
webp_options->setImageHint(get_config_int("WEBP", "ImageHint", webp_options->getImageHint()));
webp_options->setImagePreset(get_config_int("WEBP", "ImagePreset", webp_options->getImagePreset()));
auto& pref = Preferences::instance();
// Load the window to ask to the user the WebP options he wants.
if (pref.isSet(pref.webp.loop))
opts->setLoop(pref.webp.loop());
app::gen::WebpOptions win;
win.lossless()->setSelected(webp_options->lossless());
win.lossy()->setSelected(!webp_options->lossless());
win.quality()->setValue(static_cast<int>(webp_options->getQuality()));
win.compression()->setValue(webp_options->getMethod());
win.imageHint()->setSelectedItemIndex(webp_options->getImageHint());
win.imagePreset()->setSelectedItemIndex(webp_options->getImagePreset());
if (pref.isSet(pref.webp.type))
opts->setType(WebPOptions::Type(pref.webp.type()));
win.openWindowInForeground();
if (win.closer() == win.ok()) {
webp_options->setQuality(win.quality()->getValue());
webp_options->setMethod(win.compression()->getValue());
webp_options->setLossless(win.lossless()->isSelected());
webp_options->setImageHint(base::convert_to<int>(win.imageHint()->getValue()));
webp_options->setImagePreset(base::convert_to<int>(win.imagePreset()->getValue()));
set_config_int("WEBP", "Quality", webp_options->getQuality());
set_config_int("WEBP", "Compression", webp_options->getMethod());
set_config_int("WEBP", "ImageHint", webp_options->getImageHint());
set_config_int("WEBP", "ImagePreset", webp_options->getImagePreset());
}
else {
webp_options.reset(NULL);
switch (opts->type()) {
case WebPOptions::Lossless:
if (pref.isSet(pref.webp.compression)) opts->setCompression(pref.webp.compression());
if (pref.isSet(pref.webp.imageHint)) opts->setImageHint(WebPImageHint(pref.webp.imageHint()));
break;
case WebPOptions::Lossy:
if (pref.isSet(pref.webp.quality)) opts->setQuality(pref.webp.quality());
if (pref.isSet(pref.webp.imagePreset)) opts->setImagePreset(WebPPreset(pref.webp.imagePreset()));
break;
}
return webp_options;
if (pref.webp.showAlert()) {
app::gen::WebpOptions win;
auto updatePanels = [&win, &opts]{
int o = base::convert_to<int>(win.type()->getValue());
opts->setType(WebPOptions::Type(o));
win.losslessOptions()->setVisible(o == int(WebPOptions::Lossless));
win.lossyOptions()->setVisible(o == int(WebPOptions::Lossy));
auto rc = win.bounds();
win.setBounds(
gfx::Rect(rc.origin(),
win.sizeHint()));
auto manager = win.manager();
if (manager)
manager->invalidateRect(rc); // TODO this should be automatic
// when a window bounds is modified
};
win.loop()->setSelected(opts->loop());
win.type()->setSelectedItemIndex(int(opts->type()));
win.compression()->setValue(opts->compression());
win.imageHint()->setSelectedItemIndex(opts->imageHint());
win.quality()->setValue(static_cast<int>(opts->quality()));
win.imagePreset()->setSelectedItemIndex(opts->imagePreset());
updatePanels();
win.type()->Change.connect(base::Bind<void>(updatePanels));
win.openWindowInForeground();
if (win.closer() == win.ok()) {
pref.webp.loop(win.loop()->isSelected());
pref.webp.type(base::convert_to<int>(win.type()->getValue()));
pref.webp.compression(win.compression()->getValue());
pref.webp.imageHint(base::convert_to<int>(win.imageHint()->getValue()));
pref.webp.quality(win.quality()->getValue());
pref.webp.imagePreset(base::convert_to<int>(win.imagePreset()->getValue()));
opts->setLoop(pref.webp.loop());
opts->setType(WebPOptions::Type(pref.webp.type()));
switch (opts->type()) {
case WebPOptions::Lossless:
opts->setCompression(pref.webp.compression());
opts->setImageHint(WebPImageHint(pref.webp.imageHint()));
break;
case WebPOptions::Lossy:
opts->setQuality(pref.webp.quality());
opts->setImagePreset(WebPPreset(pref.webp.imagePreset()));
break;
}
}
else {
opts.reset(nullptr);
}
}
return opts;
}
catch (std::exception& e) {
catch (const std::exception& e) {
Console::showException(e);
return base::SharedPtr<WebPOptions>(0);
return base::SharedPtr<WebPOptions>(nullptr);
}
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 David Capello
// Copyright (C) 2015 Gabriel Rauter
//
// This program is distributed under the terms of
@ -14,32 +15,71 @@
#include <webp/encode.h>
namespace app {
// Data for WebP files
class WebPOptions : public FormatOptions {
public:
WebPOptions(): m_lossless(1), m_quality(75), m_method(6), m_image_hint(WEBP_HINT_DEFAULT), m_image_preset(WEBP_PRESET_DEFAULT) {};
enum Type { Simple, Lossless, Lossy };
bool lossless() { return m_lossless; }
int getQuality() { return m_quality; }
int getMethod() { return m_method; }
WebPImageHint getImageHint() { return m_image_hint; }
WebPPreset getImagePreset() { return m_image_preset; }
// By default we use 6, because 9 is too slow
const int kDefaultCompression = 6;
void setLossless(int lossless) { m_lossless = (lossless != 0); }
void setLossless(bool lossless) { m_lossless = lossless; }
void setQuality(int quality) { m_quality = quality; }
void setMethod(int method) { m_method = method; }
void setImageHint(int imageHint) { m_image_hint = static_cast<WebPImageHint>(imageHint); }
void setImageHint(WebPImageHint imageHint) { m_image_hint = imageHint; }
void setImagePreset(int imagePreset) { m_image_preset = static_cast<WebPPreset>(imagePreset); };
void setImagePreset(WebPPreset imagePreset) { m_image_preset = imagePreset ; }
WebPOptions() : m_loop(true),
m_type(Type::Simple),
m_compression(kDefaultCompression),
m_imageHint(WEBP_HINT_DEFAULT),
m_quality(100),
m_imagePreset(WEBP_PRESET_DEFAULT) { }
bool loop() const { return m_loop; }
Type type() const { return m_type; }
int compression() const { return m_compression; }
WebPImageHint imageHint() const { return m_imageHint; }
int quality() const { return m_quality; }
WebPPreset imagePreset() const { return m_imagePreset; }
void setLoop(const bool loop) {
m_loop = loop;
}
void setType(const Type type) {
m_type = type;
if (m_type == Type::Simple) {
m_compression = kDefaultCompression;
m_imageHint = WEBP_HINT_DEFAULT;
}
}
void setCompression(const int compression) {
ASSERT(m_type == Type::Lossless);
m_compression = compression;
}
void setImageHint(const WebPImageHint imageHint) {
ASSERT(m_type == Type::Lossless);
m_imageHint = imageHint;
}
void setQuality(const int quality) {
ASSERT(m_type == Type::Lossy);
m_quality = quality;
}
void setImagePreset(const WebPPreset imagePreset) {
ASSERT(m_type == Type::Lossy);
m_imagePreset = imagePreset;
}
private:
bool m_lossless; // Lossless encoding (0=lossy(default), 1=lossless).
int m_quality; // between 0 (smallest file) and 100 (biggest)
int m_method; // quality/speed trade-off (0=fast, 9=slower-better)
WebPImageHint m_image_hint; // Hint for image type (lossless only for now).
WebPPreset m_image_preset; // Image Preset for lossy webp.
bool m_loop;
Type m_type;
// Lossless options
int m_compression; // Quality/speed trade-off (0=fast, 9=slower-better)
WebPImageHint m_imageHint; // Hint for image type (lossless only for now).
// Lossy options
int m_quality; // Between 0 (smallest file) and 100 (biggest)
WebPPreset m_imagePreset; // Image Preset for lossy webp.
};
} // namespace app

View File

@ -1,5 +1,5 @@
# ASEPRITE
# Copyright (C) 2001-2017 David Capello
# Copyright (C) 2001-2018 David Capello
include_directories(.)
@ -29,20 +29,17 @@ if(NOT USE_SHARED_LIBPNG)
add_subdirectory(libpng)
endif()
if(WITH_WEBP_SUPPORT)
if(NOT USE_SHARED_LIBWEBP)
# Skia already includes webp library
if(NOT USE_SKIA_BACKEND)
add_subdirectory(libwebp-cmake)
endif()
endif()
endif()
if(NOT USE_SHARED_GIFLIB)
set(GIFLIB_UTILS OFF CACHE BOOL "Build giflib utils")
add_subdirectory(giflib)
endif()
if(WITH_WEBP_SUPPORT)
# Enable img2webp so "webpmux" library is built
set(WEBP_BUILD_IMG2WEBP ON CACHE BOOL "Build the img2webp animation tool.")
add_subdirectory(libwebp)
endif()
if(NOT USE_SHARED_TINYXML)
add_subdirectory(tinyxml)
endif()

View File

@ -1,45 +0,0 @@
project (webp C)
cmake_minimum_required(VERSION 2.6)
add_definitions(-DNDEBUG -DWEBP_USE_THREAD)
set(LIBWEBP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../libwebp")
file(GLOB WEBP_DEC_SRCS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${LIBWEBP_SOURCE_DIR}/src/dec/*c
)
file(GLOB WEBP_DEMUX_SRCS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${LIBWEBP_SOURCE_DIR}/src/demux/*c
)
file(GLOB WEBP_DSP_SRCS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${LIBWEBP_SOURCE_DIR}/src/dsp/*c
)
file(GLOB WEBP_ENC_SRCS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${LIBWEBP_SOURCE_DIR}/src/enc/*c
)
file(GLOB WEBP_UTILS_SRCS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${LIBWEBP_SOURCE_DIR}/src/utils/*c
)
file(GLOB WEBP_MUX_SRCS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${LIBWEBP_SOURCE_DIR}/src/mux/*c
)
file(GLOB WEBP_HEADERS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${LIBWEBP_SOURCE_DIR}/src/webp/*.h
)
SET(CMAKE_DEBUG_POSTFIX "d")
set(WEBP_SOURCE ${WEBP_DEC_SRCS} ${WEBP_DEMUX_SRCS} ${WEBP_DSP_SRCS} ${WEBP_ENC_SRCS} ${WEBP_UTILS_SRCS} ${WEBP_MUX_SRC})
add_library(${PROJECT_NAME} STATIC ${WEBP_SOURCE} ${WEBP_HEADERS})