qoi: write support

As a base I used the reference implementation found on the official site at https://qoiformat.org/ (MIT license).
I added a class to convert scan lines in scanlineconverter.cpp. The class takes advantage of the QImage conversion and contrary to what one might expect, with large images it improves performance (compared to converting the whole image) 😄 

In progressive mode, for each line, the following conversions (only if needed) are made before saving:
1. If the icc profile is set, the line is converted to sRGB or sRGB Linear.
2. The line is scaled to 8 bits with RGBA order.
This commit is contained in:
Mirco Miranda
2023-08-28 22:25:10 +00:00
committed by Daniel Novomeský
parent 4bd9d5baec
commit 8dc685df26
9 changed files with 337 additions and 16 deletions

View File

@ -0,0 +1,100 @@
/*
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "scanlineconverter_p.h"
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;
}