C

Qt Quick Ultralite imagedecoder Example

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial

#include "desktopimagedecoder.h"
#include <QBuffer>
#include <QImage>
#include <QImageReader>

#include <platforminterface/log.h>

static QImage::Format convertColorFormat(Qul::PixelFormat pixelFormat)
{
    switch (pixelFormat) {
    case Qul::PixelFormat_ARGB32:
        return QImage::Format_ARGB32;
    case Qul::PixelFormat_ARGB32_Premultiplied:
        return QImage::Format_ARGB32_Premultiplied;
    case Qul::PixelFormat_RGB32:
        return QImage::Format_RGB32;
    case Qul::PixelFormat_RGB888:
        return QImage::Format_RGB888;
    case Qul::PixelFormat_RGB16:
        return QImage::Format_RGB16;
    case Qul::PixelFormat_Alpha8:
        return QImage::Format_Alpha8;
    case Qul::PixelFormat_Alpha1:
        return QImage::Format_Mono;
    case Qul::PixelFormat_ARGB4444: // Unsupported?
        return QImage::Format_Invalid;
    case Qul::PixelFormat_ARGB4444_Premultiplied:
        return QImage::Format_ARGB4444_Premultiplied;
    case Qul::PixelFormat_RGB332: // Unsupported?
        return QImage::Format_Invalid;
    case Qul::PixelFormat_RLE_ARGB32:
    case Qul::PixelFormat_RLE_ARGB32_Premultiplied:
    case Qul::PixelFormat_RLE_RGB32:
    case Qul::PixelFormat_RLE_RGB888:
        return QImage::Format_Invalid;
    default:
        return QImage::Format_Invalid;
    }
}

DesktopImageDecoder::DesktopImageDecoder() {}

DesktopImageDecoder::~DesktopImageDecoder() {}

static bool probe(const unsigned char *data, uint32_t size)
{
    // Detect JPG images
    // FF D8 FF E0 xx xx 4A 46
    // FF D8 FF E1 xx xx 45 78
    // FF D8 FF E8 xx xx 53 50

    if (size < 8)
        return false;

    if (data[0] != 0xff || data[1] != 0xd8 || data[2] != 0xff)
        return false;

    if (data[3] == 0xe0 && data[6] == 0x4a && data[7] == 0x46)
        return true;
    else if (data[3] == 0xe1 && data[6] == 0x45 && data[7] == 0x78)
        return true;
    else if (data[3] == 0xe8 && data[6] == 0x53 && data[7] == 0x50)
        return true;

    return false;
}

bool DesktopImageDecoder::imageInformation(RequestDataCallback &callback,
                                           int16_t *width,
                                           int16_t *height,
                                           Qul::PixelFormat *actualPixelFormat,
                                           Qul::PixelFormat optimalOpaquePixelFormat,
                                           Qul::PixelFormat optimalAlphaPixelFormat)
{
    QUL_UNUSED(optimalOpaquePixelFormat);
    QUL_UNUSED(optimalAlphaPixelFormat);

    {
        unsigned char buffer[8];
        if (callback.readData(buffer, 0, sizeof(buffer)) != sizeof(buffer))
            return false;
        if (!probe(buffer, sizeof(buffer)))
            return false;
    }

    uint32_t offset = 0;

    while (1) {
        unsigned char marker;
        do {
            if (callback.readData(&marker, offset, sizeof(marker)) != sizeof(marker))
                return false;
            offset += sizeof(marker);
        } while (marker == 0xff); // Ignore start and padding bytes

        if (marker > 0xd0 && marker <= 0xd8)
            continue;
        if (marker == 0xd9)
            return false;   // EOI without finding a size block
        if (marker == 0x01) // TEM
            continue;

        if (marker == 0xc0) {
            unsigned char buffer[8];

            if (callback.readData(buffer, offset, sizeof(buffer)) != sizeof(buffer))
                return false;
            offset += sizeof(buffer);

            *height = buffer[3];
            *height <<= 8;
            *height += buffer[4];

            *width = buffer[5];
            *width <<= 8;
            *width += buffer[6];
#if 0 // In case we need this some time
            int numColorComponents = buffer[7];
            int bitsPerChannel = buffer[2];
            Qul::PlatformInterface::log("bpp %d\n", bitsPerChannel * numColorComponents);
#endif
            *actualPixelFormat = Qul::PixelFormat_RGB32;
            return true;
        }
        unsigned char buffer[2];
        if (callback.readData(buffer, offset, sizeof(buffer)) != sizeof(buffer))
            return false;
        offset += sizeof(buffer);
        uint32_t len = buffer[0];
        len <<= 8;
        len += buffer[1];
        len -= 2; // The two bytes from the length itself need to be subtracted

        offset += len; /* Discard data */
    }

    Qul::PlatformInterface::log("insufficient data for detecting image dimensions\n");
    return false;
}

int DesktopImageDecoder::decodeImage(RequestDataCallback &callback,
                                     unsigned char *outbuffer,
                                     uint32_t outbufferSize,
                                     Qul::PixelFormat pixelFormat,
                                     uint32_t requiredBytesPerLine)
{
    QBuffer qBuffer;

    if (callback.rawData()) {
        qBuffer.setData((const char *) callback.rawData(), callback.totalAvailableDataSize());
    } else {
        QByteArray data;
        data.fill('\0', callback.totalAvailableDataSize());
        if (callback.readData((unsigned char *) (data.data()), 0, data.capacity())
            != callback.totalAvailableDataSize()) {
            Qul::PlatformInterface::log("Insufficient data for read\n");
            return -1;
        }
        qBuffer.setData(data);
    }

    qBuffer.open(QBuffer::ReadOnly);
    QImageReader imageReader(&qBuffer);
    QImage image;

    if (!imageReader.read(&image)) {
        Qul::PlatformInterface::log(imageReader.errorString().toLocal8Bit().constData());
        return -1;
    }

    QImage::Format imageFormat = convertColorFormat(pixelFormat);
    if (imageFormat == QImage::Format_Invalid) {
        Qul::PlatformInterface::log("Unsupported image format\n");
        return -1;
    }
    image.convertTo(imageFormat);
    if (image.bytesPerLine() != requiredBytesPerLine) {
        Qul::PlatformInterface::log("Resulting scanline length is not expected and conversion is not implemented.\n");
        return -1;
    }

    const uint32_t datasize = image.size().height() * image.bytesPerLine();

    if (datasize > outbufferSize) {
        Qul::PlatformInterface::log("Outputbuffer has insufficient size of %d, required %d\n", outbufferSize, datasize);
        return -1;
    }

    memcpy(outbuffer, image.constBits(), datasize);
    return 0;
}