56 if (!WebPConfigInit(&preset)) {
60 if (!WebPConfigLosslessPreset(&preset, 6)) {
64 preset.thread_level = 1;
66 if (!WebPValidateConfig(&preset)) {
70 cfg->setProperty(
"haveAnimation",
true);
72 cfg->setProperty(
"preset", 0);
73 cfg->setProperty(
"lossless", preset.lossless == 1);
74 cfg->setProperty(
"quality", preset.quality);
75 cfg->setProperty(
"method", preset.method);
76 cfg->setProperty(
"dithering",
true);
77 cfg->setProperty(
"force_srgb",
false);
78 cfg->setProperty(
"save_profile",
true);
80 cfg->setProperty(
"target_size", preset.target_size);
81 cfg->setProperty(
"target_PSNR", preset.target_PSNR);
82 cfg->setProperty(
"segments", preset.segments);
83 cfg->setProperty(
"sns_strength", preset.sns_strength);
84 cfg->setProperty(
"filter_strength", preset.filter_strength);
85 cfg->setProperty(
"filter_sharpness", preset.filter_sharpness);
86 cfg->setProperty(
"filter_type", preset.filter_type);
87 cfg->setProperty(
"autofilter", preset.autofilter == 1);
88 cfg->setProperty(
"alpha_compression", preset.alpha_compression);
89 cfg->setProperty(
"alpha_filtering", preset.alpha_filtering);
90 cfg->setProperty(
"alpha_quality", preset.alpha_quality);
91 cfg->setProperty(
"pass", preset.pass);
92 cfg->setProperty(
"show_compressed", preset.show_compressed == 1);
93 cfg->setProperty(
"preprocessing", preset.preprocessing);
94 cfg->setProperty(
"partitions", preset.partitions);
95 cfg->setProperty(
"partition_limit", preset.partition_limit);
96 cfg->setProperty(
"emulate_jpeg_size", preset.emulate_jpeg_size == 1);
97 cfg->setProperty(
"thread_level", preset.thread_level > 0);
98 cfg->setProperty(
"low_memory", preset.low_memory == 1);
99 cfg->setProperty(
"near_lossless", preset.near_lossless);
100 cfg->setProperty(
"exact", preset.exact == 1);
101 cfg->setProperty(
"use_sharp_yuv", preset.use_sharp_yuv == 1);
102#if WEBP_ENCODER_ABI_VERSION >= 0x020f
103 cfg->setProperty(
"qmin", preset.qmin);
104 cfg->setProperty(
"qmax", preset.qmax);
107 cfg->setProperty(
"exif",
true);
108 cfg->setProperty(
"xmp",
true);
109 cfg->setProperty(
"iptc",
true);
110 cfg->setProperty(
"storeMetaData",
false);
111 cfg->setProperty(
"filters",
"");
143 using WebPMuxSP = std::unique_ptr<WebPMux,
decltype(&WebPMuxDelete)>;
144 using WebPAnimEncoderSP =
145 std::unique_ptr<WebPAnimEncoder,
decltype(&WebPAnimEncoderDelete)>;
153 document->savingImage()->projection()->colorSpace();
155 const bool needSrgbConversion = [&]() {
156 if (!cfg->getBool(
"force_srgb",
false)) {
176 WebPData imageChunk = {
nullptr, 0};
179 WebPAnimEncoderOptions encodingOptions;
180 if (!WebPAnimEncoderOptionsInit(&encodingOptions)) {
181 errFile <<
"WebP animation configuration initialization failure";
185 if (cfg->getBool(
"lossless",
true)) {
186 encodingOptions.allow_mixed =
false;
188 encodingOptions.allow_mixed =
true;
190 encodingOptions.verbose =
true;
192 encodingOptions.anim_params.loop_count = 0;
194 WebPAnimEncoderSP enc(WebPAnimEncoderNew(
bounds.width(),
197 &WebPAnimEncoderDelete);
201 if (!WebPConfigInit(&config)) {
202 errFile <<
"WebP config initialization failed!";
206 config.lossless = cfg->getBool(
"lossless",
true) ? 1 : 0;
207 config.quality = cfg->getFloat(
"quality", 75.0);
208 config.method = cfg->getInt(
"method", 4);
210 config.target_size = cfg->getInt(
"target_size", 0);
211 config.target_PSNR = cfg->getFloat(
"target_PSNR", 0.0f);
212 config.segments = cfg->getInt(
"segments", 4);
213 config.sns_strength = cfg->getInt(
"sns_strength", 50);
214 config.filter_strength = cfg->getInt(
"filter_strength", 60);
215 config.filter_sharpness = cfg->getInt(
"filter_sharpness", 0);
216 config.filter_type = cfg->getInt(
"filter_type", 1);
217 config.autofilter = cfg->getBool(
"autofilter",
false) ? 1 : 0;
218 config.alpha_compression = cfg->getInt(
"alpha_compression", 1);
219 config.alpha_filtering = cfg->getInt(
"alpha_filtering", 1);
220 config.alpha_quality = cfg->getInt(
"alpha_quality", 100);
221 config.pass = cfg->getInt(
"pass", 1);
222 config.show_compressed =
223 cfg->getBool(
"show_compressed",
false) ? 1 : 0;
224 config.preprocessing = cfg->getInt(
"preprocessing", 0);
225 config.partitions = cfg->getInt(
"partitions", 0);
226 config.partition_limit = cfg->getInt(
"partition_limit", 0);
227 config.emulate_jpeg_size =
228 cfg->getBool(
"emulate_jpeg_size",
false) ? 1 : 0;
229 config.thread_level = cfg->getBool(
"thread_level",
false) ? 1 : 0;
230 config.low_memory = cfg->getBool(
"low_memory",
false) ? 1 : 0;
231 config.near_lossless = cfg->getInt(
"near_lossless", 100);
232 config.exact = cfg->getBool(
"exact",
false) ? 1 : 0;
233 config.use_sharp_yuv = cfg->getBool(
"use_sharp_yuv",
false) ? 1 : 0;
234#if WEBP_ENCODER_ABI_VERSION >= 0x020f
235 config.qmin = cfg->getInt(
"qmin", 0);
236 config.qmax = cfg->getInt(
"qmax", 100);
239 if (!WebPValidateConfig(&config)) {
240 errFile <<
"WebP configuration validation failure";
245 const bool enableDithering = cfg->getBool(
"dithering",
true);
247 const bool isAnimated = [&]() {
272 std::sort(t.begin(), t.end());
281 /
static_cast<double>(
284 for (
const int i : times) {
285 const int timestamp_ms = i * duration;
288 if (!WebPPictureInit(currentFrame.
get())) {
289 errFile <<
"WebP picture initialization failure";
293 currentFrame.
get()->width =
bounds.width();
294 currentFrame.
get()->height =
bounds.height();
297 if (config.lossless == 1) {
298 currentFrame.
get()->use_argb = 1;
301 const QImage pixels = [&]() {
307 frameData->writeFrameToDevice(dev);
311 || !enableDithering) {
319 const KoID depthId = src->colorSpace()->colorDepthId();
325 src->colorSpace()->profile());
332 tmp->convertTo(mixCs);
344 for (
int y =
bounds.y(); y <=
bounds.bottom(); y += rows) {
348 for (
int x =
bounds.x(); x <=
bounds.right(); x += columns) {
355 const qint32 srcRowStride = srcIt->
rowStride(x, y);
356 const qint32 dstRowStride = dstIt->
rowStride(x, y);
358 quint8 *dstPtr = dstIt->
rawData();
360 op->
dither(srcPtr, srcRowStride, dstPtr, dstRowStride, x, y, columns, rows);
370 if (needSrgbConversion) {
375 .convertToFormat(QImage::Format_RGBA8888);
380 if (!WebPPictureImportRGBA(currentFrame.
get(),
383 errFile <<
"WebP picture conversion failure:"
384 << currentFrame.
get()->error_code;
388 WebPMemoryWriter writer;
389 WebPMemoryWriterInit(&writer);
390 currentFrame.
get()->writer = WebPMemoryWrite;
391 currentFrame.
get()->custom_ptr = &writer;
393 if (!WebPEncode(&config, currentFrame.
get())) {
394 errFile <<
"WebP encoding failure:"
395 << currentFrame.
get()->error_code;
399 if (!WebPAnimEncoderAdd(enc.get(),
403 errFile <<
"WebPAnimEncoderAdd failed";
407 dbgFile <<
"Added frame @" << i << timestamp_ms <<
"ms";
410 const int timestamp_ms =
415 WebPAnimEncoderAdd(enc.get(),
nullptr, timestamp_ms,
nullptr);
417 dbgFile <<
"Animation finished @" << timestamp_ms <<
"ms";
420 if (!WebPPictureInit(currentFrame.
get())) {
421 errFile <<
"WebP picture initialization failure";
425 currentFrame.
get()->width =
bounds.width();
426 currentFrame.
get()->height =
bounds.height();
429 if (config.lossless == 1) {
430 currentFrame.
get()->use_argb = 1;
434 const QImage pixels = [&]() {
437 || !enableDithering) {
438 dst = document->savingImage()->projection();
445 const KoID depthId = src->colorSpace()->colorDepthId();
451 src->colorSpace()->profile());
458 tmp->convertTo(mixCs);
470 for (
int y =
bounds.y(); y <=
bounds.bottom(); y += rows) {
474 for (
int x =
bounds.x(); x <=
bounds.right(); x += columns) {
481 const qint32 srcRowStride = srcIt->
rowStride(x, y);
482 const qint32 dstRowStride = dstIt->
rowStride(x, y);
484 quint8 *dstPtr = dstIt->
rawData();
486 op->
dither(srcPtr, srcRowStride, dstPtr, dstRowStride, x, y, columns, rows);
496 if (needSrgbConversion) {
501 .convertToFormat(QImage::Format_RGBA8888);
506 if (!WebPPictureImportRGBA(currentFrame.
get(),
509 errFile <<
"WebP picture conversion failure:"
510 << currentFrame.
get()->error_code;
514 WebPMemoryWriter writer;
515 WebPMemoryWriterInit(&writer);
516 currentFrame.
get()->writer = WebPMemoryWrite;
517 currentFrame.
get()->custom_ptr = &writer;
519 if (!WebPEncode(&config, currentFrame.
get())) {
520 errFile <<
"WebP encoding failure:"
521 << currentFrame.
get()->error_code;
525 if (!WebPAnimEncoderAdd(enc.get(),
529 errFile <<
"WebPAnimEncoderAdd failed";
534 WebPAnimEncoderAssemble(enc.get(), &imageChunk);
538 WebPMuxSP mux(WebPMuxCreate(&imageChunk, 0), &WebPMuxDelete);
541 errFile <<
"WebP mux initialization failure";
546 if (cfg->getBool(
"save_profile",
true)) {
547 const QByteArray profile = needSrgbConversion
551 WebPData iccChunk = {
reinterpret_cast<const uint8_t *
>(profile.data()),
552 static_cast<size_t>(profile.size())};
555 if (WEBP_MUX_OK != WebPMuxSetChunk(mux.get(),
"ICCP", &iccChunk, 1)) {
556 errFile <<
"WebPMuxSetChunk for the ICC profile failed";
561 if (cfg->getBool(
"storeMetaData",
false)) {
562 auto metaDataStore = [&]() -> std::unique_ptr<KisMetaData::Store> {
566 return std::make_unique<KisMetaData::Store>(
573 if (metaDataStore && !metaDataStore->isEmpty()) {
579 if (metaDataStore && cfg->getBool(
"exif",
true)) {
586 io->
saveTo(metaDataStore.get(), &ioDevice);
589 reinterpret_cast<const uint8_t *
>(ioDevice.data().constData()),
590 static_cast<size_t>(ioDevice.data().size())};
593 if (WEBP_MUX_OK != WebPMuxSetChunk(mux.get(),
"EXIF", &xmp, 1)) {
594 errFile <<
"WebPMuxSetChunk for EXIF failed";
599 if (metaDataStore && cfg->getBool(
"xmp",
true)) {
606 io->
saveTo(metaDataStore.get(), &ioDevice);
609 reinterpret_cast<const uint8_t *
>(ioDevice.data().constData()),
610 static_cast<size_t>(ioDevice.data().size())};
613 if (WEBP_MUX_OK != WebPMuxSetChunk(mux.get(),
"XMP ", &xmp, 1)) {
614 errFile <<
"WebPMuxSetChunk for XMP failed";
621 WebPMuxAssemble(mux.get(), &output);
623 s.setByteOrder(QDataStream::LittleEndian);
624 s.writeRawData(
reinterpret_cast<const char *
>(output.bytes),
625 static_cast<int>(output.size));
626 WebPDataClear(&output);