mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-05-28 00:30:23 -04:00
qoi: write support backported from master
This commit is contained in:
parent
b2b677b8a5
commit
906ecce500
@ -20,7 +20,6 @@ The following image formats have read-only support:
|
|||||||
- Photoshop documents (psd, psb, pdd, psdt)
|
- Photoshop documents (psd, psb, pdd, psdt)
|
||||||
- Radiance HDR (hdr)
|
- Radiance HDR (hdr)
|
||||||
- Sun Raster (ras)
|
- Sun Raster (ras)
|
||||||
- Quite OK Image format (qoi)
|
|
||||||
|
|
||||||
The following image formats have read and write support:
|
The following image formats have read and write support:
|
||||||
|
|
||||||
@ -28,6 +27,7 @@ The following image formats have read and write support:
|
|||||||
- Encapsulated PostScript (eps)
|
- Encapsulated PostScript (eps)
|
||||||
- JPEG XL (jxl)
|
- JPEG XL (jxl)
|
||||||
- Personal Computer Exchange (pcx)
|
- Personal Computer Exchange (pcx)
|
||||||
|
- Quite OK Image format (qoi)
|
||||||
- SGI images (rgb, rgba, sgi, bw)
|
- SGI images (rgb, rgba, sgi, bw)
|
||||||
- Softimage PIC (pic)
|
- Softimage PIC (pic)
|
||||||
- Targa (tga): supports more formats than Qt's version
|
- Targa (tga): supports more formats than Qt's version
|
||||||
|
@ -125,6 +125,7 @@ kimageformats_read_tests(FUZZ 1
|
|||||||
kimageformats_write_tests(
|
kimageformats_write_tests(
|
||||||
pcx-lossless
|
pcx-lossless
|
||||||
pic-lossless
|
pic-lossless
|
||||||
|
qoi-lossless
|
||||||
rgb-lossless
|
rgb-lossless
|
||||||
tga # fixme: the alpha images appear not to be written properly
|
tga # fixme: the alpha images appear not to be written properly
|
||||||
)
|
)
|
||||||
|
BIN
autotests/write/rgb.qoi
Normal file
BIN
autotests/write/rgb.qoi
Normal file
Binary file not shown.
BIN
autotests/write/rgba.qoi
Normal file
BIN
autotests/write/rgba.qoi
Normal file
Binary file not shown.
@ -133,7 +133,7 @@ endif()
|
|||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_qoi SOURCES qoi.cpp)
|
kimageformats_add_plugin(kimg_qoi SOURCES qoi.cpp scanlineconverter.cpp)
|
||||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
install(FILES qoi.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
install(FILES qoi.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
endif()
|
endif()
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
This file is part of the KDE project
|
This file is part of the KDE project
|
||||||
SPDX-FileCopyrightText: 2023 Ernest Gupik <ernestgupik@wp.pl>
|
SPDX-FileCopyrightText: 2023 Ernest Gupik <ernestgupik@wp.pl>
|
||||||
|
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qoi_p.h"
|
#include "qoi_p.h"
|
||||||
|
#include "scanlineconverter_p.h"
|
||||||
#include "util_p.h"
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <QColorSpace>
|
#include <QColorSpace>
|
||||||
@ -37,6 +39,10 @@ struct QoiHeader {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Px {
|
struct Px {
|
||||||
|
bool operator==(const Px &other) const
|
||||||
|
{
|
||||||
|
return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||||
|
}
|
||||||
quint8 r;
|
quint8 r;
|
||||||
quint8 g;
|
quint8 g;
|
||||||
quint8 b;
|
quint8 b;
|
||||||
@ -53,6 +59,16 @@ static QDataStream &operator>>(QDataStream &s, QoiHeader &head)
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QDataStream &operator<<(QDataStream &s, const QoiHeader &head)
|
||||||
|
{
|
||||||
|
s << head.MagicNumber;
|
||||||
|
s << head.Width;
|
||||||
|
s << head.Height;
|
||||||
|
s << head.Channels;
|
||||||
|
s << head.Colorspace;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
static bool IsSupported(const QoiHeader &head)
|
static bool IsSupported(const QoiHeader &head)
|
||||||
{
|
{
|
||||||
// Check magic number
|
// Check magic number
|
||||||
@ -85,19 +101,8 @@ static QImage::Format imageFormat(const QoiHeader &head)
|
|||||||
|
|
||||||
static bool LoadQOI(QIODevice *device, const QoiHeader &qoi, QImage &img)
|
static bool LoadQOI(QIODevice *device, const QoiHeader &qoi, QImage &img)
|
||||||
{
|
{
|
||||||
Px index[64] = {Px{
|
Px index[64] = {Px{0, 0, 0, 0}};
|
||||||
0,
|
Px px = Px{0, 0, 0, 255};
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
}};
|
|
||||||
|
|
||||||
Px px = Px{
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
255,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The px_len should be enough to read a complete "compressed" row: an uncompressible row can become
|
// The px_len should be enough to read a complete "compressed" row: an uncompressible row can become
|
||||||
// larger than the row itself. It should never be more than 1/3 (RGB) or 1/4 (RGBA) the length of the
|
// larger than the row itself. It should never be more than 1/3 (RGB) or 1/4 (RGBA) the length of the
|
||||||
@ -136,7 +141,7 @@ static bool LoadQOI(QIODevice *device, const QoiHeader &qoi, QImage &img)
|
|||||||
|
|
||||||
quint64 chunks_len = ba.size() - QOI_END_STREAM_PAD;
|
quint64 chunks_len = ba.size() - QOI_END_STREAM_PAD;
|
||||||
quint64 p = 0;
|
quint64 p = 0;
|
||||||
QRgb *scanline = (QRgb *)img.scanLine(y);
|
QRgb *scanline = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||||
const quint8 *input = reinterpret_cast<const quint8 *>(ba.constData());
|
const quint8 *input = reinterpret_cast<const quint8 *>(ba.constData());
|
||||||
for (quint32 x = 0; x < qoi.Width; ++x) {
|
for (quint32 x = 0; x < qoi.Width; ++x) {
|
||||||
if (run > 0) {
|
if (run > 0) {
|
||||||
@ -185,6 +190,111 @@ static bool LoadQOI(QIODevice *device, const QoiHeader &qoi, QImage &img)
|
|||||||
return (ba.startsWith(QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8)));
|
return (ba.startsWith(QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool SaveQOI(QIODevice *device, const QoiHeader &qoi, const QImage &img)
|
||||||
|
{
|
||||||
|
Px index[64] = {Px{0, 0, 0, 0}};
|
||||||
|
Px px = Px{0, 0, 0, 255};
|
||||||
|
Px px_prev = px;
|
||||||
|
|
||||||
|
auto run = 0;
|
||||||
|
auto channels = qoi.Channels;
|
||||||
|
|
||||||
|
QByteArray ba;
|
||||||
|
ba.reserve(img.width() * channels * 3 / 2);
|
||||||
|
|
||||||
|
ScanLineConverter converter(channels == 3 ? QImage::Format_RGB888 : QImage::Format_RGBA8888);
|
||||||
|
converter.setTargetColorSpace(QColorSpace(qoi.Colorspace == 1 ? QColorSpace::SRgbLinear : QColorSpace::SRgb));
|
||||||
|
|
||||||
|
for (auto h = img.height(), y = 0; y < h; ++y) {
|
||||||
|
auto pixels = converter.convertedScanLine(img, y);
|
||||||
|
if (pixels == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto w = img.width() * channels, px_pos = 0; px_pos < w; px_pos += channels) {
|
||||||
|
px.r = pixels[px_pos + 0];
|
||||||
|
px.g = pixels[px_pos + 1];
|
||||||
|
px.b = pixels[px_pos + 2];
|
||||||
|
|
||||||
|
if (channels == 4) {
|
||||||
|
px.a = pixels[px_pos + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (px == px_prev) {
|
||||||
|
run++;
|
||||||
|
if (run == 62 || (px_pos == w - channels && y == h - 1)) {
|
||||||
|
ba.append(QOI_OP_RUN | (run - 1));
|
||||||
|
run = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int index_pos;
|
||||||
|
|
||||||
|
if (run > 0) {
|
||||||
|
ba.append(QOI_OP_RUN | (run - 1));
|
||||||
|
run = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
index_pos = QoiHash(px) & 0x3F;
|
||||||
|
|
||||||
|
if (index[index_pos] == px) {
|
||||||
|
ba.append(QOI_OP_INDEX | index_pos);
|
||||||
|
} else {
|
||||||
|
index[index_pos] = px;
|
||||||
|
|
||||||
|
if (px.a == px_prev.a) {
|
||||||
|
signed char vr = px.r - px_prev.r;
|
||||||
|
signed char vg = px.g - px_prev.g;
|
||||||
|
signed char vb = px.b - px_prev.b;
|
||||||
|
|
||||||
|
signed char vg_r = vr - vg;
|
||||||
|
signed char vg_b = vb - vg;
|
||||||
|
|
||||||
|
if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2) {
|
||||||
|
ba.append(QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2));
|
||||||
|
} else if (vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 && vg_b < 8) {
|
||||||
|
ba.append(QOI_OP_LUMA | (vg + 32));
|
||||||
|
ba.append((vg_r + 8) << 4 | (vg_b + 8));
|
||||||
|
} else {
|
||||||
|
ba.append(char(QOI_OP_RGB));
|
||||||
|
ba.append(px.r);
|
||||||
|
ba.append(px.g);
|
||||||
|
ba.append(px.b);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ba.append(char(QOI_OP_RGBA));
|
||||||
|
ba.append(px.r);
|
||||||
|
ba.append(px.g);
|
||||||
|
ba.append(px.b);
|
||||||
|
ba.append(px.a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
px_prev = px;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto written = device->write(ba);
|
||||||
|
if (written < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (written) {
|
||||||
|
ba.remove(0, written);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QOI end of stream
|
||||||
|
ba.append(QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8));
|
||||||
|
|
||||||
|
// write remaining data
|
||||||
|
for (qint64 w = 0, write = 0, size = ba.size(); write < size; write += w) {
|
||||||
|
w = device->write(ba.constData() + write, size - write);
|
||||||
|
if (w < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QOIHandler::QOIHandler()
|
QOIHandler::QOIHandler()
|
||||||
@ -218,7 +328,7 @@ bool QOIHandler::canRead(QIODevice *device)
|
|||||||
|
|
||||||
QDataStream stream(head);
|
QDataStream stream(head);
|
||||||
stream.setByteOrder(QDataStream::BigEndian);
|
stream.setByteOrder(QDataStream::BigEndian);
|
||||||
QoiHeader qoi;
|
QoiHeader qoi = {0, 0, 0, 0, 2};
|
||||||
stream >> qoi;
|
stream >> qoi;
|
||||||
|
|
||||||
return IsSupported(qoi);
|
return IsSupported(qoi);
|
||||||
@ -230,7 +340,7 @@ bool QOIHandler::read(QImage *image)
|
|||||||
s.setByteOrder(QDataStream::BigEndian);
|
s.setByteOrder(QDataStream::BigEndian);
|
||||||
|
|
||||||
// Read image header
|
// Read image header
|
||||||
QoiHeader qoi;
|
QoiHeader qoi = {0, 0, 0, 0, 2};
|
||||||
s >> qoi;
|
s >> qoi;
|
||||||
|
|
||||||
// Check if file is supported
|
// Check if file is supported
|
||||||
@ -249,6 +359,33 @@ bool QOIHandler::read(QImage *image)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QOIHandler::write(const QImage &image)
|
||||||
|
{
|
||||||
|
if (image.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QoiHeader qoi;
|
||||||
|
qoi.MagicNumber = QOI_MAGIC;
|
||||||
|
qoi.Width = image.width();
|
||||||
|
qoi.Height = image.height();
|
||||||
|
qoi.Channels = image.hasAlphaChannel() ? 4 : 3;
|
||||||
|
qoi.Colorspace = image.colorSpace().transferFunction() == QColorSpace::TransferFunction::Linear ? 1 : 0;
|
||||||
|
|
||||||
|
if (!IsSupported(qoi)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream s(device());
|
||||||
|
s.setByteOrder(QDataStream::BigEndian);
|
||||||
|
s << qoi;
|
||||||
|
if (s.status() != QDataStream::Ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SaveQOI(s.device(), qoi, image);
|
||||||
|
}
|
||||||
|
|
||||||
bool QOIHandler::supportsOption(ImageOption option) const
|
bool QOIHandler::supportsOption(ImageOption option) const
|
||||||
{
|
{
|
||||||
if (option == QImageIOHandler::Size) {
|
if (option == QImageIOHandler::Size) {
|
||||||
@ -274,7 +411,7 @@ QVariant QOIHandler::option(ImageOption option) const
|
|||||||
QDataStream s(ba);
|
QDataStream s(ba);
|
||||||
s.setByteOrder(QDataStream::BigEndian);
|
s.setByteOrder(QDataStream::BigEndian);
|
||||||
|
|
||||||
QoiHeader header;
|
QoiHeader header = {0, 0, 0, 0, 2};
|
||||||
s >> header;
|
s >> header;
|
||||||
|
|
||||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||||
@ -293,7 +430,7 @@ QVariant QOIHandler::option(ImageOption option) const
|
|||||||
QDataStream s(ba);
|
QDataStream s(ba);
|
||||||
s.setByteOrder(QDataStream::BigEndian);
|
s.setByteOrder(QDataStream::BigEndian);
|
||||||
|
|
||||||
QoiHeader header;
|
QoiHeader header = {0, 0, 0, 0, 2};
|
||||||
s >> header;
|
s >> header;
|
||||||
|
|
||||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||||
@ -308,7 +445,7 @@ QVariant QOIHandler::option(ImageOption option) const
|
|||||||
QImageIOPlugin::Capabilities QOIPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
QImageIOPlugin::Capabilities QOIPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||||
{
|
{
|
||||||
if (format == "qoi" || format == "QOI") {
|
if (format == "qoi" || format == "QOI") {
|
||||||
return Capabilities(CanRead);
|
return Capabilities(CanRead | CanWrite);
|
||||||
}
|
}
|
||||||
if (!format.isEmpty()) {
|
if (!format.isEmpty()) {
|
||||||
return {};
|
return {};
|
||||||
@ -321,6 +458,9 @@ QImageIOPlugin::Capabilities QOIPlugin::capabilities(QIODevice *device, const QB
|
|||||||
if (device->isReadable() && QOIHandler::canRead(device)) {
|
if (device->isReadable() && QOIHandler::canRead(device)) {
|
||||||
cap |= CanRead;
|
cap |= CanRead;
|
||||||
}
|
}
|
||||||
|
if (device->isWritable()) {
|
||||||
|
cap |= CanWrite;
|
||||||
|
}
|
||||||
return cap;
|
return cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ public:
|
|||||||
|
|
||||||
bool canRead() const override;
|
bool canRead() const override;
|
||||||
bool read(QImage *image) override;
|
bool read(QImage *image) override;
|
||||||
|
bool write(const QImage &image) override;
|
||||||
|
|
||||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||||
|
101
src/imageformats/scanlineconverter.cpp
Normal file
101
src/imageformats/scanlineconverter.cpp
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "scanlineconverter_p.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
ScanLineConverter::ScanLineConverter(const QImage::Format &targetFormat)
|
||||||
|
: _targetFormat(targetFormat)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanLineConverter::ScanLineConverter(const ScanLineConverter &other)
|
||||||
|
: _targetFormat(other._targetFormat)
|
||||||
|
, _colorSpace(other._colorSpace)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanLineConverter &ScanLineConverter::operator=(const ScanLineConverter &other)
|
||||||
|
{
|
||||||
|
this->_targetFormat = other._targetFormat;
|
||||||
|
this->_colorSpace = other._colorSpace;
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage::Format ScanLineConverter::targetFormat() const
|
||||||
|
{
|
||||||
|
return _targetFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScanLineConverter::setTargetColorSpace(const QColorSpace &colorSpace)
|
||||||
|
{
|
||||||
|
_colorSpace = colorSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColorSpace ScanLineConverter::targetColorSpace() const
|
||||||
|
{
|
||||||
|
return _colorSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uchar *ScanLineConverter::convertedScanLine(const QImage &image, qint32 y)
|
||||||
|
{
|
||||||
|
auto colorSpaceConversion = isColorSpaceConversionNeeded(image, _colorSpace);
|
||||||
|
if (image.format() == _targetFormat && !colorSpaceConversion) {
|
||||||
|
return image.constScanLine(y);
|
||||||
|
}
|
||||||
|
if (image.width() != _tmpBuffer.width() || image.format() != _tmpBuffer.format()) {
|
||||||
|
_tmpBuffer = QImage(image.width(), 1, image.format());
|
||||||
|
}
|
||||||
|
if (_tmpBuffer.isNull()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::memcpy(_tmpBuffer.bits(), image.constScanLine(y), std::min(_tmpBuffer.bytesPerLine(), image.bytesPerLine()));
|
||||||
|
if (colorSpaceConversion) {
|
||||||
|
_tmpBuffer.setColorSpace(image.colorSpace());
|
||||||
|
_tmpBuffer.convertToColorSpace(_colorSpace);
|
||||||
|
}
|
||||||
|
_convBuffer = _tmpBuffer.convertToFormat(_targetFormat);
|
||||||
|
if (_convBuffer.isNull()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return _convBuffer.constBits();
|
||||||
|
}
|
||||||
|
|
||||||
|
qsizetype ScanLineConverter::bytesPerLine() const
|
||||||
|
{
|
||||||
|
if (_convBuffer.isNull()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return _convBuffer.bytesPerLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScanLineConverter::isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace) const
|
||||||
|
{
|
||||||
|
if (image.depth() < 24) { // RGB 8 bit or grater only
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto sourceColorSpace = image.colorSpace();
|
||||||
|
if (!sourceColorSpace.isValid() || !targetColorSpace.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stf = sourceColorSpace.transferFunction();
|
||||||
|
auto spr = sourceColorSpace.primaries();
|
||||||
|
auto ttf = targetColorSpace.transferFunction();
|
||||||
|
auto tpr = targetColorSpace.primaries();
|
||||||
|
// clang-format off
|
||||||
|
if (stf == QColorSpace::TransferFunction::Custom ||
|
||||||
|
ttf == QColorSpace::TransferFunction::Custom ||
|
||||||
|
spr == QColorSpace::Primaries::Custom ||
|
||||||
|
tpr == QColorSpace::Primaries::Custom) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
if (stf == ttf && spr == tpr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
79
src/imageformats/scanlineconverter_p.h
Normal file
79
src/imageformats/scanlineconverter_p.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SCANLINECONVERTER_P_H
|
||||||
|
#define SCANLINECONVERTER_P_H
|
||||||
|
|
||||||
|
#include <QColorSpace>
|
||||||
|
#include <QImage>
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The scanlineFormatConversion class
|
||||||
|
* A class to convert an image scan line. It introduces some overhead on small images
|
||||||
|
* but performs better on large images. :)
|
||||||
|
*/
|
||||||
|
class ScanLineConverter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScanLineConverter(const QImage::Format &targetFormat);
|
||||||
|
ScanLineConverter(const ScanLineConverter &other);
|
||||||
|
ScanLineConverter &operator=(const ScanLineConverter &other);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief targetFormat
|
||||||
|
* \return The target format set in the constructor.
|
||||||
|
*/
|
||||||
|
QImage::Format targetFormat() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief setTargetColorSpace
|
||||||
|
* Set the colorspace conversion.
|
||||||
|
*
|
||||||
|
* In addition to format conversion, it is also possible to convert the color
|
||||||
|
* space if the source image has a different one set.
|
||||||
|
* The conversion is done on the source format if and only if the image
|
||||||
|
* has a color depth greater than 24 bit and the color profile set is different
|
||||||
|
* from that of the image itself.
|
||||||
|
* \param colorSpace
|
||||||
|
*/
|
||||||
|
void setTargetColorSpace(const QColorSpace &colorSpace);
|
||||||
|
QColorSpace targetColorSpace() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief convertedScanLine
|
||||||
|
* Convert the scanline \a y.
|
||||||
|
* \note If the image format (and color space) is the same of converted format, it returns the image scan line.
|
||||||
|
* \return The scan line converted.
|
||||||
|
*/
|
||||||
|
const uchar *convertedScanLine(const QImage &image, qint32 y);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief bytesPerLine
|
||||||
|
* \return The size of the last converted scanline.
|
||||||
|
*/
|
||||||
|
qsizetype bytesPerLine() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief isColorSpaceConversionNeeded
|
||||||
|
* Calculates if a color space conversion is needed.
|
||||||
|
* \note Only 24 bit or grater images.
|
||||||
|
* \param image The source image.
|
||||||
|
* \param targetColorSpace The target color space.
|
||||||
|
* \return True if the conversion should be done otherwise false.
|
||||||
|
*/
|
||||||
|
bool isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// data
|
||||||
|
QImage::Format _targetFormat;
|
||||||
|
QColorSpace _colorSpace;
|
||||||
|
|
||||||
|
// internal buffers
|
||||||
|
QImage _tmpBuffer;
|
||||||
|
QImage _convBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SCANLINECONVERTER_P_H
|
Loading…
Reference in New Issue
Block a user