| 1 | // Aseprite | 
|---|
| 2 | // Copyright (C) 2018-2022  Igara Studio S.A. | 
|---|
| 3 | // Copyright (C) 2015-2018  David Capello | 
|---|
| 4 | // Copyright (C) 2015  Gabriel Rauter | 
|---|
| 5 | // | 
|---|
| 6 | // This program is distributed under the terms of | 
|---|
| 7 | // the End-User License Agreement for Aseprite. | 
|---|
| 8 |  | 
|---|
| 9 | #ifdef HAVE_CONFIG_H | 
|---|
| 10 | #include "config.h" | 
|---|
| 11 | #endif | 
|---|
| 12 |  | 
|---|
| 13 | #include "app/app.h" | 
|---|
| 14 | #include "app/console.h" | 
|---|
| 15 | #include "app/context.h" | 
|---|
| 16 | #include "app/doc.h" | 
|---|
| 17 | #include "app/file/file.h" | 
|---|
| 18 | #include "app/file/file_format.h" | 
|---|
| 19 | #include "app/file/format_options.h" | 
|---|
| 20 | #include "app/file/webp_options.h" | 
|---|
| 21 | #include "app/ini_file.h" | 
|---|
| 22 | #include "app/pref/preferences.h" | 
|---|
| 23 | #include "base/convert_to.h" | 
|---|
| 24 | #include "base/file_handle.h" | 
|---|
| 25 | #include "doc/doc.h" | 
|---|
| 26 | #include "ui/manager.h" | 
|---|
| 27 |  | 
|---|
| 28 | #include "webp_options.xml.h" | 
|---|
| 29 |  | 
|---|
| 30 | #include <cstdio> | 
|---|
| 31 | #include <cstdlib> | 
|---|
| 32 | #include <algorithm> | 
|---|
| 33 | #include <map> | 
|---|
| 34 |  | 
|---|
| 35 | #include <webp/demux.h> | 
|---|
| 36 | #include <webp/mux.h> | 
|---|
| 37 |  | 
|---|
| 38 | namespace app { | 
|---|
| 39 |  | 
|---|
| 40 | using namespace base; | 
|---|
| 41 |  | 
|---|
| 42 | class WebPFormat : public FileFormat { | 
|---|
| 43 |  | 
|---|
| 44 | const char* onGetName() const override { | 
|---|
| 45 | return "webp"; | 
|---|
| 46 | } | 
|---|
| 47 |  | 
|---|
| 48 | void onGetExtensions(base::paths& exts) const override { | 
|---|
| 49 | exts.push_back( "webp"); | 
|---|
| 50 | } | 
|---|
| 51 |  | 
|---|
| 52 | dio::FileFormat onGetDioFormat() const override { | 
|---|
| 53 | return dio::FileFormat::WEBP_ANIMATION; | 
|---|
| 54 | } | 
|---|
| 55 |  | 
|---|
| 56 | int onGetFlags() const override { | 
|---|
| 57 | return | 
|---|
| 58 | FILE_SUPPORT_LOAD | | 
|---|
| 59 | FILE_SUPPORT_SAVE | | 
|---|
| 60 | FILE_SUPPORT_RGB | | 
|---|
| 61 | FILE_SUPPORT_RGBA | | 
|---|
| 62 | FILE_SUPPORT_FRAMES | | 
|---|
| 63 | FILE_SUPPORT_GET_FORMAT_OPTIONS | | 
|---|
| 64 | FILE_ENCODE_ABSTRACT_IMAGE; | 
|---|
| 65 | } | 
|---|
| 66 |  | 
|---|
| 67 | bool onLoad(FileOp* fop) override; | 
|---|
| 68 | #ifdef ENABLE_SAVE | 
|---|
| 69 | bool onSave(FileOp* fop) override; | 
|---|
| 70 | #endif | 
|---|
| 71 | FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) override; | 
|---|
| 72 | }; | 
|---|
| 73 |  | 
|---|
| 74 | FileFormat* CreateWebPFormat() | 
|---|
| 75 | { | 
|---|
| 76 | return new WebPFormat; | 
|---|
| 77 | } | 
|---|
| 78 |  | 
|---|
| 79 | const char* getDecoderErrorMessage(VP8StatusCode statusCode) | 
|---|
| 80 | { | 
|---|
| 81 | switch (statusCode) { | 
|---|
| 82 | case VP8_STATUS_OK: return ""; break; | 
|---|
| 83 | case VP8_STATUS_OUT_OF_MEMORY: return "out of memory"; break; | 
|---|
| 84 | case VP8_STATUS_INVALID_PARAM: return "invalid parameters"; break; | 
|---|
| 85 | case VP8_STATUS_BITSTREAM_ERROR: return "bitstream error"; break; | 
|---|
| 86 | case VP8_STATUS_UNSUPPORTED_FEATURE: return "unsupported feature"; break; | 
|---|
| 87 | case VP8_STATUS_SUSPENDED: return "suspended"; break; | 
|---|
| 88 | case VP8_STATUS_USER_ABORT: return "user aborted"; break; | 
|---|
| 89 | case VP8_STATUS_NOT_ENOUGH_DATA: return "not enough data"; break; | 
|---|
| 90 | default: return "unknown error"; break; | 
|---|
| 91 | } | 
|---|
| 92 | } | 
|---|
| 93 |  | 
|---|
| 94 | bool WebPFormat::onLoad(FileOp* fop) | 
|---|
| 95 | { | 
|---|
| 96 | FileHandle handle(open_file_with_exception(fop->filename(), "rb")); | 
|---|
| 97 | FILE* fp = handle.get(); | 
|---|
| 98 |  | 
|---|
| 99 | long len = 0; | 
|---|
| 100 | if (fseek(fp, 0, SEEK_END) == 0) { | 
|---|
| 101 | len = ftell(fp); | 
|---|
| 102 | fseek(fp, 0, SEEK_SET); | 
|---|
| 103 | } | 
|---|
| 104 |  | 
|---|
| 105 | if (len < 4) { | 
|---|
| 106 | fop->setError( "The specified file is not a WebP file\n"); | 
|---|
| 107 | return false; | 
|---|
| 108 | } | 
|---|
| 109 |  | 
|---|
| 110 | std::vector<uint8_t> buf(len); | 
|---|
| 111 | if (fread(&buf[0], 1, buf.size(), fp) != buf.size()) { | 
|---|
| 112 | fop->setError( "Error moving the whole WebP file to memory\n"); | 
|---|
| 113 | return false; | 
|---|
| 114 | } | 
|---|
| 115 |  | 
|---|
| 116 | WebPData webp_data; | 
|---|
| 117 | WebPDataInit(&webp_data); | 
|---|
| 118 | webp_data.bytes = &buf[0]; | 
|---|
| 119 | webp_data.size = buf.size(); | 
|---|
| 120 |  | 
|---|
| 121 | WebPAnimDecoderOptions dec_options; | 
|---|
| 122 | WebPAnimDecoderOptionsInit(&dec_options); | 
|---|
| 123 | dec_options.color_mode = MODE_RGBA; | 
|---|
| 124 |  | 
|---|
| 125 | WebPAnimDecoder* dec = WebPAnimDecoderNew(&webp_data, &dec_options); | 
|---|
| 126 | if (dec == nullptr) { | 
|---|
| 127 | fop->setError( "Error parsing WebP image\n"); | 
|---|
| 128 | return false; | 
|---|
| 129 | } | 
|---|
| 130 |  | 
|---|
| 131 | WebPAnimInfo anim_info; | 
|---|
| 132 | if (!WebPAnimDecoderGetInfo(dec, &anim_info)) { | 
|---|
| 133 | fop->setError( "Error getting global info about the WebP animation\n"); | 
|---|
| 134 | return false; | 
|---|
| 135 | } | 
|---|
| 136 |  | 
|---|
| 137 | WebPDecoderConfig config; | 
|---|
| 138 | WebPInitDecoderConfig(&config); | 
|---|
| 139 | if (WebPGetFeatures(webp_data.bytes, webp_data.size, &config.input)) { | 
|---|
| 140 | if (!fop->formatOptions()) { | 
|---|
| 141 | auto opts = std::make_shared<WebPOptions>(); | 
|---|
| 142 | WebPOptions::Type type = WebPOptions::Simple; | 
|---|
| 143 | switch (config.input.format) { | 
|---|
| 144 | case 0: type = WebPOptions::Simple; break; | 
|---|
| 145 | case 1: type = WebPOptions::Lossy; break; | 
|---|
| 146 | case 2: type = WebPOptions::Lossless; break; | 
|---|
| 147 | } | 
|---|
| 148 | opts->setType(type); | 
|---|
| 149 | fop->setLoadedFormatOptions(opts); | 
|---|
| 150 | } | 
|---|
| 151 | } | 
|---|
| 152 | else { | 
|---|
| 153 | config.input.has_alpha = false; | 
|---|
| 154 | } | 
|---|
| 155 |  | 
|---|
| 156 | const int w = anim_info.canvas_width; | 
|---|
| 157 | const int h = anim_info.canvas_height; | 
|---|
| 158 |  | 
|---|
| 159 | Sprite* sprite = new Sprite(ImageSpec(ColorMode::RGB, w, h), 256); | 
|---|
| 160 | LayerImage* layer = new LayerImage(sprite); | 
|---|
| 161 | sprite->root()->addLayer(layer); | 
|---|
| 162 | sprite->setTotalFrames(anim_info.frame_count); | 
|---|
| 163 |  | 
|---|
| 164 | for (frame_t f=0; f<anim_info.frame_count; ++f) { | 
|---|
| 165 | ImageRef image(Image::create(IMAGE_RGB, w, h)); | 
|---|
| 166 | Cel* cel = new Cel(f, image); | 
|---|
| 167 | layer->addCel(cel); | 
|---|
| 168 | } | 
|---|
| 169 |  | 
|---|
| 170 | bool has_alpha = config.input.has_alpha; | 
|---|
| 171 | frame_t f = 0; | 
|---|
| 172 | int prev_timestamp = 0; | 
|---|
| 173 | while (WebPAnimDecoderHasMoreFrames(dec)) { | 
|---|
| 174 | uint8_t* frame_rgba; | 
|---|
| 175 | int frame_timestamp = 0; | 
|---|
| 176 | if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &frame_timestamp)) { | 
|---|
| 177 | fop->setError( "Error loading WebP frame\n"); | 
|---|
| 178 | return false; | 
|---|
| 179 | } | 
|---|
| 180 |  | 
|---|
| 181 | Cel* cel = layer->cel(f); | 
|---|
| 182 | if (cel) { | 
|---|
| 183 | memcpy(cel->image()->getPixelAddress(0, 0), | 
|---|
| 184 | frame_rgba, h*w*sizeof(uint32_t)); | 
|---|
| 185 |  | 
|---|
| 186 | if (!has_alpha) { | 
|---|
| 187 | const uint32_t* src = (const uint32_t*)frame_rgba; | 
|---|
| 188 | const uint32_t* src_end = src + w*h; | 
|---|
| 189 | while (src < src_end) { | 
|---|
| 190 | const uint8_t alpha = (*src >> 24) & 0xff; | 
|---|
| 191 | if (alpha < 255) { | 
|---|
| 192 | has_alpha = true; | 
|---|
| 193 | break; | 
|---|
| 194 | } | 
|---|
| 195 | ++src; | 
|---|
| 196 | } | 
|---|
| 197 | } | 
|---|
| 198 | } | 
|---|
| 199 |  | 
|---|
| 200 | sprite->setFrameDuration(f, frame_timestamp - prev_timestamp); | 
|---|
| 201 |  | 
|---|
| 202 | prev_timestamp = frame_timestamp; | 
|---|
| 203 | fop->setProgress(double(f) / double(anim_info.frame_count)); | 
|---|
| 204 | if (fop->isStop()) | 
|---|
| 205 | break; | 
|---|
| 206 |  | 
|---|
| 207 | ++f; | 
|---|
| 208 | } | 
|---|
| 209 | WebPAnimDecoderReset(dec); | 
|---|
| 210 |  | 
|---|
| 211 | if (!has_alpha) | 
|---|
| 212 | layer->configureAsBackground(); | 
|---|
| 213 |  | 
|---|
| 214 | WebPAnimDecoderDelete(dec); | 
|---|
| 215 |  | 
|---|
| 216 | // Don't use WebPDataClear because webp_data use a std::vector<> data. | 
|---|
| 217 | //WebPDataClear(&webp_data); | 
|---|
| 218 |  | 
|---|
| 219 | if (fop->isStop()) | 
|---|
| 220 | return false; | 
|---|
| 221 |  | 
|---|
| 222 | fop->createDocument(sprite); | 
|---|
| 223 | return true; | 
|---|
| 224 | } | 
|---|
| 225 |  | 
|---|
| 226 | #ifdef ENABLE_SAVE | 
|---|
| 227 |  | 
|---|
| 228 | struct WriterData { | 
|---|
| 229 | FILE* fp; | 
|---|
| 230 | FileOp* fop; | 
|---|
| 231 | frame_t f, n; | 
|---|
| 232 | double progress; | 
|---|
| 233 |  | 
|---|
| 234 | WriterData(FILE* fp, FileOp* fop, frame_t f, frame_t n, double progress) | 
|---|
| 235 | : fp(fp), fop(fop), f(f), n(n), progress(progress) { } | 
|---|
| 236 | }; | 
|---|
| 237 |  | 
|---|
| 238 | static int progress_report(int percent, const WebPPicture* pic) | 
|---|
| 239 | { | 
|---|
| 240 | auto wd = (WriterData*)pic->user_data; | 
|---|
| 241 | FileOp* fop = wd->fop; | 
|---|
| 242 |  | 
|---|
| 243 | double newProgress = (double(wd->f) + double(percent)/100.0) / double(wd->n); | 
|---|
| 244 | wd->progress = std::max(wd->progress, newProgress); | 
|---|
| 245 | wd->progress = std::clamp(wd->progress, 0.0, 1.0); | 
|---|
| 246 |  | 
|---|
| 247 | fop->setProgress(wd->progress); | 
|---|
| 248 | if (fop->isStop()) | 
|---|
| 249 | return false; | 
|---|
| 250 | else | 
|---|
| 251 | return true; | 
|---|
| 252 | } | 
|---|
| 253 |  | 
|---|
| 254 | bool WebPFormat::onSave(FileOp* fop) | 
|---|
| 255 | { | 
|---|
| 256 | FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb")); | 
|---|
| 257 | FILE* fp = handle.get(); | 
|---|
| 258 |  | 
|---|
| 259 | const FileAbstractImage* sprite = fop->abstractImage(); | 
|---|
| 260 | const doc::frame_t totalFrames = sprite->frames(); | 
|---|
| 261 | const int w = sprite->width(); | 
|---|
| 262 | const int h = sprite->height(); | 
|---|
| 263 |  | 
|---|
| 264 | if (w > WEBP_MAX_DIMENSION || | 
|---|
| 265 | h > WEBP_MAX_DIMENSION) { | 
|---|
| 266 | fop->setError( "WebP format cannot store %dx%d images. The maximum allowed size is %dx%d\n", | 
|---|
| 267 | w, h, WEBP_MAX_DIMENSION, WEBP_MAX_DIMENSION); | 
|---|
| 268 | return false; | 
|---|
| 269 | } | 
|---|
| 270 |  | 
|---|
| 271 | auto opts = fop->formatOptionsForSaving<WebPOptions>(); | 
|---|
| 272 | WebPConfig config; | 
|---|
| 273 | WebPConfigInit(&config); | 
|---|
| 274 |  | 
|---|
| 275 | switch (opts->type()) { | 
|---|
| 276 |  | 
|---|
| 277 | case WebPOptions::Simple: | 
|---|
| 278 | case WebPOptions::Lossless: | 
|---|
| 279 | if (!WebPConfigLosslessPreset(&config, | 
|---|
| 280 | opts->compression())) { | 
|---|
| 281 | fop->setError( "Error in WebP configuration\n"); | 
|---|
| 282 | return false; | 
|---|
| 283 | } | 
|---|
| 284 | config.image_hint = opts->imageHint(); | 
|---|
| 285 | break; | 
|---|
| 286 |  | 
|---|
| 287 | case WebPOptions::Lossy: | 
|---|
| 288 | if (!WebPConfigPreset(&config, | 
|---|
| 289 | opts->imagePreset(), | 
|---|
| 290 | static_cast<float>(opts->quality()))) { | 
|---|
| 291 | fop->setError( "Error in WebP configuration preset\n"); | 
|---|
| 292 | return false; | 
|---|
| 293 | } | 
|---|
| 294 | break; | 
|---|
| 295 | } | 
|---|
| 296 |  | 
|---|
| 297 | WebPAnimEncoderOptions enc_options; | 
|---|
| 298 | WebPAnimEncoderOptionsInit(&enc_options); | 
|---|
| 299 | enc_options.anim_params.loop_count = | 
|---|
| 300 | (opts->loop() ? 0:  // 0 = infinite | 
|---|
| 301 | 1); // 1 = loop once | 
|---|
| 302 |  | 
|---|
| 303 | ImageRef image(Image::create(IMAGE_RGB, w, h)); | 
|---|
| 304 |  | 
|---|
| 305 | WriterData wd(fp, fop, 0, totalFrames, 0.0); | 
|---|
| 306 | WebPPicture pic; | 
|---|
| 307 | WebPPictureInit(&pic); | 
|---|
| 308 | pic.width = w; | 
|---|
| 309 | pic.height = h; | 
|---|
| 310 | pic.use_argb = true; | 
|---|
| 311 | pic.argb = (uint32_t*)image->getPixelAddress(0, 0); | 
|---|
| 312 | pic.argb_stride = w; | 
|---|
| 313 | pic.user_data = &wd; | 
|---|
| 314 | pic.progress_hook = progress_report; | 
|---|
| 315 |  | 
|---|
| 316 | WebPAnimEncoder* enc = WebPAnimEncoderNew(w, h, &enc_options); | 
|---|
| 317 | int timestamp_ms = 0; | 
|---|
| 318 | for (frame_t f=0; f<totalFrames; ++f) { | 
|---|
| 319 | // Render the frame in the bitmap | 
|---|
| 320 | clear_image(image.get(), image->maskColor()); | 
|---|
| 321 | sprite->renderFrame(f, image.get()); | 
|---|
| 322 |  | 
|---|
| 323 | // Switch R <-> B channels because WebPAnimEncoderAssemble() | 
|---|
| 324 | // expects MODE_BGRA pictures. | 
|---|
| 325 | { | 
|---|
| 326 | LockImageBits<RgbTraits> bits(image.get(), Image::ReadWriteLock); | 
|---|
| 327 | auto it = bits.begin(), end = bits.end(); | 
|---|
| 328 | for (; it != end; ++it) { | 
|---|
| 329 | auto c = *it; | 
|---|
| 330 | *it = rgba(rgba_getb(c), // Use blue in red channel | 
|---|
| 331 | rgba_getg(c), | 
|---|
| 332 | rgba_getr(c), // Use red in blue channel | 
|---|
| 333 | rgba_geta(c)); | 
|---|
| 334 | } | 
|---|
| 335 | } | 
|---|
| 336 |  | 
|---|
| 337 | if (!WebPAnimEncoderAdd(enc, &pic, timestamp_ms, &config)) { | 
|---|
| 338 | if (!fop->isStop()) { | 
|---|
| 339 | fop->setError( "Error saving frame %d info\n", f); | 
|---|
| 340 | return false; | 
|---|
| 341 | } | 
|---|
| 342 | else | 
|---|
| 343 | return true; | 
|---|
| 344 | } | 
|---|
| 345 | timestamp_ms += sprite->frameDuration(f); | 
|---|
| 346 |  | 
|---|
| 347 | wd.f = f; | 
|---|
| 348 | } | 
|---|
| 349 | WebPAnimEncoderAdd(enc, nullptr, timestamp_ms, nullptr); | 
|---|
| 350 |  | 
|---|
| 351 | WebPData webp_data; | 
|---|
| 352 | WebPDataInit(&webp_data); | 
|---|
| 353 | WebPAnimEncoderAssemble(enc, &webp_data); | 
|---|
| 354 | WebPAnimEncoderDelete(enc); | 
|---|
| 355 |  | 
|---|
| 356 | if (fwrite(webp_data.bytes, 1, webp_data.size, fp) != webp_data.size) { | 
|---|
| 357 | fop->setError( "Error saving content into file\n"); | 
|---|
| 358 | return false; | 
|---|
| 359 | } | 
|---|
| 360 |  | 
|---|
| 361 | WebPDataClear(&webp_data); | 
|---|
| 362 | return true; | 
|---|
| 363 | } | 
|---|
| 364 |  | 
|---|
| 365 | #endif  // ENABLE_SAVE | 
|---|
| 366 |  | 
|---|
| 367 | // Shows the WebP configuration dialog. | 
|---|
| 368 | FormatOptionsPtr WebPFormat::onAskUserForFormatOptions(FileOp* fop) | 
|---|
| 369 | { | 
|---|
| 370 | auto opts = fop->formatOptionsOfDocument<WebPOptions>(); | 
|---|
| 371 | #ifdef ENABLE_UI | 
|---|
| 372 | if (fop->context() && fop->context()->isUIAvailable()) { | 
|---|
| 373 | try { | 
|---|
| 374 | auto& pref = Preferences::instance(); | 
|---|
| 375 |  | 
|---|
| 376 | if (pref.isSet(pref.webp.loop)) | 
|---|
| 377 | opts->setLoop(pref.webp.loop()); | 
|---|
| 378 |  | 
|---|
| 379 | if (pref.isSet(pref.webp.type)) | 
|---|
| 380 | opts->setType(WebPOptions::Type(pref.webp.type())); | 
|---|
| 381 |  | 
|---|
| 382 | switch (opts->type()) { | 
|---|
| 383 | case WebPOptions::Lossless: | 
|---|
| 384 | if (pref.isSet(pref.webp.compression)) opts->setCompression(pref.webp.compression()); | 
|---|
| 385 | if (pref.isSet(pref.webp.imageHint))   opts->setImageHint(WebPImageHint(pref.webp.imageHint())); | 
|---|
| 386 | break; | 
|---|
| 387 | case WebPOptions::Lossy: | 
|---|
| 388 | if (pref.isSet(pref.webp.quality))     opts->setQuality(pref.webp.quality()); | 
|---|
| 389 | if (pref.isSet(pref.webp.imagePreset)) opts->setImagePreset(WebPPreset(pref.webp.imagePreset())); | 
|---|
| 390 | break; | 
|---|
| 391 | } | 
|---|
| 392 |  | 
|---|
| 393 | if (pref.webp.showAlert()) { | 
|---|
| 394 | app::gen::WebpOptions win; | 
|---|
| 395 |  | 
|---|
| 396 | auto updatePanels = [&win, &opts]{ | 
|---|
| 397 | int o = base::convert_to<int>(win.type()->getValue()); | 
|---|
| 398 | opts->setType(WebPOptions::Type(o)); | 
|---|
| 399 | win.losslessOptions()->setVisible(o == int(WebPOptions::Lossless)); | 
|---|
| 400 | win.lossyOptions()->setVisible(o == int(WebPOptions::Lossy)); | 
|---|
| 401 |  | 
|---|
| 402 | auto rc = win.bounds(); | 
|---|
| 403 | win.setBounds( | 
|---|
| 404 | gfx::Rect(rc.origin(), | 
|---|
| 405 | win.sizeHint())); | 
|---|
| 406 |  | 
|---|
| 407 | auto manager = win.manager(); | 
|---|
| 408 | if (manager) | 
|---|
| 409 | manager->invalidateRect(rc); // TODO this should be automatic | 
|---|
| 410 | // when a window bounds is modified | 
|---|
| 411 | }; | 
|---|
| 412 |  | 
|---|
| 413 | win.loop()->setSelected(opts->loop()); | 
|---|
| 414 | win.type()->setSelectedItemIndex(int(opts->type())); | 
|---|
| 415 | win.compression()->setValue(opts->compression()); | 
|---|
| 416 | win.imageHint()->setSelectedItemIndex(opts->imageHint()); | 
|---|
| 417 | win.quality()->setValue(static_cast<int>(opts->quality())); | 
|---|
| 418 | win.imagePreset()->setSelectedItemIndex(opts->imagePreset()); | 
|---|
| 419 |  | 
|---|
| 420 | updatePanels(); | 
|---|
| 421 | win.type()->Change.connect(updatePanels); | 
|---|
| 422 |  | 
|---|
| 423 | win.openWindowInForeground(); | 
|---|
| 424 |  | 
|---|
| 425 | if (win.closer() == win.ok()) { | 
|---|
| 426 | pref.webp.loop(win.loop()->isSelected()); | 
|---|
| 427 | pref.webp.type(base::convert_to<int>(win.type()->getValue())); | 
|---|
| 428 | pref.webp.compression(win.compression()->getValue()); | 
|---|
| 429 | pref.webp.imageHint(base::convert_to<int>(win.imageHint()->getValue())); | 
|---|
| 430 | pref.webp.quality(win.quality()->getValue()); | 
|---|
| 431 | pref.webp.imagePreset(base::convert_to<int>(win.imagePreset()->getValue())); | 
|---|
| 432 |  | 
|---|
| 433 | opts->setLoop(pref.webp.loop()); | 
|---|
| 434 | opts->setType(WebPOptions::Type(pref.webp.type())); | 
|---|
| 435 | switch (opts->type()) { | 
|---|
| 436 | case WebPOptions::Lossless: | 
|---|
| 437 | opts->setCompression(pref.webp.compression()); | 
|---|
| 438 | opts->setImageHint(WebPImageHint(pref.webp.imageHint())); | 
|---|
| 439 | break; | 
|---|
| 440 | case WebPOptions::Lossy: | 
|---|
| 441 | opts->setQuality(pref.webp.quality()); | 
|---|
| 442 | opts->setImagePreset(WebPPreset(pref.webp.imagePreset())); | 
|---|
| 443 | break; | 
|---|
| 444 | } | 
|---|
| 445 | } | 
|---|
| 446 | else { | 
|---|
| 447 | opts.reset(); | 
|---|
| 448 | } | 
|---|
| 449 | } | 
|---|
| 450 | } | 
|---|
| 451 | catch (const std::exception& e) { | 
|---|
| 452 | Console::showException(e); | 
|---|
| 453 | return std::shared_ptr<WebPOptions>(nullptr); | 
|---|
| 454 | } | 
|---|
| 455 | } | 
|---|
| 456 | #endif // ENABLE_UI | 
|---|
| 457 | return opts; | 
|---|
| 458 | } | 
|---|
| 459 |  | 
|---|
| 460 | } // namespace app | 
|---|
| 461 |  | 
|---|