Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions YACReader/YACReader.pro
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ macx {
lessThan(QT_MAJOR_VERSION, 6): QT += macextras
}

LIBS += -lavif -ljxl -ljxl_threads

QT += network widgets core multimedia svg

greaterThan(QT_MAJOR_VERSION, 5): QT += openglwidgets core5compat
Expand Down Expand Up @@ -109,6 +111,7 @@ HEADERS += ../common/comic.h \
../common/opengl_checker.h \
../common/pdf_comic.h \
../common/global_info_provider.h \
image_decoders.h \

!CONFIG(no_opengl) {
HEADERS += ../common/gl/yacreader_flow_gl.h \
Expand All @@ -117,6 +120,7 @@ HEADERS += ../common/comic.h \

SOURCES += ../common/comic.cpp \
configuration.cpp \
image_decoders.cpp \
goto_dialog.cpp \
magnifying_glass.cpp \
main_window_viewer.cpp \
Expand Down
124 changes: 124 additions & 0 deletions YACReader/image_decoders.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include "image_decoders.h"

#include <avif/avif.h>
#include <jxl/decode.h>
#include <jxl/decode_cxx.h>
#include <jxl/resizable_parallel_runner.h>
#include <jxl/types.h>
#include <vector>

bool isAvif(const QByteArray &data)
{
if (data.size() < 12)
return false;
return (data.at(4) == 'f' && data.at(5) == 't' && data.at(6) == 'y' && data.at(7) == 'p' &&
data.at(8) == 'a' && data.at(9) == 'v' && data.at(10) == 'i' && data.at(11) == 'f');
}

bool isJxl(const QByteArray &data)
{
if (data.size() < 2)
return false;
return (static_cast<quint8>(data.at(0)) == 0xFF && static_cast<quint8>(data.at(1)) == 0x0A);
}

QImage decodeAvif(const QByteArray &data)
{
avifDecoder *decoder = avifDecoderCreate();
avifResult result = avifDecoderSetIOMemory(decoder, (const uint8_t *)data.constData(), data.size());
if (result != AVIF_RESULT_OK) {
avifDecoderDestroy(decoder);
return QImage();
}

result = avifDecoderParse(decoder);
if (result != AVIF_RESULT_OK) {
avifDecoderDestroy(decoder);
return QImage();
}

QImage image;
if (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) {
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, decoder->image);
rgb.format = AVIF_RGB_FORMAT_RGBA;
rgb.depth = 8;

avifRGBImageAllocatePixels(&rgb);
avifImageYUVToRGB(decoder->image, &rgb);
image = QImage(rgb.pixels, decoder->image->width, decoder->image->height, QImage::Format_RGBA8888).copy();
avifRGBImageFreePixels(&rgb);
}

avifDecoderDestroy(decoder);
return image.convertToFormat(QImage::Format_ARGB32);
}

QImage decodeJxl(const QByteArray &data)
{
auto dec = JxlDecoderMake(nullptr);
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE)) {
return QImage();
}

void* runner = JxlResizableParallelRunnerCreate(nullptr);
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner)) {
JxlResizableParallelRunnerDestroy(runner);
return QImage();
}

JxlBasicInfo info;
JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
std::vector<uint8_t> pixels;

JxlDecoderSetInput(dec.get(), (const uint8_t *)data.constData(), data.size());
JxlDecoderCloseInput(dec.get());

for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
JxlResizableParallelRunnerDestroy(runner);
return QImage();
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
JxlResizableParallelRunnerDestroy(runner);
return QImage();
} else if (status == JXL_DEC_BASIC_INFO) {
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
JxlResizableParallelRunnerDestroy(runner);
return QImage();
}
JxlResizableParallelRunnerSetThreads(runner,
JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
} else if (status == JXL_DEC_SUCCESS) {
break;
} else if (status == JXL_DEC_FULL_IMAGE) {
// Nothing to do.
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
size_t buffer_size;
if (JXL_DEC_SUCCESS !=
JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
JxlResizableParallelRunnerDestroy(runner);
return QImage();
}
if (buffer_size != info.xsize * info.ysize * 4) {
JxlResizableParallelRunnerDestroy(runner);
return QImage();
}
pixels.resize(buffer_size);
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels.data(), pixels.size())) {
JxlResizableParallelRunnerDestroy(runner);
return QImage();
}
} else {
JxlResizableParallelRunnerDestroy(runner);
return QImage();
}
}

JxlResizableParallelRunnerDestroy(runner);

if(pixels.empty())
return QImage();

return QImage(pixels.data(), info.xsize, info.ysize, QImage::Format_RGBA8888).copy().convertToFormat(QImage::Format_ARGB32);
}
12 changes: 12 additions & 0 deletions YACReader/image_decoders.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef IMAGE_DECODERS_H
#define IMAGE_DECODERS_H

#include <QImage>
#include <QByteArray>

bool isAvif(const QByteArray &data);
bool isJxl(const QByteArray &data);
QImage decodeAvif(const QByteArray &data);
QImage decodeJxl(const QByteArray &data);

#endif // IMAGE_DECODERS_H
10 changes: 9 additions & 1 deletion YACReader/render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "comic_db.h"
#include "yacreader_global_gui.h"
#include "configuration.h"
#include "image_decoders.h"

template<class T>
inline const T &kClamp(const T &x, const T &low, const T &high)
Expand Down Expand Up @@ -346,7 +347,14 @@ void PageRender::run()
QMutexLocker locker(&(render->mutex));

QImage img;
img.loadFromData(data);
if (isAvif(data)) {
img = decodeAvif(data);
} else if (isJxl(data)) {
img = decodeJxl(data);
} else {
img.loadFromData(data);
}

if (degrees > 0) {
QTransform m;
m.rotate(degrees);
Expand Down
4 changes: 3 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ RUN \
7zip \
7zip-rar \
libpoppler-qt6-dev \
zlib1g-dev && \
zlib1g-dev \
libavif-dev \
libjxl-dev && \
ldconfig


Expand Down
28 changes: 28 additions & 0 deletions docker/Dockerfile.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM ghcr.io/linuxserver/baseimage-ubuntu:noble AS builder

# env variables
ARG DEBIAN_FRONTEND="noninteractive"

# install build packages
RUN \
apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
git \
qt6-tools-dev \
qt6-base-dev-tools \
qmake6 \
libqt6core5compat6-dev \
libavif-dev \
libjxl-dev \
qt6-declarative-dev && \
ldconfig

# copy the local repository to the image
COPY . /yacreader

# build and run the test
RUN cd /yacreader/tests/image_format_test && \
qmake6 image_format_test.pro && \
make && \
./image_format_test
16 changes: 16 additions & 0 deletions tests/image_format_test/image_format_test.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
TEMPLATE = app
CONFIG += console

SOURCES += \
main.cpp \
../../YACReader/image_decoders.cpp

HEADERS += \
../../YACReader/image_decoders.h

QT += core

LIBS += -lavif -ljxl -ljxl_threads

RESOURCES += \
test_images.qrc
37 changes: 37 additions & 0 deletions tests/image_format_test/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <QCoreApplication>
#include <QFile>
#include <QDebug>
#include <QImage>
#include "../../YACReader/image_decoders.h"

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

QFile avifFile(":/sample.avif");
if (!avifFile.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open sample.avif";
return 1;
}
QByteArray avifData = avifFile.readAll();
QImage avifImage = decodeAvif(avifData);
if (avifImage.isNull()) {
qCritical() << "Failed to decode sample.avif";
return 1;
}

QFile jxlFile(":/sample.jxl");
if (!jxlFile.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open sample.jxl";
return 1;
}
QByteArray jxlData = jxlFile.readAll();
QImage jxlImage = decodeJxl(jxlData);
if (jxlImage.isNull()) {
qCritical() << "Failed to decode sample.jxl";
return 1;
}

qDebug() << "Successfully decoded sample.avif and sample.jxl";
return 0;
}
Binary file added tests/image_format_test/original sample.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions tests/image_format_test/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[Public domain comic image from the Digital Comic Museum](https://digitalcomicmuseum.com/index.php?dlid=2100)

`original sample.jpg` was converted to several image formats at around 50% quality. These new sample images will be used in tests.
Binary file added tests/image_format_test/sample.avif
Binary file not shown.
Binary file added tests/image_format_test/sample.heic
Binary file not shown.
Binary file added tests/image_format_test/sample.jxl
Binary file not shown.
Binary file added tests/image_format_test/sample.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions tests/image_format_test/test_images.qrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>sample.avif</file>
<file>sample.jxl</file>
</qresource>
</RCC>