Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
e6955e1f03 | |||
6074c4d6fd | |||
6f44c5c52a | |||
d030c75925 | |||
9b3133ac92 | |||
b0a0bb1294 | |||
3d5090593c | |||
d4966d169b | |||
bf52896347 | |||
c52ffa2227 | |||
e4e386babf | |||
b47a9d7022 | |||
2cbf815d1f | |||
6cd0056f3b | |||
83374f390e | |||
5e59d950bd | |||
de320447f6 | |||
cf375a207f | |||
2aec1d3926 | |||
2a84dd677d | |||
ebcc34519c | |||
cff2604cf9 | |||
f8a251e268 | |||
52134fc2e9 | |||
343954ca98 | |||
44fd6b7bc0 | |||
c8a0806aab | |||
bb475dedd1 | |||
9e28aae868 | |||
5c47a97b79 | |||
84d56d00cf | |||
384f78a13c | |||
72fc32aefc | |||
98f19c60ae |
@ -7,3 +7,4 @@ include:
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
|
@ -6,3 +6,4 @@ Dependencies:
|
||||
|
||||
Options:
|
||||
test-before-installing: True
|
||||
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
|
||||
|
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(KImageFormats)
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 5.93.0 NO_MODULE)
|
||||
find_package(ECM 5.97.0 NO_MODULE)
|
||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
@ -13,9 +13,9 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
|
||||
include(KDEInstallDirs)
|
||||
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
|
||||
include(KDECMakeSettings)
|
||||
|
||||
include(KDEGitCommitHooks)
|
||||
|
||||
|
||||
include(ECMDeprecationSettings)
|
||||
include(CheckIncludeFiles)
|
||||
include(FindPkgConfig)
|
||||
|
||||
@ -70,8 +70,11 @@ if(KIMAGEFORMATS_JXL)
|
||||
endif()
|
||||
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
||||
|
||||
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f02)
|
||||
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x055900)
|
||||
ecm_set_disabled_deprecation_versions(
|
||||
QT 5.15.2
|
||||
KF 5.95
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
if (BUILD_TESTING)
|
||||
add_subdirectory(autotests)
|
||||
|
@ -16,7 +16,7 @@ The following image formats have read-only support:
|
||||
- Animated Windows cursors (ani)
|
||||
- Gimp (xcf)
|
||||
- OpenEXR (exr)
|
||||
- Photoshop documents (psd)
|
||||
- Photoshop documents (psd, psb, pdd, psdt)
|
||||
- Sun Raster (ras)
|
||||
|
||||
The following image formats have read and write support:
|
||||
|
@ -86,8 +86,7 @@ if (TARGET avif)
|
||||
kimageformats_read_tests(
|
||||
avif
|
||||
)
|
||||
# because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference.
|
||||
kimageformats_write_tests(FUZZ 1
|
||||
kimageformats_write_tests(
|
||||
avif-nodatacheck-lossless
|
||||
)
|
||||
endif()
|
||||
|
BIN
autotests/read/psd/16bit_grayscale.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
autotests/read/psd/16bit_grayscale.psd
Normal file
BIN
autotests/read/psd/16bit_photoshop.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
autotests/read/psd/16bit_photoshop.psb
Normal file
BIN
autotests/read/psd/32bit-rgb.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
autotests/read/psd/32bit-rgb.psd
Normal file
BIN
autotests/read/psd/32bit_grayscale.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
autotests/read/psd/32bit_grayscale.psd
Normal file
BIN
autotests/read/psd/8bit-grayscale.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
autotests/read/psd/8bit-grayscale.psd
Normal file
BIN
autotests/read/psd/8bit-photoshop.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
autotests/read/psd/8bit-photoshop.psb
Normal file
BIN
autotests/read/psd/adobehq-2_5.png
Normal file
After Width: | Height: | Size: 298 KiB |
BIN
autotests/read/psd/adobehq-2_5.psd
Normal file
BIN
autotests/read/psd/birthday.pdd
Normal file
BIN
autotests/read/psd/birthday.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
autotests/read/psd/bitmap.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
autotests/read/psd/bitmap.psd
Normal file
BIN
autotests/read/psd/ccbug182496.png
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
autotests/read/psd/ccbug182496.psd
Normal file
BIN
autotests/read/psd/cmyka-16bits.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
autotests/read/psd/cmyka-16bits.psd
Normal file
BIN
autotests/read/psd/cmyka-8bits.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
autotests/read/psd/cmyka-8bits.psd
Normal file
BIN
autotests/read/psd/duotone.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
autotests/read/psd/duotone.psb
Normal file
BIN
autotests/read/psd/indexed.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
autotests/read/psd/indexed.psd
Normal file
BIN
autotests/read/psd/laba_16bit.png
Normal file
After Width: | Height: | Size: 189 KiB |
BIN
autotests/read/psd/laba_16bit.psd
Normal file
BIN
autotests/read/psd/laba_8bit.png
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
autotests/read/psd/laba_8bit.psd
Normal file
@ -4,6 +4,7 @@
|
||||
|
||||
function(kimageformats_add_plugin plugin)
|
||||
set(options)
|
||||
set(oneValueArgs)
|
||||
set(multiValueArgs SOURCES)
|
||||
cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
if(NOT KIF_ADD_PLUGIN_SOURCES)
|
||||
@ -86,6 +87,9 @@ endif()
|
||||
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
|
||||
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
||||
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
|
||||
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
|
||||
endif()
|
||||
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
|
@ -67,7 +67,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
|
||||
|
||||
bool QAVIFHandler::ensureParsed() const
|
||||
{
|
||||
if (m_parseState == ParseAvifSuccess) {
|
||||
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata) {
|
||||
return true;
|
||||
}
|
||||
if (m_parseState == ParseAvifError) {
|
||||
@ -79,6 +79,28 @@ bool QAVIFHandler::ensureParsed() const
|
||||
return that->ensureDecoder();
|
||||
}
|
||||
|
||||
bool QAVIFHandler::ensureOpened() const
|
||||
{
|
||||
if (m_parseState == ParseAvifSuccess) {
|
||||
return true;
|
||||
}
|
||||
if (m_parseState == ParseAvifError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QAVIFHandler *that = const_cast<QAVIFHandler *>(this);
|
||||
if (ensureParsed()) {
|
||||
if (m_parseState == ParseAvifMetadata) {
|
||||
bool success = that->jumpToNextImage();
|
||||
that->m_parseState = success ? ParseAvifSuccess : ParseAvifError;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
that->m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QAVIFHandler::ensureDecoder()
|
||||
{
|
||||
if (m_decoder) {
|
||||
@ -97,6 +119,9 @@ bool QAVIFHandler::ensureDecoder()
|
||||
|
||||
m_decoder = avifDecoderCreate();
|
||||
|
||||
m_decoder->ignoreExif = AVIF_TRUE;
|
||||
m_decoder->ignoreXMP = AVIF_TRUE;
|
||||
|
||||
#if AVIF_VERSION >= 80400
|
||||
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
||||
#endif
|
||||
@ -127,45 +152,58 @@ bool QAVIFHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
decodeResult = avifDecoderNextImage(m_decoder);
|
||||
m_container_width = m_decoder->image->width;
|
||||
m_container_height = m_decoder->image->height;
|
||||
|
||||
if (decodeResult == AVIF_RESULT_OK) {
|
||||
m_container_width = m_decoder->image->width;
|
||||
m_container_height = m_decoder->image->height;
|
||||
|
||||
if ((m_container_width > 65535) || (m_container_height > 65535)) {
|
||||
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((m_container_width == 0) || (m_container_height == 0)) {
|
||||
qWarning("Empty image, nothing to decode");
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_container_width > ((16384 * 16384) / m_container_height)) {
|
||||
qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_parseState = ParseAvifSuccess;
|
||||
if (decode_one_frame()) {
|
||||
return true;
|
||||
} else {
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
qWarning("ERROR: Failed to decode image: %s", avifResultToString(decodeResult));
|
||||
if ((m_container_width > 65535) || (m_container_height > 65535)) {
|
||||
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
avifDecoderDestroy(m_decoder);
|
||||
m_decoder = nullptr;
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
if ((m_container_width == 0) || (m_container_height == 0)) {
|
||||
qWarning("Empty image, nothing to decode");
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_container_width > ((16384 * 16384) / m_container_height)) {
|
||||
qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate final dimensions with crop and rotate operations applied
|
||||
int new_width = m_container_width;
|
||||
int new_height = m_container_height;
|
||||
|
||||
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
|
||||
if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
|
||||
&& (m_decoder->image->clap.vertOffD > 0)) {
|
||||
int crop_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
|
||||
if (crop_width < new_width && crop_width > 0) {
|
||||
new_width = crop_width;
|
||||
}
|
||||
int crop_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
|
||||
if (crop_height < new_height && crop_height > 0) {
|
||||
new_height = crop_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
|
||||
if (m_decoder->image->irot.angle == 1 || m_decoder->image->irot.angle == 3) {
|
||||
int tmp = new_width;
|
||||
new_width = new_height;
|
||||
new_height = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
m_estimated_dimensions.setWidth(new_width);
|
||||
m_estimated_dimensions.setHeight(new_height);
|
||||
|
||||
m_parseState = ParseAvifMetadata;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QAVIFHandler::decode_one_frame()
|
||||
@ -192,9 +230,9 @@ bool QAVIFHandler::decode_one_frame()
|
||||
}
|
||||
} else {
|
||||
if (loadalpha) {
|
||||
resultformat = QImage::Format_RGBA8888;
|
||||
resultformat = QImage::Format_ARGB32;
|
||||
} else {
|
||||
resultformat = QImage::Format_RGBX8888;
|
||||
resultformat = QImage::Format_RGB32;
|
||||
}
|
||||
}
|
||||
QImage result(m_decoder->image->width, m_decoder->image->height, resultformat);
|
||||
@ -285,14 +323,16 @@ bool QAVIFHandler::decode_one_frame()
|
||||
rgb.depth = 16;
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
|
||||
if (!loadalpha) {
|
||||
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
|
||||
resultformat = QImage::Format_Grayscale16;
|
||||
}
|
||||
if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
|
||||
resultformat = QImage::Format_Grayscale16;
|
||||
}
|
||||
} else {
|
||||
rgb.depth = 8;
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
rgb.format = AVIF_RGB_FORMAT_BGRA;
|
||||
#else
|
||||
rgb.format = AVIF_RGB_FORMAT_ARGB;
|
||||
#endif
|
||||
|
||||
#if AVIF_VERSION >= 80400
|
||||
if (m_decoder->imageCount > 1) {
|
||||
@ -301,14 +341,8 @@ bool QAVIFHandler::decode_one_frame()
|
||||
}
|
||||
#endif
|
||||
|
||||
if (loadalpha) {
|
||||
resultformat = QImage::Format_ARGB32;
|
||||
} else {
|
||||
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
|
||||
resultformat = QImage::Format_Grayscale8;
|
||||
} else {
|
||||
resultformat = QImage::Format_RGB32;
|
||||
}
|
||||
if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
|
||||
resultformat = QImage::Format_Grayscale8;
|
||||
}
|
||||
}
|
||||
|
||||
@ -399,13 +433,15 @@ bool QAVIFHandler::decode_one_frame()
|
||||
m_current_image = result.convertToFormat(resultformat);
|
||||
}
|
||||
|
||||
m_estimated_dimensions = m_current_image.size();
|
||||
|
||||
m_must_jump_to_next_image = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QAVIFHandler::read(QImage *image)
|
||||
{
|
||||
if (!ensureParsed()) {
|
||||
if (!ensureOpened()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -452,8 +488,13 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_quality >= 100 && !avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE)) {
|
||||
qWarning("You are using %s encoder. It is recommended to enable libAOM encoder in libavif for better near-lossless compression.", encoder_name);
|
||||
bool lossless = false;
|
||||
if (m_quality >= 100) {
|
||||
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE)) {
|
||||
lossless = true;
|
||||
} else {
|
||||
qWarning("You are using %s encoder. It is recommended to enable libAOM encoder in libavif to use lossless compression.", encoder_name);
|
||||
}
|
||||
}
|
||||
|
||||
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
|
||||
@ -648,43 +689,47 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
|
||||
// in case primaries or trc were not identified
|
||||
if ((primaries_to_save == 2) || (transfer_to_save == 2)) {
|
||||
// upgrade image to higher bit depth
|
||||
if (save_depth == 8) {
|
||||
save_depth = 10;
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
|
||||
} else {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
|
||||
if (lossless) {
|
||||
iccprofile = tmpcolorimage.colorSpace().iccProfile();
|
||||
} else {
|
||||
// upgrade image to higher bit depth
|
||||
if (save_depth == 8) {
|
||||
save_depth = 10;
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
|
||||
} else {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
|
||||
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
|
||||
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
|
||||
if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
|
||||
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
|
||||
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
|
||||
|
||||
switch (transfer_to_save) {
|
||||
case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
|
||||
break;
|
||||
case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
|
||||
break;
|
||||
case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
|
||||
break;
|
||||
default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
|
||||
switch (transfer_to_save) {
|
||||
case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
|
||||
break;
|
||||
case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
|
||||
break;
|
||||
case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
|
||||
break;
|
||||
default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
break;
|
||||
}
|
||||
} else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
break;
|
||||
tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
|
||||
} else { // unrecognized profile
|
||||
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
|
||||
}
|
||||
} else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
|
||||
} else { // unrecognized profile
|
||||
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
|
||||
}
|
||||
}
|
||||
} else { // profile is unsupported by Qt
|
||||
@ -694,6 +739,9 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
}
|
||||
}
|
||||
|
||||
if (lossless && pixel_format == AVIF_PIXEL_FORMAT_YUV444) {
|
||||
matrix_to_save = (avifMatrixCoefficients)0;
|
||||
}
|
||||
avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
|
||||
avif->matrixCoefficients = matrix_to_save;
|
||||
|
||||
@ -712,9 +760,7 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
if (save_depth > 8) { // 10bit depth
|
||||
rgb.depth = 16;
|
||||
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
avif->alphaRange = AVIF_RANGE_FULL;
|
||||
} else {
|
||||
if (!tmpcolorimage.hasAlphaChannel()) {
|
||||
rgb.ignoreAlpha = AVIF_TRUE;
|
||||
}
|
||||
|
||||
@ -724,7 +770,6 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
avif->alphaRange = AVIF_RANGE_FULL;
|
||||
} else {
|
||||
rgb.format = AVIF_RGB_FORMAT_RGB;
|
||||
}
|
||||
@ -783,7 +828,7 @@ QVariant QAVIFHandler::option(ImageOption option) const
|
||||
|
||||
switch (option) {
|
||||
case Size:
|
||||
return m_current_image.size();
|
||||
return m_estimated_dimensions;
|
||||
case Animation:
|
||||
if (imageCount() >= 2) {
|
||||
return true;
|
||||
@ -839,6 +884,14 @@ int QAVIFHandler::currentImageNumber() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_parseState == ParseAvifMetadata) {
|
||||
if (m_decoder->imageCount >= 2) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return m_decoder->imageIndex;
|
||||
}
|
||||
|
||||
@ -848,12 +901,14 @@ bool QAVIFHandler::jumpToNextImage()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_decoder->imageCount < 2) {
|
||||
return true;
|
||||
}
|
||||
if (m_decoder->imageIndex >= 0) {
|
||||
if (m_decoder->imageCount < 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||
avifDecoderReset(m_decoder);
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||
avifDecoderReset(m_decoder);
|
||||
}
|
||||
}
|
||||
|
||||
avifResult decodeResult = avifDecoderNextImage(m_decoder);
|
||||
@ -876,6 +931,7 @@ bool QAVIFHandler::jumpToNextImage()
|
||||
}
|
||||
|
||||
if (decode_one_frame()) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
return true;
|
||||
} else {
|
||||
m_parseState = ParseAvifError;
|
||||
@ -891,7 +947,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
|
||||
if (m_decoder->imageCount < 2) { // not an animation
|
||||
if (imageNumber == 0) {
|
||||
return true;
|
||||
return ensureOpened();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -926,6 +982,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
}
|
||||
|
||||
if (decode_one_frame()) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
return true;
|
||||
} else {
|
||||
m_parseState = ParseAvifError;
|
||||
@ -935,7 +992,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
|
||||
int QAVIFHandler::nextImageDelay() const
|
||||
{
|
||||
if (!ensureParsed()) {
|
||||
if (!ensureOpened()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <QImage>
|
||||
#include <QImageIOPlugin>
|
||||
#include <QPointF>
|
||||
#include <QSize>
|
||||
#include <QVariant>
|
||||
#include <avif/avif.h>
|
||||
#include <qimageiohandler.h>
|
||||
@ -45,6 +46,7 @@ public:
|
||||
private:
|
||||
static QPointF CompatibleChromacity(qreal chrX, qreal chrY);
|
||||
bool ensureParsed() const;
|
||||
bool ensureOpened() const;
|
||||
bool ensureDecoder();
|
||||
bool decode_one_frame();
|
||||
|
||||
@ -52,6 +54,7 @@ private:
|
||||
ParseAvifError = -1,
|
||||
ParseAvifNotParsed = 0,
|
||||
ParseAvifSuccess = 1,
|
||||
ParseAvifMetadata = 2,
|
||||
};
|
||||
|
||||
ParseAvifState m_parseState;
|
||||
@ -59,6 +62,7 @@ private:
|
||||
|
||||
uint32_t m_container_width;
|
||||
uint32_t m_container_height;
|
||||
QSize m_estimated_dimensions;
|
||||
|
||||
QByteArray m_rawData;
|
||||
avifROData m_rawAvifData;
|
||||
|
@ -143,6 +143,10 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlDecoderCloseInput(m_decoder);
|
||||
#endif
|
||||
|
||||
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||
@ -482,37 +486,15 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
|
||||
void *runner = nullptr;
|
||||
int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
|
||||
|
||||
if (num_worker_threads > 1) {
|
||||
runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
|
||||
if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
|
||||
qWarning("JxlEncoderSetParallelRunner failed");
|
||||
JxlThreadParallelRunnerDestroy(runner);
|
||||
JxlEncoderDestroy(encoder);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
|
||||
|
||||
if (m_quality > 100) {
|
||||
m_quality = 100;
|
||||
} else if (m_quality < 0) {
|
||||
m_quality = 90;
|
||||
}
|
||||
|
||||
JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||
|
||||
JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
|
||||
JxlBasicInfo output_info;
|
||||
JxlEncoderInitBasicInfo(&output_info);
|
||||
|
||||
JxlColorEncoding color_profile;
|
||||
JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
|
||||
|
||||
bool convert_color_profile;
|
||||
QByteArray iccprofile;
|
||||
|
||||
@ -526,7 +508,28 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
convert_color_profile = false;
|
||||
iccprofile = image.colorSpace().iccProfile();
|
||||
if (iccprofile.size() > 0 || m_quality == 100) {
|
||||
output_info.uses_original_profile = 1;
|
||||
output_info.uses_original_profile = JXL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (save_depth == 16 && (image.hasAlphaChannel() || output_info.uses_original_profile)) {
|
||||
output_info.have_container = JXL_TRUE;
|
||||
JxlEncoderUseContainer(encoder, JXL_TRUE);
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlEncoderSetCodestreamLevel(encoder, 10);
|
||||
#endif
|
||||
}
|
||||
|
||||
void *runner = nullptr;
|
||||
int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
|
||||
|
||||
if (num_worker_threads > 1) {
|
||||
runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
|
||||
if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
|
||||
qWarning("JxlEncoderSetParallelRunner failed");
|
||||
JxlThreadParallelRunnerDestroy(runner);
|
||||
JxlEncoderDestroy(encoder);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -537,7 +540,6 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
||||
pixel_format.align = 0;
|
||||
|
||||
output_info.intensity_target = 255.0f;
|
||||
output_info.orientation = JXL_ORIENT_IDENTITY;
|
||||
output_info.num_color_channels = 3;
|
||||
output_info.animation.tps_numerator = 10;
|
||||
@ -615,6 +617,9 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
JxlColorEncoding color_profile;
|
||||
JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
|
||||
|
||||
status = JxlEncoderSetColorEncoding(encoder, &color_profile);
|
||||
if (status != JXL_ENC_SUCCESS) {
|
||||
qWarning("JxlEncoderSetColorEncoding failed!");
|
||||
@ -626,6 +631,20 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder, nullptr);
|
||||
|
||||
JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||
|
||||
JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
#else
|
||||
JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
|
||||
|
||||
JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||
|
||||
JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
#endif
|
||||
|
||||
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size);
|
||||
} else {
|
||||
@ -915,6 +934,10 @@ bool QJpegXLHandler::rewind()
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlDecoderCloseInput(m_decoder);
|
||||
#endif
|
||||
|
||||
if (m_basicinfo.uses_original_profile) {
|
||||
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||
|
@ -1,7 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=psd
|
||||
X-KDE-ImageFormat=psd,psb,pdd,psdt
|
||||
X-KDE-MimeType=image/vnd.adobe.photoshop
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=false
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"Keys": [ "psd" ],
|
||||
"MimeTypes": [ "image/vnd.adobe.photoshop" ]
|
||||
"Keys": [ "psd", "psb", "pdd", "psdt" ],
|
||||
"MimeTypes": [ "image/vnd.adobe.photoshop", "image/vnd.adobe.photoshop", "image/vnd.adobe.photoshop", "image/vnd.adobe.photoshop" ]
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ public:
|
||||
bool canRead() const override;
|
||||
bool read(QImage *image) override;
|
||||
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
#include "ras_p.h"
|
||||
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QImage>
|
||||
@ -102,8 +104,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
{
|
||||
s.device()->seek(RasHeader::SIZE);
|
||||
|
||||
// QVector uses some extra space for stuff, hence the 32 here suggested by thiago
|
||||
if (ras.ColorMapLength > std::numeric_limits<int>::max() - 32) {
|
||||
if (ras.ColorMapLength > kMaxQVectorSize) {
|
||||
qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
|
||||
return false;
|
||||
}
|
||||
@ -127,8 +128,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
qWarning() << "LoadRAS() mistmatch between height and width" << ras.Width << ras.Height << ras.Length << ras.Depth;
|
||||
return false;
|
||||
}
|
||||
// QVector uses some extra space for stuff, hence the 32 here suggested by thiago
|
||||
if (ras.Length > std::numeric_limits<int>::max() - 32) {
|
||||
if (ras.Length > kMaxQVectorSize) {
|
||||
qWarning() << "LoadRAS() unsupported image length in file header" << ras.Length;
|
||||
return false;
|
||||
}
|
||||
|
10
src/imageformats/util_p.h
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2022 Albert Astals Cid <aacid@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
|
||||
// QVector uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira
|
||||
static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
|
@ -135,7 +135,7 @@ public:
|
||||
PROP_SAMPLE_POINTS = 39,
|
||||
MAX_SUPPORTED_PROPTYPE, // should always be at the end so its value is last + 1
|
||||
};
|
||||
Q_ENUM(PropType);
|
||||
Q_ENUM(PropType)
|
||||
|
||||
//! Compression type used in layer tiles.
|
||||
enum XcfCompressionType {
|
||||
@ -145,7 +145,7 @@ public:
|
||||
COMPRESS_ZLIB = 2, /* unused */
|
||||
COMPRESS_FRACTAL = 3, /* unused */
|
||||
};
|
||||
Q_ENUM(XcfCompressionType);
|
||||
Q_ENUM(XcfCompressionType)
|
||||
|
||||
enum LayerModeType {
|
||||
GIMP_LAYER_MODE_NORMAL_LEGACY,
|
||||
@ -212,7 +212,7 @@ public:
|
||||
GIMP_LAYER_MODE_PASS_THROUGH,
|
||||
GIMP_LAYER_MODE_COUNT,
|
||||
};
|
||||
Q_ENUM(LayerModeType);
|
||||
Q_ENUM(LayerModeType)
|
||||
|
||||
//! Type of individual layers in an XCF file.
|
||||
enum GimpImageType {
|
||||
@ -223,7 +223,7 @@ public:
|
||||
INDEXED_GIMAGE,
|
||||
INDEXEDA_GIMAGE,
|
||||
};
|
||||
Q_ENUM(GimpImageType);
|
||||
Q_ENUM(GimpImageType)
|
||||
|
||||
//! Type of individual layers in an XCF file.
|
||||
enum GimpColorSpace {
|
||||
@ -3270,6 +3270,52 @@ bool XCFHandler::write(const QImage &)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XCFHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant XCFHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
/*
|
||||
* The image structure always starts at offset 0 in the XCF file.
|
||||
* byte[9] "gimp xcf " File type identification
|
||||
* byte[4] version XCF version
|
||||
* "file": version 0
|
||||
* "v001": version 1
|
||||
* "v002": version 2
|
||||
* "v003": version 3
|
||||
* byte 0 Zero marks the end of the version tag.
|
||||
* uint32 width Width of canvas
|
||||
* uint32 height Height of canvas
|
||||
*/
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba9 = d->read(9); // "gimp xcf "
|
||||
auto ba5 = d->read(4+1); // version + null terminator
|
||||
auto ba = d->read(8); // width and height
|
||||
d->rollbackTransaction();
|
||||
if (ba9 == QByteArray("gimp xcf ") && ba5.size() == 5) {
|
||||
QDataStream ds(ba);
|
||||
quint32 width;
|
||||
ds >> width;
|
||||
quint32 height;
|
||||
ds >> height;
|
||||
if (ds.status() == QDataStream::Ok)
|
||||
v = QVariant::fromValue(QSize(width, height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
bool XCFHandler::canRead(QIODevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
|
@ -20,6 +20,9 @@ public:
|
||||
bool read(QImage *image) override;
|
||||
bool write(const QImage &image) override;
|
||||
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
};
|
||||
|
||||
|