Skip to content
Closed
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,9 @@ c_x86_64.pch

compile_commands.json
.ccls-cache

local-build-logs

# Test artifacts (generated during testing, should not be committed)
tests/docker_config_test/
tests/test_library/library/.yacreaderlibrary/
16 changes: 16 additions & 0 deletions YACReaderLibrary/YACReaderLibrary.pro
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ DEFINES += SERVER_RELEASE YACREADER_LIBRARY
include (../config.pri)
include (../dependencies/pdf_backend.pri)

# AVIF and JXL image format support
unix {
CONFIG += link_pkgconfig
PKGCONFIG += libavif libjxl libjxl_threads
}
win32 {
# For Windows, manually specify paths if needed
LIBS += -lavif -ljxl -ljxl_threads
}
macx {
# For macOS, specify library paths if using Homebrew or MacPorts
LIBS += -lavif -ljxl -ljxl_threads
}

INCLUDEPATH += ../common/gl

# there are two builds for Windows, Desktop OpenGL based and ANGLE OpenGL ES based
Expand Down Expand Up @@ -75,6 +89,7 @@ greaterThan(QT_MAJOR_VERSION, 5): QT += openglwidgets core5compat
HEADERS += comic_flow.h \
../common/concurrent_queue.h \
../common/cover_utils.h \
../common/image_decoders.h \
create_library_dialog.h \
db/comic_query_result_processor.h \
db/folder_query_result_processor.h \
Expand Down Expand Up @@ -165,6 +180,7 @@ HEADERS += comic_flow.h \
SOURCES += comic_flow.cpp \
../common/concurrent_queue.cpp \
../common/cover_utils.cpp \
../common/image_decoders.cpp \
create_library_dialog.cpp \
db/comic_query_result_processor.cpp \
db/folder_query_result_processor.cpp \
Expand Down
7 changes: 4 additions & 3 deletions YACReaderLibrary/initial_comic_info_extractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,14 @@ void InitialComicInfoExtractor::extract()
int index = order.indexOf(fileNames.at(_coverPage - 1));

if (_target == "") {
if (!_cover.loadFromData(archive.getRawDataAtIndex(index))) {
_cover = loadImageFromData(archive.getRawDataAtIndex(index));
if (_cover.isNull()) {
QLOG_WARN() << "Extracting cover: unable to load image from extracted cover " << _fileSource;
_cover.load(":/images/notCover.png");
}
} else {
QImage p;
if (p.loadFromData(archive.getRawDataAtIndex(index))) {
QImage p = loadImageFromData(archive.getRawDataAtIndex(index));
if (!p.isNull()) {
_coverSize = QPair<int, int>(p.width(), p.height());
saveCover(_target, p);
} else {
Expand Down
16 changes: 16 additions & 0 deletions YACReaderLibraryServer/YACReaderLibraryServer.pro
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ DEFINES += SERVER_RELEASE YACREADER_LIBRARY
# load default build flags
# do a basic dependency check
include(headless_config.pri)

# AVIF and JXL image format support
unix {
CONFIG += link_pkgconfig
PKGCONFIG += libavif libjxl libjxl_threads
}
win32 {
# For Windows, manually specify paths if needed
LIBS += -lavif -ljxl -ljxl_threads
}
macx {
# For macOS, specify library paths if using Homebrew or MacPorts
LIBS += -lavif -ljxl -ljxl_threads
}
include(../dependencies/pdf_backend.pri)
include(../third_party/QrCode/QrCode.pri)

Expand Down Expand Up @@ -58,6 +72,7 @@ HEADERS += ../YACReaderLibrary/library_creator.h \
../common/qnaturalsorting.h \
../common/yacreader_global.h \
../common/cover_utils.h \
../common/image_decoders.h \
../YACReaderLibrary/yacreader_local_server.h \
../YACReaderLibrary/comics_remover.h \
../common/http_worker.h \
Expand Down Expand Up @@ -89,6 +104,7 @@ SOURCES += ../YACReaderLibrary/library_creator.cpp \
../common/bookmarks.cpp \
../common/qnaturalsorting.cpp \
../common/cover_utils.cpp \
../common/image_decoders.cpp \
../YACReaderLibrary/yacreader_local_server.cpp \
../YACReaderLibrary/comics_remover.cpp \
../common/http_worker.cpp \
Expand Down
10 changes: 8 additions & 2 deletions common/comic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ QStringList Comic::getSupportedImageLiteralFormats()
for (QByteArray &item : supportedImageFormats) {
supportedImageFormatStrings.append(QString::fromLocal8Bit(item));
}
// Add custom supported formats (AVIF and JXL)
supportedImageFormatStrings << "avif" << "jxl";
return supportedImageFormatStrings;
}

Expand All @@ -50,15 +52,19 @@ const QStringList Comic::imageExtensions = QStringList() << "*.jpg"
<< "*.tiff"
<< "*.tif"
<< "*.bmp"
<< "*.webp";
<< "*.webp"
<< "*.avif"
<< "*.jxl";
const QStringList Comic::literalImageExtensions = QStringList() << "jpg"
<< "jpeg"
<< "png"
<< "gif"
<< "tiff"
<< "tif"
<< "bmp"
<< "webp";
<< "webp"
<< "avif"
<< "jxl";

#ifndef use_unarr
const QStringList ComicArchiveExtensions = QStringList() << "*.cbr"
Expand Down
17 changes: 17 additions & 0 deletions common/cover_utils.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
#include "cover_utils.h"
#include "image_decoders.h"

QImage YACReader::loadImageFromData(const QByteArray &data)
{
// Try AVIF first
if (isAvif(data)) {
return decodeAvif(data);
}
// Try JXL
if (isJxl(data)) {
return decodeJxl(data);
}
// Fall back to Qt's built-in loaders (JPEG, PNG, etc.)
QImage image;
image.loadFromData(data);
return image;
}

bool YACReader::saveCover(const QString &path, const QImage &cover)
{
Expand Down
2 changes: 2 additions & 0 deletions common/cover_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
#define COVER_UTILS_H

#include <QImage>
#include <QByteArray>

namespace YACReader {
bool saveCover(const QString &path, const QImage &image);
QImage loadImageFromData(const QByteArray &data);
}
#endif // COVER_UTILS_H
133 changes: 133 additions & 0 deletions common/image_decoders.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#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;

// Check for raw JXL codestream (starts with FF0A)
if (static_cast<quint8>(data.at(0)) == 0xFF && static_cast<quint8>(data.at(1)) == 0x0A)
return true;

// Check for JXL container format (JXL signature box at offset 4)
if (data.size() >= 12 && data.at(4) == 'J' && data.at(5) == 'X' && data.at(6) == 'L' && data.at(7) == ' ')
return true;

return false;
}

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 common/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
Loading