/* This file is part of the KDE project SPDX-FileCopyrightText: 2024 Mirco Miranda SPDX-License-Identifier: LGPL-2.0-or-later */ #include "pxr_p.h" #include "util_p.h" #include #include #include 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(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"