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
43 QoiHeader(
const QoiHeader&) =
default;
44 QoiHeader& operator=(
const QoiHeader&) =
default;
56 return r == other.r && g == other.g && b == other.b && a == other.a;
66 s >> head.MagicNumber;
76 s << head.MagicNumber;
84static bool IsSupported(
const QoiHeader &head)
87 if (head.MagicNumber != QOI_MAGIC) {
91 if (head.Width == 0 || head.Height == 0 || head.Channels < 3 || head.Colorspace > 1) {
95 if (head.Width > 300000 || head.Height > 300000) {
101static int QoiHash(
const Px &px)
103 return px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11;
108 if (IsSupported(head)) {
114static bool LoadQOI(
QIODevice *device,
const QoiHeader &qoi,
QImage &img)
116 Px index[64] = {Px{0, 0, 0, 0}};
117 Px px = Px{0, 0, 0, 255};
124 quint64 px_len = std::max(quint64(1024), quint64(qoi.Width) * qoi.Channels * 3 / 2);
125 if (px_len > kMaxQVectorSize) {
130 img = imageAlloc(qoi.Width, qoi.Height, imageFormat(qoi));
137 if (qoi.Colorspace) {
145 for (quint32 y = 0, run = 0; y < qoi.Height; ++y) {
146 if (quint64(ba.
size()) < px_len) {
150 if (ba.
size() < QOI_END_STREAM_PAD) {
154 quint64 chunks_len = ba.
size() - QOI_END_STREAM_PAD;
156 QRgb *scanline =
reinterpret_cast<QRgb *
>(img.
scanLine(y));
157 const quint8 *input =
reinterpret_cast<const quint8 *
>(ba.
constData());
158 for (quint32 x = 0; x < qoi.Width; ++x) {
161 }
else if (p < chunks_len) {
162 quint32 b1 = input[p++];
164 if (b1 == QOI_OP_RGB) {
168 }
else if (b1 == QOI_OP_RGBA) {
173 }
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
175 }
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
176 px.r += ((b1 >> 4) & 0x03) - 2;
177 px.g += ((b1 >> 2) & 0x03) - 2;
178 px.b += (b1 & 0x03) - 2;
179 }
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
180 quint32 b2 = input[p++];
181 quint32 vg = (b1 & 0x3f) - 32;
182 px.r += vg - 8 + ((b2 >> 4) & 0x0f);
184 px.b += vg - 8 + (b2 & 0x0f);
185 }
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
188 index[QoiHash(px) & 0x3F] = px;
191 scanline[x] = qRgba(px.r, px.g, px.b, px.a);
205static bool SaveQOI(
QIODevice *device,
const QoiHeader &qoi,
const QImage &img)
207 Px index[64] = {Px{0, 0, 0, 0}};
208 Px px = Px{0, 0, 0, 255};
212 auto channels = qoi.Channels;
220 for (
auto h = img.
height(), y = 0; y < h; ++y) {
221 auto pixels = converter.convertedScanLine(img, y);
222 if (pixels ==
nullptr) {
226 for (
auto w = img.
width() * channels, px_pos = 0; px_pos < w; px_pos += channels) {
227 px.r = pixels[px_pos + 0];
228 px.g = pixels[px_pos + 1];
229 px.b = pixels[px_pos + 2];
232 px.a = pixels[px_pos + 3];
237 if (run == 62 || (px_pos == w - channels && y == h - 1)) {
238 ba.
append(QOI_OP_RUN | (run - 1));
245 ba.
append(QOI_OP_RUN | (run - 1));
249 index_pos = QoiHash(px) & 0x3F;
251 if (index[index_pos] == px) {
252 ba.
append(QOI_OP_INDEX | index_pos);
254 index[index_pos] = px;
256 if (px.a == px_prev.a) {
257 signed char vr = px.r - px_prev.r;
258 signed char vg = px.g - px_prev.g;
259 signed char vb = px.b - px_prev.b;
261 signed char vg_r = vr - vg;
262 signed char vg_b = vb - vg;
264 if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2) {
265 ba.
append(QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2));
266 }
else if (vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 && vg_b < 8) {
267 ba.
append(QOI_OP_LUMA | (vg + 32));
268 ba.
append((vg_r + 8) << 4 | (vg_b + 8));
270 ba.
append(
char(QOI_OP_RGB));
276 ba.
append(
char(QOI_OP_RGBA));
287 auto written = device->
write(ba);
300 for (qint64 w = 0, write = 0, size = ba.
size(); write < size; write += w) {
312class QOIHandlerPrivate
315 QOIHandlerPrivate() {}
316 ~QOIHandlerPrivate() {}
322QOIHandler::QOIHandler()
324 , d(new QOIHandlerPrivate)
328bool QOIHandler::canRead()
const
330 if (canRead(device())) {
337bool QOIHandler::canRead(
QIODevice *device)
340 qWarning(
"QOIHandler::canRead() called with no device");
344 auto head = device->
peek(QOI_HEADER_SIZE);
345 if (head.size() < QOI_HEADER_SIZE) {
354 return IsSupported(qoi);
357bool QOIHandler::read(
QImage *image)
363 auto&& qoi = d->m_header;
367 if (!IsSupported(qoi)) {
372 bool result = LoadQOI(s.device(), qoi, img);
374 if (result ==
false) {
382bool QOIHandler::write(
const QImage &image)
389 qoi.MagicNumber = QOI_MAGIC;
390 qoi.Width = image.
width();
391 qoi.Height = image.
height();
395 if (!IsSupported(qoi)) {
406 return SaveQOI(s.device(), qoi, image);
409bool QOIHandler::supportsOption(ImageOption option)
const
420QVariant QOIHandler::option(ImageOption option)
const
425 auto&& header = d->m_header;
426 if (IsSupported(header)) {
428 }
else if (
auto d = device()) {
439 auto&& header = d->m_header;
440 if (IsSupported(header)) {
442 }
else if (
auto d = device()) {
457 if (format ==
"qoi" || format ==
"QOI") {
468 if (device->
isReadable() && QOIHandler::canRead(device)) {
485#include "moc_qoi_p.cpp"
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QFlags< Capability > Capabilities
KTEXTEDITOR_EXPORT QDebug operator<<(QDebug s, const MovingCursor &cursor)
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 peek(qint64 maxSize)
QByteArray read(qint64 maxSize)
qint64 write(const QByteArray &data)
bool operator==(const QGraphicsApiFilter &reference, const QGraphicsApiFilter &sample)
QFuture< T > run(Function function,...)
QVariant fromValue(T &&value)