|
| 1 | +#include "image_decoders.h" |
| 2 | + |
| 3 | +#include <avif/avif.h> |
| 4 | +#include <jxl/decode.h> |
| 5 | +#include <jxl/decode_cxx.h> |
| 6 | +#include <jxl/resizable_parallel_runner.h> |
| 7 | +#include <jxl/types.h> |
| 8 | +#include <vector> |
| 9 | + |
| 10 | +bool isAvif(const QByteArray &data) |
| 11 | +{ |
| 12 | + if (data.size() < 12) |
| 13 | + return false; |
| 14 | + return (data.at(4) == 'f' && data.at(5) == 't' && data.at(6) == 'y' && data.at(7) == 'p' && |
| 15 | + data.at(8) == 'a' && data.at(9) == 'v' && data.at(10) == 'i' && data.at(11) == 'f'); |
| 16 | +} |
| 17 | + |
| 18 | +bool isJxl(const QByteArray &data) |
| 19 | +{ |
| 20 | + if (data.size() < 2) |
| 21 | + return false; |
| 22 | + |
| 23 | + // Check for raw JXL codestream (starts with FF0A) |
| 24 | + if (static_cast<quint8>(data.at(0)) == 0xFF && static_cast<quint8>(data.at(1)) == 0x0A) |
| 25 | + return true; |
| 26 | + |
| 27 | + // Check for JXL container format (JXL signature box at offset 4) |
| 28 | + if (data.size() >= 12 && data.at(4) == 'J' && data.at(5) == 'X' && data.at(6) == 'L' && data.at(7) == ' ') |
| 29 | + return true; |
| 30 | + |
| 31 | + return false; |
| 32 | +} |
| 33 | + |
| 34 | +QImage decodeAvif(const QByteArray &data) |
| 35 | +{ |
| 36 | + avifDecoder *decoder = avifDecoderCreate(); |
| 37 | + avifResult result = avifDecoderSetIOMemory(decoder, (const uint8_t *)data.constData(), data.size()); |
| 38 | + if (result != AVIF_RESULT_OK) { |
| 39 | + avifDecoderDestroy(decoder); |
| 40 | + return QImage(); |
| 41 | + } |
| 42 | + |
| 43 | + result = avifDecoderParse(decoder); |
| 44 | + if (result != AVIF_RESULT_OK) { |
| 45 | + avifDecoderDestroy(decoder); |
| 46 | + return QImage(); |
| 47 | + } |
| 48 | + |
| 49 | + QImage image; |
| 50 | + if (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) { |
| 51 | + avifRGBImage rgb; |
| 52 | + avifRGBImageSetDefaults(&rgb, decoder->image); |
| 53 | + rgb.format = AVIF_RGB_FORMAT_RGBA; |
| 54 | + rgb.depth = 8; |
| 55 | + |
| 56 | + avifRGBImageAllocatePixels(&rgb); |
| 57 | + avifImageYUVToRGB(decoder->image, &rgb); |
| 58 | + image = QImage(rgb.pixels, decoder->image->width, decoder->image->height, QImage::Format_RGBA8888).copy(); |
| 59 | + avifRGBImageFreePixels(&rgb); |
| 60 | + } |
| 61 | + |
| 62 | + avifDecoderDestroy(decoder); |
| 63 | + return image.convertToFormat(QImage::Format_ARGB32); |
| 64 | +} |
| 65 | + |
| 66 | +QImage decodeJxl(const QByteArray &data) |
| 67 | +{ |
| 68 | + auto dec = JxlDecoderMake(nullptr); |
| 69 | + if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE)) { |
| 70 | + return QImage(); |
| 71 | + } |
| 72 | + |
| 73 | + void* runner = JxlResizableParallelRunnerCreate(nullptr); |
| 74 | + if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner)) { |
| 75 | + JxlResizableParallelRunnerDestroy(runner); |
| 76 | + return QImage(); |
| 77 | + } |
| 78 | + |
| 79 | + JxlBasicInfo info; |
| 80 | + JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; |
| 81 | + std::vector<uint8_t> pixels; |
| 82 | + |
| 83 | + JxlDecoderSetInput(dec.get(), (const uint8_t *)data.constData(), data.size()); |
| 84 | + JxlDecoderCloseInput(dec.get()); |
| 85 | + |
| 86 | + for (;;) { |
| 87 | + JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); |
| 88 | + if (status == JXL_DEC_ERROR) { |
| 89 | + JxlResizableParallelRunnerDestroy(runner); |
| 90 | + return QImage(); |
| 91 | + } else if (status == JXL_DEC_NEED_MORE_INPUT) { |
| 92 | + JxlResizableParallelRunnerDestroy(runner); |
| 93 | + return QImage(); |
| 94 | + } else if (status == JXL_DEC_BASIC_INFO) { |
| 95 | + if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) { |
| 96 | + JxlResizableParallelRunnerDestroy(runner); |
| 97 | + return QImage(); |
| 98 | + } |
| 99 | + JxlResizableParallelRunnerSetThreads(runner, |
| 100 | + JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize)); |
| 101 | + } else if (status == JXL_DEC_SUCCESS) { |
| 102 | + break; |
| 103 | + } else if (status == JXL_DEC_FULL_IMAGE) { |
| 104 | + // Nothing to do. |
| 105 | + } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { |
| 106 | + size_t buffer_size; |
| 107 | + if (JXL_DEC_SUCCESS != |
| 108 | + JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) { |
| 109 | + JxlResizableParallelRunnerDestroy(runner); |
| 110 | + return QImage(); |
| 111 | + } |
| 112 | + if (buffer_size != info.xsize * info.ysize * 4) { |
| 113 | + JxlResizableParallelRunnerDestroy(runner); |
| 114 | + return QImage(); |
| 115 | + } |
| 116 | + pixels.resize(buffer_size); |
| 117 | + if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels.data(), pixels.size())) { |
| 118 | + JxlResizableParallelRunnerDestroy(runner); |
| 119 | + return QImage(); |
| 120 | + } |
| 121 | + } else { |
| 122 | + JxlResizableParallelRunnerDestroy(runner); |
| 123 | + return QImage(); |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + JxlResizableParallelRunnerDestroy(runner); |
| 128 | + |
| 129 | + if(pixels.empty()) |
| 130 | + return QImage(); |
| 131 | + |
| 132 | + return QImage(pixels.data(), info.xsize, info.ysize, QImage::Format_RGBA8888).copy().convertToFormat(QImage::Format_ARGB32); |
| 133 | +} |
0 commit comments