Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_webp_import.cpp
Go to the documentation of this file.
1/*
2 * This file is part of Krita
3 *
4 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <kpluginfactory.h>
10#include <webp/demux.h>
11
12#include <QBuffer>
13#include <QByteArray>
14
15#include <cmath>
16#include <cstdint>
17#include <memory>
18
19#include <KisDocument.h>
22#include <KoColorProfile.h>
24#include <KoDialog.h>
25#include <kis_group_layer.h>
29#include <kis_paint_layer.h>
30#include <kis_painter.h>
33
34#include "kis_webp_import.h"
35
36K_PLUGIN_FACTORY_WITH_JSON(KisWebPImportFactory, "krita_webp_import.json", registerPlugin<KisWebPImport>();)
37
38KisWebPImport::KisWebPImport(QObject *parent, const QVariantList &)
39 : KisImportExportFilter(parent)
40{
41}
42
44
46 QIODevice *io,
48{
49 const QByteArray buf = io->readAll();
50
51 if (buf.isEmpty()) {
53 }
54
55 const uint8_t *data = reinterpret_cast<const uint8_t *>(buf.constData());
56 const size_t data_size = static_cast<size_t>(buf.size());
57
58 const WebPData webpData = {data, data_size};
59
60 WebPDemuxer *demux = WebPDemux(&webpData);
61 if (!demux) {
62 dbgFile << "WebP demuxer initialization failure";
64 }
65
66 const uint32_t width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
67 const uint32_t height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);
68 const uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS);
69 const uint32_t bg = WebPDemuxGetI(demux, WEBP_FF_BACKGROUND_COLOR);
70
71 const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8();
72 const KoColorSpace *imageColorSpace = nullptr;
73
74 bool isRgba = true;
75
76 {
77 WebPChunkIterator chunk_iter;
78 if (flags & ICCP_FLAG) {
79 if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter)) {
80 dbgFile << "WebPDemuxGetChunk on ICCP succeeded, ICC profile "
81 "available";
82
83 const QByteArray iccProfile(
84 reinterpret_cast<const char *>(chunk_iter.chunk.bytes),
85 static_cast<int>(chunk_iter.chunk.size));
86 const KoColorProfile *profile =
90 iccProfile);
91 imageColorSpace = KoColorSpaceRegistry::instance()->colorSpace(
94 profile);
95
96 // Assign as non-RGBA color space to convert it back later
97 if (!imageColorSpace) {
98 const QString colId = profile->colorModelID();
99 const KoColorProfile *cProfile =
101 colId,
103 iccProfile);
104 imageColorSpace = KoColorSpaceRegistry::instance()->colorSpace(
105 colId,
107 cProfile);
108 if (imageColorSpace) {
109 isRgba = false;
110 }
111 }
112 }
113 }
114 WebPDemuxReleaseChunkIterator(&chunk_iter);
115 }
116
117 if (isRgba && imageColorSpace) {
118 colorSpace = imageColorSpace;
119 }
120
121 const KoColor bgColor(
122 QColor(bg >> 8 & 0xFFu, bg >> 16 & 0xFFu, bg >> 24 & 0xFFu, bg & 0xFFu),
123 colorSpace);
124
125 KisImageSP image = new KisImage(document->createUndoStore(),
126 static_cast<qint32>(width),
127 static_cast<qint32>(height),
128 colorSpace,
129 i18n("WebP Image"));
130
131 KisPaintLayerSP layer(
132 new KisPaintLayer(image, image->nextLayerName(), 255));
133
134 {
135 WebPChunkIterator chunk_iter;
136 if (flags & EXIF_FLAG) {
137 if (WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter)) {
138 dbgFile << "Loading EXIF data. Size: " << chunk_iter.chunk.size;
139
140 QBuffer buf;
141 buf.setData(
142 reinterpret_cast<const char *>(chunk_iter.chunk.bytes),
143 static_cast<int>(chunk_iter.chunk.size));
144
145 const KisMetaData::IOBackend *backend =
147
148 backend->loadFrom(layer->metaData(), &buf);
149 }
150 }
151 WebPDemuxReleaseChunkIterator(&chunk_iter);
152 }
153
154 {
155 WebPChunkIterator chunk_iter;
156 if (flags & XMP_FLAG) {
157 if (WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter)) {
158 dbgFile << "Loading XMP data. Size: " << chunk_iter.chunk.size;
159
160 QBuffer buf;
161 buf.setData(
162 reinterpret_cast<const char *>(chunk_iter.chunk.bytes),
163 static_cast<int>(chunk_iter.chunk.size));
164
165 const KisMetaData::IOBackend *xmpBackend =
167
168 xmpBackend->loadFrom(layer->metaData(), &buf);
169 }
170 }
171 WebPDemuxReleaseChunkIterator(&chunk_iter);
172 }
173
174 {
175 WebPIterator iter;
176 if (WebPDemuxGetFrame(demux, 1, &iter)) {
177 int nextTimestamp = 0;
178 WebPDecoderConfig config;
179
180 KisPaintDeviceSP compositedFrame(
181 new KisPaintDevice(image->colorSpace()));
182
183 do {
184 if (!WebPInitDecoderConfig(&config)) {
185 dbgFile << "WebP decode config initialization failure";
187 }
188
189 {
190 const VP8StatusCode result =
191 WebPGetFeatures(iter.fragment.bytes,
192 iter.fragment.size,
193 &config.input);
194 dbgFile << "WebP import validation status: " << result;
195 switch (result) {
196 case VP8_STATUS_OK:
197 break;
198 case VP8_STATUS_OUT_OF_MEMORY:
200 case VP8_STATUS_INVALID_PARAM:
202 case VP8_STATUS_BITSTREAM_ERROR:
204 case VP8_STATUS_UNSUPPORTED_FEATURE:
206 case VP8_STATUS_SUSPENDED:
207 case VP8_STATUS_USER_ABORT:
210 case VP8_STATUS_NOT_ENOUGH_DATA:
212 }
213 }
214
215 // Doesn't make sense to ask for options for each individual
216 // frame. See jxl plugin for a similar approach.
217 config.output.colorspace = MODE_BGRA;
218 config.options.use_threads = 1;
219
220 {
221 const VP8StatusCode result = WebPDecode(iter.fragment.bytes,
222 iter.fragment.size,
223 &config);
224
225 dbgFile << "WebP frame:" << iter.frame_num
226 << ", import status: " << result;
227 switch (result) {
228 case VP8_STATUS_OK:
229 break;
230 case VP8_STATUS_OUT_OF_MEMORY:
232 case VP8_STATUS_INVALID_PARAM:
234 case VP8_STATUS_BITSTREAM_ERROR:
236 case VP8_STATUS_UNSUPPORTED_FEATURE:
238 case VP8_STATUS_SUSPENDED:
239 case VP8_STATUS_USER_ABORT:
242 case VP8_STATUS_NOT_ENOUGH_DATA:
244 }
245 }
246
247 // Check for "we're initializing the first frame".
248 // This code had previously "config.input.has_animation",
249 // this is incorrect when using the demuxer because
250 // each frame is yielded through GetFrame().
251 if (iter.num_frames > 0 && iter.frame_num == 1) {
252 dbgFile << "Animation detected, estimated framerate:"
253 << static_cast<double>(1000) / iter.duration;
254 const int framerate = std::lround(
255 1000.0 / static_cast<double>(iter.duration));
256 layer->enableAnimation();
258 image->animationInterface()->setFramerate(framerate);
259 }
260
261 const QRect bounds(
262 QPoint{iter.x_offset, iter.y_offset},
263 QSize{config.output.width, config.output.height});
264
265 {
266 KisPaintDeviceSP currentFrame(
267 new KisPaintDevice(image->colorSpace()));
268 currentFrame->fill(bounds, bgColor);
269
270 currentFrame->writeBytes(config.output.u.RGBA.rgba,
271 iter.x_offset,
272 iter.y_offset,
273 config.output.width,
274 config.output.height);
275
276 KisPainter painter(compositedFrame);
277 painter.setCompositeOpId(iter.blend_method == WEBP_MUX_BLEND
280
281 painter.bitBlt(
282 {iter.x_offset, iter.y_offset},
283 currentFrame,
284 {QPoint(iter.x_offset, iter.y_offset),
285 QSize(config.output.width, config.output.height)});
286 }
287
288 if (iter.num_frames > 1) {
289 const int currentFrameTime =
290 std::lround(static_cast<double>(nextTimestamp)
291 / static_cast<double>(iter.duration));
292 dbgFile << QString(
293 "Importing frame %1 @ %2, duration %3 ms, "
294 "blending %4, disposal %5")
295 .arg(iter.frame_num)
296 .arg(currentFrameTime)
297 .arg(iter.duration)
298 .arg(iter.blend_method)
299 .arg(iter.dispose_method)
300 .toStdString()
301 .c_str();
302 KisKeyframeChannel *channel = layer->getKeyframeChannel(
304 true);
305 auto *frame =
306 dynamic_cast<KisRasterKeyframeChannel *>(channel);
308 std::lround(static_cast<double>(nextTimestamp)
309 / static_cast<double>(iter.duration)));
310 frame->importFrame(currentFrameTime,
311 compositedFrame,
312 nullptr);
313 nextTimestamp += iter.duration;
314 } else {
315 layer->paintDevice()->makeCloneFrom(compositedFrame,
316 image->bounds());
317 }
318
319 if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
320 compositedFrame->fill(bounds, bgColor);
321 }
322
323 WebPFreeDecBuffer(&config.output);
324 } while (WebPDemuxNextFrame(&iter));
325 }
326 WebPDemuxReleaseIterator(&iter);
327 }
328
329 WebPDemuxDelete(demux);
330
331 image->addNode(layer.data(), image->rootLayer().data());
332
333 if (!isRgba) {
335 }
336
337 document->setCurrentImage(image);
338
340}
341
342#include "kis_webp_import.moc"
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
const QString COMPOSITE_OVER
const QString COMPOSITE_COPY
KisGroupLayerSP rootLayer() const
const KoColorSpace * colorSpace() const
KisImageAnimationInterface * animationInterface() const
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:715
void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
QRect bounds() const override
The base class for import and export filters.
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
static const KoID Raster
virtual bool loadFrom(Store *store, QIODevice *ioDevice) const =0
static KisMetadataBackendRegistry * instance()
void fill(const QRect &rc, const KoColor &color)
void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect)
void writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h)
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
void setCompositeOpId(const KoCompositeOp *op)
The KisRasterKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisRas...
~KisWebPImport() override
KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration=0) override
KisWebPImport(QObject *parent, const QVariantList &)
const T value(const QString &id) const
QString id() const
Definition KoID.cpp:63
K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin< KritaASCCDL >();) KritaASCCDL
#define bounds(x, a, b)
#define dbgFile
Definition kis_debug.h:53
KisKeyframeChannel * getKeyframeChannel(const QString &id, bool create)
void enableAnimation()
KisMetaData::Store * metaData()
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
KisPaintDeviceSP paintDevice
virtual QString colorModelID() const
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())
const KoColorProfile * createColorProfile(const QString &colorModelId, const QString &colorDepthId, const QByteArray &rawData)