10#include "scanlineconverter_p.h"
21#define QOI_OP_INDEX 0x00
22#define QOI_OP_DIFF 0x40
23#define QOI_OP_LUMA 0x80
24#define QOI_OP_RUN 0xc0
25#define QOI_OP_RGB 0xfe
26#define QOI_OP_RGBA 0xff
27#define QOI_MASK_2 0xc0
29#define QOI_MAGIC (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | ((unsigned int)'i') << 8 | ((unsigned int)'f'))
30#define QOI_HEADER_SIZE 14
31#define QOI_END_STREAM_PAD 8
44 return r == other.r && g == other.g && b == other.b && a == other.a;
54 s >> head.MagicNumber;
64 s << head.MagicNumber;
72static bool IsSupported(
const QoiHeader &head)
75 if (head.MagicNumber != QOI_MAGIC) {
79 if (head.Width == 0 || head.Height == 0 || head.Channels < 3 || head.Colorspace > 1) {
83 if (head.Width > 300000 || head.Height > 300000) {
89static int QoiHash(
const Px &px)
91 return px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11;
96 if (IsSupported(head)) {
102static bool LoadQOI(
QIODevice *device,
const QoiHeader &qoi,
QImage &img)
104 Px index[64] = {Px{0, 0, 0, 0}};
105 Px px = Px{0, 0, 0, 255};
112 quint64 px_len = std::max(quint64(1024), quint64(qoi.Width) * qoi.Channels * 3 / 2);
113 if (px_len > kMaxQVectorSize) {
118 img = imageAlloc(qoi.Width, qoi.Height, imageFormat(qoi));
125 if (qoi.Colorspace) {
133 for (quint32 y = 0, run = 0; y < qoi.Height; ++y) {
134 if (quint64(ba.
size()) < px_len) {
138 if (ba.
size() < QOI_END_STREAM_PAD) {
142 quint64 chunks_len = ba.
size() - QOI_END_STREAM_PAD;
144 QRgb *scanline =
reinterpret_cast<QRgb *
>(img.
scanLine(y));
145 const quint8 *input =
reinterpret_cast<const quint8 *
>(ba.
constData());
146 for (quint32 x = 0; x < qoi.Width; ++x) {
149 }
else if (p < chunks_len) {
150 quint32 b1 = input[p++];
152 if (b1 == QOI_OP_RGB) {
156 }
else if (b1 == QOI_OP_RGBA) {
161 }
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
163 }
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
164 px.r += ((b1 >> 4) & 0x03) - 2;
165 px.g += ((b1 >> 2) & 0x03) - 2;
166 px.b += (b1 & 0x03) - 2;
167 }
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
168 quint32 b2 = input[p++];
169 quint32 vg = (b1 & 0x3f) - 32;
170 px.r += vg - 8 + ((b2 >> 4) & 0x0f);
172 px.b += vg - 8 + (b2 & 0x0f);
173 }
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
176 index[QoiHash(px) & 0x3F] = px;
179 scanline[x] = qRgba(px.r, px.g, px.b, px.a);
193static bool SaveQOI(
QIODevice *device,
const QoiHeader &qoi,
const QImage &img)
195 Px index[64] = {Px{0, 0, 0, 0}};
196 Px px = Px{0, 0, 0, 255};
200 auto channels = qoi.Channels;
208 for (
auto h = img.
height(), y = 0; y < h; ++y) {
209 auto pixels = converter.convertedScanLine(img, y);
210 if (pixels ==
nullptr) {
214 for (
auto w = img.
width() * channels, px_pos = 0; px_pos < w; px_pos += channels) {
215 px.r = pixels[px_pos + 0];
216 px.g = pixels[px_pos + 1];
217 px.b = pixels[px_pos + 2];
220 px.a = pixels[px_pos + 3];
225 if (run == 62 || (px_pos == w - channels && y == h - 1)) {
226 ba.
append(QOI_OP_RUN | (run - 1));
233 ba.
append(QOI_OP_RUN | (run - 1));
237 index_pos = QoiHash(px) & 0x3F;
239 if (index[index_pos] == px) {
240 ba.
append(QOI_OP_INDEX | index_pos);
242 index[index_pos] = px;
244 if (px.a == px_prev.a) {
245 signed char vr = px.r - px_prev.r;
246 signed char vg = px.g - px_prev.g;
247 signed char vb = px.b - px_prev.b;
249 signed char vg_r = vr - vg;
250 signed char vg_b = vb - vg;
252 if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2) {
253 ba.
append(QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2));
254 }
else if (vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 && vg_b < 8) {
255 ba.
append(QOI_OP_LUMA | (vg + 32));
256 ba.
append((vg_r + 8) << 4 | (vg_b + 8));
258 ba.
append(
char(QOI_OP_RGB));
264 ba.
append(
char(QOI_OP_RGBA));
275 auto written = device->
write(ba);
288 for (qint64 w = 0, write = 0, size = ba.
size(); write < size; write += w) {
300QOIHandler::QOIHandler()
304bool QOIHandler::canRead()
const
306 if (canRead(device())) {
313bool QOIHandler::canRead(
QIODevice *device)
316 qWarning(
"QOIHandler::canRead() called with no device");
322 qsizetype readBytes = head.
size();
325 if (readBytes < QOI_HEADER_SIZE) {
331 QoiHeader qoi = {0, 0, 0, 0, 2};
334 return IsSupported(qoi);
337bool QOIHandler::read(
QImage *image)
343 QoiHeader qoi = {0, 0, 0, 0, 2};
347 if (!IsSupported(qoi)) {
352 bool result = LoadQOI(s.device(), qoi, img);
354 if (result ==
false) {
362bool QOIHandler::write(
const QImage &image)
369 qoi.MagicNumber = QOI_MAGIC;
370 qoi.Width = image.
width();
371 qoi.Height = image.
height();
375 if (!IsSupported(qoi)) {
386 return SaveQOI(s.device(), qoi, image);
389bool QOIHandler::supportsOption(ImageOption option)
const
400QVariant QOIHandler::option(ImageOption option)
const
405 if (
auto d = device()) {
407 d->startTransaction();
408 auto ba = d->read(
sizeof(QoiHeader));
409 d->rollbackTransaction();
414 QoiHeader header = {0, 0, 0, 0, 2};
424 if (
auto d = device()) {
426 d->startTransaction();
427 auto ba = d->read(
sizeof(QoiHeader));
428 d->rollbackTransaction();
433 QoiHeader header = {0, 0, 0, 0, 2};
447 if (format ==
"qoi" || format ==
"QOI") {
458 if (device->
isReadable() && QOIHandler::canRead(device)) {
475#include "moc_qoi_p.cpp"
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QFlags< Capability > Capabilities
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &value)
QByteArray & append(QByteArrayView data)
const char * constData() const const
QByteArray fromRawData(const char *data, qsizetype size)
bool isEmpty() const const
QByteArray & remove(qsizetype pos, qsizetype len)
void reserve(qsizetype size)
qsizetype size() const const
bool startsWith(QByteArrayView bv) const const
TransferFunction transferFunction() const const
QColorSpace colorSpace() const const
bool hasAlphaChannel() const const
bool isNull() const const
void setColorSpace(const QColorSpace &colorSpace)
void setDevice(QIODevice *device)
bool isOpen() const const
bool isReadable() const const
bool isWritable() const const
QByteArray read(qint64 maxSize)
void rollbackTransaction()
qint64 write(const QByteArray &data)
bool operator==(const QGraphicsApiFilter &reference, const QGraphicsApiFilter &sample)
QFuture< T > run(Function function,...)
QVariant fromValue(T &&value)