Mirco Miranda 97120b2537 Simplified read/verify header process
Where possible, QIODevice::peek has been used instead of transactions or instead of using ungetchar() for sequential access devices and seek() for random access devices.

Furthermore:
- RAS format gained the ability of read on sequential devices.
- Removed unused code in XCF (still related to ungetchar and sequential devices).
- These changes should prevent errors like the ones fixed by MR !258
2024-10-06 17:26:25 +00:00

281 lines
6.1 KiB
C++

/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "pxr_p.h"
#include "util_p.h"
#include <QIODevice>
#include <QImage>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(LOG_PXRPLUGIN)
Q_LOGGING_CATEGORY(LOG_PXRPLUGIN, "kf.imageformats.plugins.pxr", QtWarningMsg)
class PXRHeader
{
private:
QByteArray m_rawHeader;
quint16 ui16(quint8 c1, quint8 c2) const {
return (quint16(c2) << 8) | quint16(c1);
}
quint32 ui32(quint8 c1, quint8 c2, quint8 c3, quint8 c4) const {
return (quint32(c4) << 24) | (quint32(c3) << 16) | (quint32(c2) << 8) | quint32(c1);
}
public:
PXRHeader()
{
}
bool isValid() const
{
return (m_rawHeader.size() == 512 &&
m_rawHeader.startsWith(QByteArray::fromRawData("\x80\xE8\x00\x00", 4)));
}
bool isSupported() const
{
return format() != QImage::Format_Invalid;
}
qint32 width() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(m_rawHeader.at(418), m_rawHeader.at(419)));
}
qint32 height() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(m_rawHeader.at(416), m_rawHeader.at(417)));
}
QSize size() const
{
return QSize(width(), height());
}
qint32 channel() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(m_rawHeader.at(424), m_rawHeader.at(425)));
}
qint32 depth() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(m_rawHeader.at(426), m_rawHeader.at(427)));
}
// supposing the image offset (always 1024 on sample files)
qint32 offset() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(m_rawHeader.at(428), m_rawHeader.at(429)));
}
QImage::Format format() const
{
if (channel() == 14 && depth() == 2) {
return QImage::Format_RGB888;
}
if (channel() == 8 && depth() == 2) {
return QImage::Format_Grayscale8;
}
return QImage::Format_Invalid;
}
qsizetype strideSize() const
{
if (format() == QImage::Format_RGB888) {
return width() * 3;
}
if (format() == QImage::Format_Grayscale8) {
return width();
}
return 0;
}
bool read(QIODevice *d)
{
m_rawHeader = d->read(512);
return isValid();
}
bool peek(QIODevice *d)
{
m_rawHeader = d->peek(512);
return isValid();
}
bool jumpToImageData(QIODevice *d) const
{
if (d->isSequential()) {
if (auto sz = std::max(offset() - qint32(m_rawHeader.size()), 0)) {
return d->read(sz).size() == sz;
}
return true;
}
return d->seek(offset());
}
};
class PXRHandlerPrivate
{
public:
PXRHandlerPrivate() {}
~PXRHandlerPrivate() {}
PXRHeader m_header;
};
PXRHandler::PXRHandler()
: QImageIOHandler()
, d(new PXRHandlerPrivate)
{
}
bool PXRHandler::canRead() const
{
if (canRead(device())) {
setFormat("pxr");
return true;
}
return false;
}
bool PXRHandler::canRead(QIODevice *device)
{
if (!device) {
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::canRead() called with no device";
return false;
}
PXRHeader h;
if (!h.peek(device)) {
return false;
}
return h.isSupported();
}
bool PXRHandler::read(QImage *image)
{
auto&& header = d->m_header;
if (!header.read(device())) {
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() invalid header";
return false;
}
auto img = imageAlloc(header.size(), header.format());
if (img.isNull()) {
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while allocating the image";
return false;
}
auto d = device();
if (!header.jumpToImageData(d)) {
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while seeking image data";
return false;
}
auto size = std::min(img.bytesPerLine(), header.strideSize());
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y));
if (d->read(line, size) != size) {
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while reading image scanline";
return false;
}
}
*image = img;
return true;
}
bool PXRHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
return false;
}
QVariant PXRHandler::option(ImageOption option) const
{
QVariant v;
if (option == QImageIOHandler::Size) {
auto&& h = d->m_header;
if (h.isValid()) {
v = QVariant::fromValue(h.size());
} else if (auto d = device()) {
if (h.peek(d)) {
v = QVariant::fromValue(h.size());
}
}
}
if (option == QImageIOHandler::ImageFormat) {
auto&& h = d->m_header;
if (h.isValid()) {
v = QVariant::fromValue(h.format());
} else if (auto d = device()) {
if (h.peek(d)) {
v = QVariant::fromValue(h.format());
}
}
}
return v;
}
QImageIOPlugin::Capabilities PXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "pxr") {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && PXRHandler::canRead(device)) {
cap |= CanRead;
}
return cap;
}
QImageIOHandler *PXRPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new PXRHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}
#include "moc_pxr_p.cpp"