Compare commits

...

52 Commits

Author SHA1 Message Date
Halla Rempt
b89c2d1b70 Deal with different offsets in kra and ora
Because of a change in zip library, the location of the mimetype
is different for different versions of krita.
2022-01-05 15:51:16 +01:00
l10n daemon script
56376ffd66 GIT_SILENT Upgrade ECM and KF version requirements for 5.90.0 release. 2022-01-01 12:16:00 +00:00
Laurent Montel
f534254063 GIT_SILENT: It compiles fine without deprecated methods 2021-12-26 11:35:54 +01:00
Laurent Montel
32347725cb Fix typo 2021-12-21 20:28:05 +01:00
Laurent Montel
56e762c563 Make it compile against qt6 2021-12-21 20:10:11 +01:00
l10n daemon script
81603ed908 GIT_SILENT Upgrade ECM and KF version requirements for 5.89.0 release. 2021-12-04 17:00:59 +00:00
Laurent Montel
957c199c68 GIT_SILENT: add missing licences 2021-12-02 07:14:54 +01:00
Daniel Novomesky
f445e5dd0a avif: limit scope of variables 2021-11-29 17:35:51 +01:00
Daniel Novomesky
d3da56bba3 Add JXL to the list of supported formats 2021-11-17 20:06:11 +01:00
Daniel Novomesky
41c4b5930c Add plugin for JPEG XL (JXL) 2021-11-10 16:01:58 +01:00
Nicolas Fella
fb66044714 Add FreeBSD CI 2021-10-07 00:28:35 +02:00
Nicolas Fella
a43394a759 Add Android CI 2021-10-07 00:04:08 +02:00
l10n daemon script
904c251f50 GIT_SILENT Upgrade ECM and KF version requirements for 5.87.0 release. 2021-10-02 12:18:49 +00:00
Ben Cooksley
2f9e09f04a Add Gitlab CI configuration 2021-09-29 21:55:22 +13:00
Daniel Novomesky
6458c9ae52 avif: performance and quality improvements
Enable decoder to use more threads.
8bit YUV->RGB conversion is significantly faster
when AVIF_RGB_FORMAT_RGBA format is used,
because libavif can use libyuv to perform conversion quickly.
Prefer faster AVIF_CHROMA_UPSAMPLING_FASTEST when decoding animation.
Encoder speed changed from to 8 to 7.
Recent AV1 encoders got faster,
so it is OK to switch to better compression quality.
Remove obsolete image/avif-sequence mime-type
as it was merged with image/avif.
2021-09-22 11:53:46 +02:00
David Faure
45cd128f73 GIT_SILENT Add CI configuration file 2021-09-05 14:03:42 +02:00
l10n daemon script
e89d21f12d GIT_SILENT Upgrade ECM and KF version requirements for 5.86.0 release. 2021-09-04 15:45:36 +00:00
Albert Astals Cid
1d2b51ddf1 Fix build with clang12 + libc++ 2021-08-30 20:29:23 +02:00
Laurent Montel
1080976abe GIT_SILENT: we can use std::as_const directly 2021-08-28 18:07:43 +02:00
Laurent Montel
abd550c60c GIT_SILENT: replace MacOSX with macOS 2021-08-25 14:56:21 +02:00
Alexander Lohnau
aade392da3 Clean up unneeded JSON parameter in kimageformats_add_plugin
The moc process already rebuilds the plugin when the JSON file changes, consequently the additional parameter is not needed.

Task: https://phabricator.kde.org/T14649
2021-08-24 12:06:06 +02:00
Albert Astals Cid
7642633551 SGIImage::writeImage: Properly fail if the image is too big 2021-08-19 17:29:44 +02:00
Albert Astals Cid
9f2c5061c8 exr: Port to std::log/pow
The Imath functions are marked as deprecated and they just call the
std::log/pow anyway
2021-08-18 21:51:53 +00:00
l10n daemon script
28099eed71 GIT_SILENT Upgrade Qt5 version requirement to 5.15.2. 2021-08-15 08:56:59 +00:00
Ahmad Samir
f5d574b3ad clang-tidy: one declaration per line; braces around statements
clang-tidy checks:
readability-isolate-declaration and readability-braces-around-statements

KF task: https://phabricator.kde.org/T14729

GIT_SILENT
2021-08-13 15:13:21 +02:00
Albert Astals Cid
a8f92e5525 PCXHandler::write: Properly fail if the image is too big 2021-08-12 16:43:52 +02:00
Albert Astals Cid
fbeef559b7 exr: Repair compability with openexr2
BUGS: 440084
2021-07-21 00:04:45 +02:00
l10n daemon script
7f56d835f0 GIT_SILENT Upgrade ECM and KF version requirements for 5.85.0 release. 2021-07-14 22:32:29 +00:00
Christophe Giboudeaux
8f87ce4cb2 Fix typos found by codespell
GIT_SILENT
2021-07-14 00:08:38 +02:00
Albert Astals Cid
5aa03c12ad exr: Override the actual function signature
For gcc there's a typedef that makes it work, but seems clang in macos is
not so lucky

BUGS: 439767
2021-07-12 20:15:12 +02:00
Christophe Giboudeaux
3266a9c466 Fix build with older openEXR versions
Dynamic exception specification is not allowed in c++-17. The enforcement needs
to be relaxed for the kimg_exr plugin when using openEXR versions older than 2.3.0.
2021-07-08 12:56:30 +02:00
Ahmad Samir
1b2bf6e931 Remove CMAKE_CXX_STANDARD, now set to 17 in KDEFrameworkCompilerSettings in ECM
NO_CHANGELOG
2021-06-19 20:05:41 +02:00
l10n daemon script
894391b000 GIT_SILENT Upgrade ECM and KF version requirements for 5.84.0 release. 2021-06-19 15:58:16 +00:00
Daniel Novomesky
ef6be2c077 avif: Disable all strict decoder checks
New libavif 0.9.1 apply some very strict standard compliance
checks by default. Unfortunately, it rejects many files made by
libheif-based apps (GIMP, ImageMagick) in the past.
libheif 1.12.0 (released on May 5, 2021) addressed the problem,
but it would be good to extend grace period, allowing other
projects and distributions to upgrade to fixed version.
2021-06-08 15:20:41 +02:00
l10n daemon script
96b40da089 GIT_SILENT Upgrade ECM and KF version requirements for 5.83.0 release. 2021-06-05 08:55:28 +00:00
Daniel Novomesky
bf3f94da76 avif: Adjust for libavif breaking change 2021-06-04 14:37:10 +02:00
Ahmad Samir
318dacda75 Remove compiler flags already defined in extra-cmake-modules
-DQT_NO_FOREACH

GIT_SILENT
2021-05-23 17:18:51 +02:00
Ahmad Samir
e358bb0feb Bump required CMake version to 3.16
KF6 task: https://phabricator.kde.org/T14467
2021-05-17 13:21:23 +02:00
Daniel Novomesky
dca6e87c89 Enable HEIC plugin to save all ICC profiles 2021-05-14 12:30:28 +02:00
Daniel Novomesky
62f9af9a35 Color profile loading/saving fixes
Allow saving Qt-unsupported variants of ICC profiles, they could be
correctly handled by apps with wider color management support.
Change way how QColorSpace is created to avoid
rare problems in some apps.
2021-05-13 16:56:46 +02:00
Albert Astals Cid
ff53d3d7e9 xcf: Make sure offsets are not negative
It's not a huge problem since QIODevice::seek() is a noop on negative values but it's
just better to bail out as soon as possible when we realize the file is
broken
2021-05-05 17:23:59 +02:00
Alexander Lohnau
780f342825 GIT_SILENT Add auto generated files to .gitignore 2021-05-02 12:48:16 +02:00
Albert Astals Cid
297ed9a2fe xcf: Fix Stack-buffer-overflow WRITE on broken files
oss-fuzz/33742
2021-05-02 09:50:50 +00:00
l10n daemon script
55b4077f2c GIT_SILENT Upgrade ECM and KF version requirements for 5.82.0 release. 2021-05-01 09:42:14 +00:00
Antonio Rojas
2429c95336 Support building with OpenEXR 3
Try to find OpenEXR 3 first via the upstream cmake config and fallback to using our FindOpenEXR
2021-04-24 10:17:25 +00:00
David Faure
224f892b09 GIT_SILENT increase KF_DISABLE_DEPRECATED_BEFORE_AND_AT 2021-04-11 12:05:38 +02:00
Alexander Lohnau
64fa129ed6 GIT_SILENT Add auto generated files to .gitignore 2021-04-07 21:06:01 +02:00
Albert Astals Cid
3cb4021afc test: imageconverter: add a way to list mimes instead of formats 2021-04-05 09:44:03 +00:00
David Faure
95a19a15c3 xcf: fix new[]/delete mismatch, as detected by ASAN 2021-04-04 17:30:52 +02:00
David Faure
1ba23a1e8e Port away from QPrinter::setPaperSize, deprecated in Qt 5.15
The formula was found in Qt's qt_pixelMultiplier (qpagesize.cpp)
as called by the deprecated setPaperSize method (when the unit is
DevicePixel)

NO_CHANGELOG
2021-04-04 15:56:13 +02:00
David Faure
bc3c04c7ce GIT_SILENT Upgrade Qt5 version requirement to 5.15.0. 2021-04-04 14:44:40 +02:00
Albert Astals Cid
2755f74fbb ani: convert +1 to -1 so we don't do a potential integer overflow
oss-fuzz/32601

runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
2021-04-03 22:46:33 +00:00
36 changed files with 1533 additions and 180 deletions

View File

@@ -1,2 +1,3 @@
#clang-format
#clang-format/tidy
1169859b07f25c865ee0bfc2a7dc97a431651776
eaca33bec8b19498c3621fc3a20a8c55a2812462

4
.gitignore vendored
View File

@@ -22,3 +22,7 @@ CMakeLists.txt.user*
.cmake/
/.clang-format
/compile_commands.json
.clangd
.idea
/cmake-build*
.cache

7
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: CC0-1.0
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml

8
.kde-ci.yml Normal file
View File

@@ -0,0 +1,8 @@
Dependencies:
- 'on': ['Linux', 'FreeBSD', 'macOS', 'Windows']
'require':
'frameworks/extra-cmake-modules': '@same'
'frameworks/karchive' : '@same'
Options:
test-before-installing: True

View File

@@ -1,11 +1,9 @@
cmake_minimum_required(VERSION 3.6)
cmake_minimum_required(VERSION 3.16)
project(KImageFormats)
set (CMAKE_CXX_STANDARD 14)
include(FeatureSummary)
find_package(ECM 5.81.0 NO_MODULE)
find_package(ECM 5.90.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)
@@ -19,9 +17,10 @@ include(KDEGitCommitHooks)
include(CheckIncludeFiles)
include(FindPkgConfig)
set(REQUIRED_QT_VERSION 5.14.0)
find_package(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
set(REQUIRED_QT_VERSION 5.15.2)
find_package(Qt${QT_MAJOR_VERSION}Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF5Archive)
set_package_properties(KF5Archive PROPERTIES
@@ -33,17 +32,20 @@ set_package_properties(KF5Archive PROPERTIES
# this available in PATH
set(BUILD_EPS_PLUGIN FALSE)
if (UNIX)
find_package(Qt5PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
set_package_properties(Qt5PrintSupport PROPERTIES
find_package(Qt${QT_MAJOR_VERSION}PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
set_package_properties(Qt${QT_MAJOR_VERSION}PrintSupport PROPERTIES
PURPOSE "Required for the QImage plugin for EPS images"
TYPE OPTIONAL
)
if (Qt5PrintSupport_FOUND)
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
set(BUILD_EPS_PLUGIN TRUE)
endif()
endif()
find_package(OpenEXR)
find_package(OpenEXR 3.0 CONFIG QUIET)
if(NOT OpenEXR_FOUND)
find_package(OpenEXR)
endif()
set_package_properties(OpenEXR PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for OpenEXR images"
@@ -57,16 +59,19 @@ set_package_properties(libavif PROPERTIES
option(KIMAGEFORMATS_HEIF "Enable plugin for HEIF format" OFF)
if(KIMAGEFORMATS_HEIF)
include(FindPkgConfig)
pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.10.0)
endif()
add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/HEIC images")
add_definitions(-DQT_NO_FOREACH)
# 050d00 (5.13) triggers a BIC in qimageiohandler.h, in Qt 5.13, so do not enable that until we can require 5.14
# https://codereview.qt-project.org/c/qt/qtbase/+/279215
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050e00)
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x055000)
option(KIMAGEFORMATS_JXL "Enable plugin for JPEG XL format" ON)
if(KIMAGEFORMATS_JXL)
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.6.1)
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.6.1)
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)
add_subdirectory(src)
if (BUILD_TESTING)
add_subdirectory(autotests)

121
LICENSES/CC0-1.0.txt Normal file
View File

@@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@@ -23,6 +23,7 @@ The following image formats have read and write support:
- AV1 Image File Format (AVIF)
- Encapsulated PostScript (eps)
- JPEG XL (jxl)
- Personal Computer Exchange (pcx)
- SGI images (rgb, rgba, sgi, bw)
- Softimage PIC (pic)

View File

@@ -15,7 +15,7 @@ macro(kimageformats_read_tests)
if (NOT TARGET readtest)
add_executable(readtest readtest.cpp)
target_link_libraries(readtest Qt5::Gui)
target_link_libraries(readtest Qt${QT_MAJOR_VERSION}::Gui)
target_compile_definitions(readtest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
ecm_mark_as_test(readtest)
@@ -32,7 +32,7 @@ endmacro()
macro(kimageformats_write_tests)
if (NOT TARGET writetest)
add_executable(writetest writetest.cpp)
target_link_libraries(writetest Qt5::Gui)
target_link_libraries(writetest Qt${QT_MAJOR_VERSION}::Gui)
target_compile_definitions(writetest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write")
ecm_mark_as_test(writetest)
@@ -82,6 +82,12 @@ if (LibHeif_FOUND)
)
endif()
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
kimageformats_read_tests(
jxl
)
endif()
# Allow some fuzziness when reading this formats, to allow for
# rounding errors (eg: in alpha blending).
kimageformats_read_tests(FUZZ 1
@@ -111,19 +117,19 @@ if (OpenEXR_FOUND)
# FIXME: OpenEXR tests
endif()
find_package(Qt5Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT Qt5Test_FOUND)
message(STATUS "Qt5Test not found, some autotests will not be built.")
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
message(STATUS "Qt${QT_MAJOR_VERSION}Test not found, some autotests will not be built.")
return()
endif()
add_executable(pictest pictest.cpp)
target_link_libraries(pictest Qt5::Gui Qt5::Test)
target_link_libraries(pictest Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Test)
ecm_mark_as_test(pictest)
add_test(NAME kimageformats-pic COMMAND pictest)
add_executable(anitest anitest.cpp)
target_link_libraries(anitest Qt5::Gui Qt5::Test)
target_link_libraries(anitest Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Test)
ecm_mark_as_test(anitest)
add_test(NAME kimageformats-ani COMMAND anitest)

BIN
autotests/read/jxl/rgb.jxl Normal file

Binary file not shown.

BIN
autotests/read/jxl/rgb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

BIN
autotests/read/jxl/rgba.jxl Normal file

Binary file not shown.

BIN
autotests/read/jxl/rgba.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -44,11 +44,13 @@ static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
const Trait *line2 = reinterpret_cast<const Trait *>(im2.scanLine(i));
for (int j = 0; j < width; ++j) {
if (line1[j] > line2[j]) {
if (line1[j] - line2[j] > fuzziness)
if (line1[j] - line2[j] > fuzziness) {
return false;
}
} else {
if (line2[j] - line1[j] > fuzziness)
if (line2[j] - line1[j] > fuzziness) {
return false;
}
}
}
}

View File

@@ -5,7 +5,7 @@ type: functional
platforms:
- name: Linux
- name: FreeBSD
- name: MacOSX
- name: macOS
- name: Windows
note: No EPS support on Windows
- name: Android

View File

@@ -4,125 +4,136 @@
function(kimageformats_add_plugin plugin)
set(options)
set(oneValueArgs JSON)
set(multiValueArgs SOURCES)
cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT KIF_ADD_PLUGIN_SOURCES)
message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter")
endif()
get_filename_component(json "${KIF_ADD_PLUGIN_JSON}" REALPATH)
if(NOT KIF_ADD_PLUGIN_JSON OR NOT EXISTS ${json})
message(FATAL_ERROR "JSON file doesn't exist: ${json}")
endif()
add_library(${plugin} MODULE ${KIF_ADD_PLUGIN_SOURCES})
set_property(TARGET ${plugin} APPEND PROPERTY AUTOGEN_TARGET_DEPENDS ${json})
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats")
target_link_libraries(${plugin} Qt5::Gui)
target_link_libraries(${plugin} Qt${QT_MAJOR_VERSION}::Gui)
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
endfunction()
##################################
kimageformats_add_plugin(kimg_ani JSON "ani.json" SOURCES ani.cpp)
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
if (TARGET avif)
kimageformats_add_plugin(kimg_avif JSON "avif.json" SOURCES "avif.cpp")
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
target_link_libraries(kimg_avif "avif")
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
if (BUILD_EPS_PLUGIN)
if (Qt5PrintSupport_FOUND)
kimageformats_add_plugin(kimg_eps JSON "eps.json" SOURCES eps.cpp)
target_link_libraries(kimg_eps Qt5::PrintSupport)
install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
target_link_libraries(kimg_eps Qt${QT_MAJOR_VERSION}::PrintSupport)
install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
# need this for Qt's version of the plugin
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
if(OpenEXR_FOUND)
kimageformats_add_plugin(kimg_exr JSON "exr.json" SOURCES exr.cpp)
target_link_libraries(kimg_exr OpenEXR::IlmImf)
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp)
if(TARGET OpenEXR::OpenEXR)
target_link_libraries(kimg_exr OpenEXR::OpenEXR)
else()
if(OpenEXR_VERSION_STRING VERSION_LESS 2.3.0)
# Older OpenEXR versions use dynamic exception specifications, so
# cannot use C++17 with them
set_target_properties(kimg_exr PROPERTIES CXX_STANDARD 14)
endif()
target_link_libraries(kimg_exr OpenEXR::IlmImf)
endif()
kde_target_enable_exceptions(kimg_exr PRIVATE)
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_hdr JSON "hdr.json" SOURCES hdr.cpp)
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
if (LibHeif_FOUND)
kimageformats_add_plugin(kimg_heif JSON "heif.json" SOURCES heif.cpp)
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
target_link_libraries(kimg_heif PkgConfig::LibHeif)
kde_target_enable_exceptions(kimg_heif PRIVATE)
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_pcx JSON "pcx.json" SOURCES pcx.cpp)
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_pic JSON "pic.json" SOURCES pic.cpp)
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
kimageformats_add_plugin(kimg_psd JSON "psd.json" SOURCES psd.cpp)
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
kimageformats_add_plugin(kimg_ras JSON "ras.json" SOURCES ras.cpp)
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
kimageformats_add_plugin(kimg_rgb JSON "rgb.json" SOURCES rgb.cpp)
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
kimageformats_add_plugin(kimg_tga JSON "tga.json" SOURCES tga.cpp)
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
kimageformats_add_plugin(kimg_xcf JSON "xcf.json" SOURCES xcf.cpp)
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp)
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
kimageformats_add_plugin(kimg_xcf SOURCES xcf.cpp)
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
##################################
if (KF5Archive_FOUND)
kimageformats_add_plugin(kimg_kra JSON "kra.json" SOURCES kra.cpp)
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
target_link_libraries(kimg_kra KF5::Archive)
install(FILES kra.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
install(FILES kra.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
kimageformats_add_plugin(kimg_ora JSON "ora.json" SOURCES ora.cpp)
kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
target_link_libraries(kimg_ora KF5::Archive)
install(FILES ora.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
install(FILES ora.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()

View File

@@ -504,7 +504,7 @@ bool ANIHandler::ensureScanned() const
return false;
}
if (!m_frameOffsets.isEmpty() && m_frameOffsets.count() != m_frameCount + 1) {
if (!m_frameOffsets.isEmpty() && m_frameOffsets.count() - 1 != m_frameCount) {
qWarning("ANIHandler: number of actual frames does not match 'nFrames' in anih");
return false;
}

View File

@@ -97,6 +97,14 @@ bool QAVIFHandler::ensureDecoder()
m_decoder = avifDecoderCreate();
#if AVIF_VERSION >= 80400
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
#endif
#if AVIF_VERSION >= 90100
m_decoder->strictFlags = AVIF_STRICT_DISABLED;
#endif
avifResult decodeResult;
decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
@@ -180,7 +188,7 @@ bool QAVIFHandler::decode_one_frame()
if (loadalpha) {
resultformat = QImage::Format_RGBA8888;
} else {
resultformat = QImage::Format_RGB888;
resultformat = QImage::Format_RGBX8888;
}
}
QImage result(m_decoder->image->width, m_decoder->image->height, resultformat);
@@ -190,10 +198,12 @@ bool QAVIFHandler::decode_one_frame()
return false;
}
QColorSpace colorspace;
if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) {
result.setColorSpace(QColorSpace::fromIccProfile(QByteArray::fromRawData((const char *)m_decoder->image->icc.data, (int)m_decoder->image->icc.size)));
if (!result.colorSpace().isValid()) {
qWarning("Invalid QColorSpace created from ICC!");
const QByteArray icc_data((const char *)m_decoder->image->icc.data, (int)m_decoder->image->icc.size);
colorspace = QColorSpace::fromIccProfile(icc_data);
if (!colorspace.isValid()) {
qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
}
} else {
float prim[8] = {0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f};
@@ -243,23 +253,25 @@ bool QAVIFHandler::decode_one_frame()
case 0:
case 1:
case 2: /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */
result.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma));
colorspace = QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma);
break;
/* AVIF_COLOR_PRIMARIES_SMPTE432 */
case 12:
result.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma));
colorspace = QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma);
break;
default:
result.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma));
colorspace = QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma);
break;
}
}
if (!result.colorSpace().isValid()) {
qWarning("Invalid QColorSpace created from NCLX/CICP!");
if (!colorspace.isValid()) {
qWarning("AVIF plugin created invalid QColorSpace from NCLX/CICP!");
}
}
result.setColorSpace(colorspace);
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, m_decoder->image);
@@ -268,20 +280,24 @@ bool QAVIFHandler::decode_one_frame()
rgb.format = AVIF_RGB_FORMAT_RGBA;
if (!loadalpha) {
rgb.ignoreAlpha = AVIF_TRUE;
result.fill(Qt::black);
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
resultformat = QImage::Format_Grayscale16;
}
}
} else {
rgb.depth = 8;
rgb.format = AVIF_RGB_FORMAT_RGBA;
#if AVIF_VERSION >= 80400
if (m_decoder->imageCount > 1) {
/* accelerate animated AVIF */
rgb.chromaUpsampling = AVIF_CHROMA_UPSAMPLING_FASTEST;
}
#endif
if (loadalpha) {
rgb.format = AVIF_RGB_FORMAT_RGBA;
resultformat = QImage::Format_ARGB32;
} else {
rgb.format = AVIF_RGB_FORMAT_RGB;
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
resultformat = QImage::Format_Grayscale8;
} else {
@@ -302,27 +318,27 @@ bool QAVIFHandler::decode_one_frame()
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 new_width, new_height, offx, offy;
new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
int new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
if (new_width > result.width()) {
new_width = result.width();
}
new_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
int new_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
if (new_height > result.height()) {
new_height = result.height();
}
if (new_width > 0 && new_height > 0) {
offx = ((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5;
int offx =
((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5;
if (offx < 0) {
offx = 0;
} else if (offx > (result.width() - new_width)) {
offx = result.width() - new_width;
}
offy = ((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5;
int offy =
((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5;
if (offy < 0) {
offy = 0;
} else if (offy > (result.height() - new_height)) {
@@ -357,11 +373,15 @@ bool QAVIFHandler::decode_one_frame()
}
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) {
#if AVIF_VERSION > 90100
switch (m_decoder->image->imir.mode) {
#else
switch (m_decoder->image->imir.axis) {
case 0: // vertical
#endif
case 0: // top-to-bottom
result = result.mirrored(false, true);
break;
case 1: // horizontal
case 1: // left-to-right
result = result.mirrored(true, false);
break;
}
@@ -545,6 +565,7 @@ bool QAVIFHandler::write(const QImage &image)
avifColorPrimaries primaries_to_save = (avifColorPrimaries)2;
avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2;
QByteArray iccprofile;
if (tmpcolorimage.colorSpace().isValid()) {
switch (tmpcolorimage.colorSpace().primaries()) {
@@ -636,13 +657,23 @@ bool QAVIFHandler::write(const QImage &image)
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
}
}
} else { // profile is unsupported by Qt
iccprofile = tmpcolorimage.colorSpace().iccProfile();
if (iccprofile.size() > 0) {
matrix_to_save = (avifMatrixCoefficients)6;
}
}
avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
avif->matrixCoefficients = matrix_to_save;
avif->colorPrimaries = primaries_to_save;
avif->transferCharacteristics = transfer_to_save;
if (iccprofile.size() > 0) {
avifImageSetProfileICC(avif, (const uint8_t *)iccprofile.constData(), iccprofile.size());
}
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, avif);
rgb.rowBytes = tmpcolorimage.bytesPerLine();
@@ -687,7 +718,7 @@ bool QAVIFHandler::write(const QImage &image)
encoder->maxQuantizerAlpha = maxQuantizerAlpha;
}
encoder->speed = 8;
encoder->speed = 7;
res = avifEncoderWrite(encoder, avif, &raw);
avifEncoderDestroy(encoder);
@@ -791,7 +822,7 @@ bool QAVIFHandler::jumpToNextImage()
return true;
}
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from begining
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
avifDecoderReset(m_decoder);
}

View File

@@ -1,4 +1,4 @@
{
"Keys": [ "avif", "avifs" ],
"MimeTypes": [ "image/avif", "image/avif-sequence" ]
"MimeTypes": [ "image/avif", "image/avif" ]
}

View File

@@ -94,7 +94,10 @@ static bool bbox(QIODevice *io, int *x1, int *y1, int *x2, int *y2)
if (strncmp(buf, BBOX, BBOX_LEN) == 0) {
// Some EPS files have non-integer values for the bbox
// We don't support that currently, but at least we parse it
float _x1, _y1, _x2, _y2;
float _x1;
float _y1;
float _x2;
float _y2;
if (sscanf(buf, "%*s %f %f %f %f", &_x1, &_y1, &_x2, &_y2) == 4) {
qCDebug(EPSPLUGIN) << "BBOX: " << _x1 << " " << _y1 << " " << _x2 << " " << _y2;
*x1 = int(_x1);
@@ -127,14 +130,18 @@ bool EPSHandler::read(QImage *image)
{
qCDebug(EPSPLUGIN) << "starting...";
int x1, y1, x2, y2;
int x1;
int y1;
int x2;
int y2;
#ifdef EPS_PERFORMANCE_DEBUG
QTime dt;
dt.start();
#endif
QIODevice *io = device();
qint64 ps_offset, ps_size;
qint64 ps_offset;
qint64 ps_size;
// find start of PostScript code
if (!seekToCodeStart(io, ps_offset, ps_size)) {
@@ -251,7 +258,8 @@ bool EPSHandler::write(const QImage &image)
psOut.setOutputFileName(tmpFile.fileName());
psOut.setOutputFormat(QPrinter::PdfFormat);
psOut.setFullPage(true);
psOut.setPaperSize(image.size(), QPrinter::DevicePixel);
const double multiplier = psOut.resolution() <= 0 ? 1.0 : 72.0 / psOut.resolution();
psOut.setPageSize(QPageSize(image.size() * multiplier, QPageSize::Point));
// painting the pixmap to the "printer" which is a file
p.begin(&psOut);

View File

@@ -18,6 +18,7 @@
#include <ImfConvert.h>
#include <ImfFloatAttribute.h>
#include <ImfInputFile.h>
#include <ImfInt64.h>
#include <ImfIntAttribute.h>
#include <ImfLineOrderAttribute.h>
#include <ImfRgbaFile.h>
@@ -43,8 +44,13 @@ public:
}
bool read(char c[], int n) override;
#if OPENEXR_VERSION_MAJOR > 2
uint64_t tellg() override;
void seekg(uint64_t pos) override;
#else
Imf::Int64 tellg() override;
void seekg(Imf::Int64 pos) override;
#endif
void clear() override;
private:
@@ -64,12 +70,20 @@ bool K_IStream::read(char c[], int n)
return false;
}
#if OPENEXR_VERSION_MAJOR > 2
uint64_t K_IStream::tellg()
#else
Imf::Int64 K_IStream::tellg()
#endif
{
return m_dev->pos();
}
#if OPENEXR_VERSION_MAJOR > 2
void K_IStream::seekg(uint64_t pos)
#else
void K_IStream::seekg(Imf::Int64 pos)
#endif
{
m_dev->seek(pos);
}
@@ -85,7 +99,10 @@ void K_IStream::clear()
*/
QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
{
float r, g, b, a;
float r;
float g;
float b;
float a;
// 1) Compensate for fogging by subtracting defog
// from the raw pixel values.
@@ -118,24 +135,24 @@ QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
// maximum intensity).
// Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32)
if (r > 1.0) {
r = 1.0 + Imath::Math<float>::log((r - 1.0) * 0.184874 + 1) / 0.184874;
r = 1.0 + std::log((r - 1.0) * 0.184874 + 1) / 0.184874;
}
if (g > 1.0) {
g = 1.0 + Imath::Math<float>::log((g - 1.0) * 0.184874 + 1) / 0.184874;
g = 1.0 + std::log((g - 1.0) * 0.184874 + 1) / 0.184874;
}
if (b > 1.0) {
b = 1.0 + Imath::Math<float>::log((b - 1.0) * 0.184874 + 1) / 0.184874;
b = 1.0 + std::log((b - 1.0) * 0.184874 + 1) / 0.184874;
}
if (a > 1.0) {
a = 1.0 + Imath::Math<float>::log((a - 1.0) * 0.184874 + 1) / 0.184874;
a = 1.0 + std::log((a - 1.0) * 0.184874 + 1) / 0.184874;
}
//
// 5) Gamma-correct the pixel values, assuming that the
// screen's gamma is 0.4545 (or 1/2.2).
r = Imath::Math<float>::pow(r, 0.4545);
g = Imath::Math<float>::pow(g, 0.4545);
b = Imath::Math<float>::pow(b, 0.4545);
a = Imath::Math<float>::pow(a, 0.4545);
r = std::pow(r, 0.4545);
g = std::pow(g, 0.4545);
b = std::pow(b, 0.4545);
a = std::pow(a, 0.4545);
// 6) Scale the values such that pixels middle gray
// pixels are mapped to 84.66 (or 3.5 f-stops below
@@ -164,7 +181,8 @@ bool EXRHandler::canRead() const
bool EXRHandler::read(QImage *outImage)
{
try {
int width, height;
int width;
int height;
K_IStream istr(device(), QByteArray());
Imf::RgbaInputFile file(istr);

View File

@@ -89,7 +89,8 @@ static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
// Load the HDR image.
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
{
uchar val, code;
uchar val;
uchar code;
// Create dst image.
img = QImage(width, height, QImage::Format_RGB32);

View File

@@ -137,12 +137,10 @@ bool HEIFHandler::write(const QImage &image)
heif::Image heifImage;
heifImage.create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma);
if (tmpimage.colorSpace().isValid()) {
QByteArray iccprofile = tmpimage.colorSpace().iccProfile();
if (iccprofile.size() > 0) {
std::vector<uint8_t> rawProfile(iccprofile.begin(), iccprofile.end());
heifImage.set_raw_color_profile(heif_color_profile_type_prof, rawProfile);
}
QByteArray iccprofile = tmpimage.colorSpace().iccProfile();
if (iccprofile.size() > 0) {
std::vector<uint8_t> rawProfile(iccprofile.begin(), iccprofile.end());
heifImage.set_raw_color_profile(heif_color_profile_type_prof, rawProfile);
}
heifImage.add_plane(heif_channel_interleaved, image.width(), image.height(), save_depth);
@@ -617,7 +615,7 @@ bool HEIFHandler::ensureDecoder()
} else {
m_current_image.setColorSpace(QColorSpace::fromIccProfile(ba));
if (!m_current_image.colorSpace().isValid()) {
qWarning() << "icc profile is invalid";
qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!";
}
}
} else {
@@ -679,7 +677,7 @@ bool HEIFHandler::ensureDecoder()
heif_nclx_color_profile_free(nclx);
if (!m_current_image.colorSpace().isValid()) {
qWarning() << "invalid color profile created from NCLX";
qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!";
}
}

876
src/imageformats/jxl.cpp Normal file
View File

@@ -0,0 +1,876 @@
/*
JPEG XL (JXL) support for QImage.
SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
SPDX-License-Identifier: BSD-2-Clause
*/
#include <QThread>
#include <QtGlobal>
#include "jxl_p.h"
#include <jxl/encode.h>
#include <jxl/thread_parallel_runner.h>
QJpegXLHandler::QJpegXLHandler()
: m_parseState(ParseJpegXLNotParsed)
, m_quality(90)
, m_currentimage_index(0)
, m_previousimage_index(-1)
, m_decoder(nullptr)
, m_runner(nullptr)
, m_next_image_delay(0)
, m_input_image_format(QImage::Format_Invalid)
, m_target_image_format(QImage::Format_Invalid)
, m_buffer_size(0)
{
}
QJpegXLHandler::~QJpegXLHandler()
{
if (m_runner) {
JxlThreadParallelRunnerDestroy(m_runner);
}
if (m_decoder) {
JxlDecoderDestroy(m_decoder);
}
}
bool QJpegXLHandler::canRead() const
{
if (m_parseState == ParseJpegXLNotParsed && !canRead(device())) {
return false;
}
if (m_parseState != ParseJpegXLError) {
setFormat("jxl");
return true;
}
return false;
}
bool QJpegXLHandler::canRead(QIODevice *device)
{
if (!device) {
return false;
}
QByteArray header = device->peek(32);
if (header.size() < 12) {
return false;
}
JxlSignature signature = JxlSignatureCheck((const uint8_t *)header.constData(), header.size());
if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
return true;
}
return false;
}
bool QJpegXLHandler::ensureParsed() const
{
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed) {
return true;
}
if (m_parseState == ParseJpegXLError) {
return false;
}
QJpegXLHandler *that = const_cast<QJpegXLHandler *>(this);
return that->ensureDecoder();
}
bool QJpegXLHandler::ensureALLCounted() const
{
if (!ensureParsed()) {
return false;
}
if (m_parseState == ParseJpegXLSuccess) {
return true;
}
QJpegXLHandler *that = const_cast<QJpegXLHandler *>(this);
return that->countALLFrames();
}
bool QJpegXLHandler::ensureDecoder()
{
if (m_decoder) {
return true;
}
m_rawData = device()->readAll();
if (m_rawData.isEmpty()) {
return false;
}
JxlSignature signature = JxlSignatureCheck((const uint8_t *)m_rawData.constData(), m_rawData.size());
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
m_parseState = ParseJpegXLError;
return false;
}
m_decoder = JxlDecoderCreate(nullptr);
if (!m_decoder) {
qWarning("ERROR: JxlDecoderCreate failed");
m_parseState = ParseJpegXLError;
return false;
}
int num_worker_threads = QThread::idealThreadCount();
if (!m_runner && num_worker_threads >= 4) {
/* use half of the threads because plug-in is usually used in environment
* where application performs another tasks in backround (pre-load other images) */
num_worker_threads = num_worker_threads / 2;
num_worker_threads = qBound(2, num_worker_threads, 64);
m_runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetParallelRunner failed");
m_parseState = ParseJpegXLError;
return false;
}
}
if (JxlDecoderSetInput(m_decoder, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetInput failed");
m_parseState = ParseJpegXLError;
return false;
}
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");
m_parseState = ParseJpegXLError;
return false;
}
status = JxlDecoderProcessInput(m_decoder);
if (status == JXL_DEC_ERROR) {
qWarning("ERROR: JXL decoding failed");
m_parseState = ParseJpegXLError;
return false;
}
if (status == JXL_DEC_NEED_MORE_INPUT) {
qWarning("ERROR: JXL data incomplete");
m_parseState = ParseJpegXLError;
return false;
}
status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
if (status != JXL_DEC_SUCCESS) {
qWarning("ERROR: JXL basic info not available");
m_parseState = ParseJpegXLError;
return false;
}
if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
qWarning("ERROR: JXL image has zero dimensions");
m_parseState = ParseJpegXLError;
return false;
}
if (m_basicinfo.xsize > 32768 || m_basicinfo.ysize > 32768) {
qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError;
return false;
} else if (sizeof(void *) <= 4) {
/* On 32bit systems, there is limited address space.
* We skip imagess bigger than 8192 x 8192 pixels.
* If we don't do it, abort() in libjxl may close whole application */
if ((m_basicinfo.xsize * m_basicinfo.ysize) > 67108864) {
qWarning("JXL image (%dx%d) is too large for 32bit build of the plug-in", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError;
return false;
}
}
m_parseState = ParseJpegXLBasicInfoParsed;
return true;
}
bool QJpegXLHandler::countALLFrames()
{
if (m_parseState != ParseJpegXLBasicInfoParsed) {
return false;
}
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
if (status != JXL_DEC_COLOR_ENCODING) {
qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
m_parseState = ParseJpegXLError;
return false;
}
JxlColorEncoding color_encoding;
if (m_basicinfo.uses_original_profile == JXL_FALSE) {
JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
}
bool loadalpha;
if (m_basicinfo.alpha_bits > 0) {
loadalpha = true;
} else {
loadalpha = false;
}
m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
m_input_pixel_format.align = 0;
m_input_pixel_format.num_channels = 4;
if (m_basicinfo.bits_per_sample > 8) { // high bit depth
m_input_pixel_format.data_type = JXL_TYPE_UINT16;
m_buffer_size = 8 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize;
m_input_image_format = QImage::Format_RGBA64;
if (loadalpha) {
m_target_image_format = QImage::Format_RGBA64;
} else {
m_target_image_format = QImage::Format_RGBX64;
}
} else { // 8bit depth
m_input_pixel_format.data_type = JXL_TYPE_UINT8;
m_buffer_size = 4 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize;
m_input_image_format = QImage::Format_RGBA8888;
if (loadalpha) {
m_target_image_format = QImage::Format_ARGB32;
} else {
m_target_image_format = QImage::Format_RGB32;
}
}
status = JxlDecoderGetColorAsEncodedProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &color_encoding);
if (status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
&& color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
m_colorspace = QColorSpace(QColorSpace::SRgb);
} else {
size_t icc_size = 0;
if (JxlDecoderGetICCProfileSize(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
if (icc_size > 0) {
QByteArray icc_data((int)icc_size, 0);
if (JxlDecoderGetColorAsICCProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, (uint8_t *)icc_data.data(), icc_data.size())
== JXL_DEC_SUCCESS) {
m_colorspace = QColorSpace::fromIccProfile(icc_data);
if (!m_colorspace.isValid()) {
qWarning("JXL image has Qt-unsupported or invalid ICC profile!");
}
} else {
qWarning("Failed to obtain data from JPEG XL decoder");
}
} else {
qWarning("Empty ICC data");
}
} else {
qWarning("no ICC, other color profile");
}
}
if (m_basicinfo.have_animation) { // count all frames
JxlFrameHeader frame_header;
int delay;
for (status = JxlDecoderProcessInput(m_decoder); status != JXL_DEC_SUCCESS; status = JxlDecoderProcessInput(m_decoder)) {
if (status != JXL_DEC_FRAME) {
switch (status) {
case JXL_DEC_ERROR:
qWarning("ERROR: JXL decoding failed");
break;
case JXL_DEC_NEED_MORE_INPUT:
qWarning("ERROR: JXL data incomplete");
break;
default:
qWarning("Unexpected event %d instead of JXL_DEC_FRAME", status);
break;
}
m_parseState = ParseJpegXLError;
return false;
}
if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderGetFrameHeader failed");
m_parseState = ParseJpegXLError;
return false;
}
if (m_basicinfo.animation.tps_denominator > 0 && m_basicinfo.animation.tps_numerator > 0) {
delay = (int)(0.5 + 1000.0 * frame_header.duration * m_basicinfo.animation.tps_denominator / m_basicinfo.animation.tps_numerator);
} else {
delay = 0;
}
m_framedelays.append(delay);
}
if (m_framedelays.isEmpty()) {
qWarning("no frames loaded by the JXL plug-in");
m_parseState = ParseJpegXLError;
return false;
}
if (m_framedelays.count() == 1) {
qWarning("JXL file was marked as animation but it has only one frame.");
m_basicinfo.have_animation = JXL_FALSE;
}
} else { // static picture
m_framedelays.resize(1);
m_framedelays[0] = 0;
}
if (!rewind()) {
return false;
}
m_next_image_delay = m_framedelays[0];
m_parseState = ParseJpegXLSuccess;
return true;
}
bool QJpegXLHandler::decode_one_frame()
{
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
qWarning("Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER", status);
m_parseState = ParseJpegXLError;
return false;
}
m_current_image = QImage(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
if (m_current_image.isNull()) {
qWarning("Memory cannot be allocated");
m_parseState = ParseJpegXLError;
return false;
}
m_current_image.setColorSpace(m_colorspace);
if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetImageOutBuffer failed");
m_parseState = ParseJpegXLError;
return false;
}
status = JxlDecoderProcessInput(m_decoder);
if (status != JXL_DEC_FULL_IMAGE) {
qWarning("Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
m_parseState = ParseJpegXLError;
return false;
}
if (m_target_image_format != m_input_image_format) {
m_current_image.convertTo(m_target_image_format);
}
m_next_image_delay = m_framedelays[m_currentimage_index];
m_previousimage_index = m_currentimage_index;
if (m_framedelays.count() > 1) {
m_currentimage_index++;
if (m_currentimage_index >= m_framedelays.count()) {
if (!rewind()) {
return false;
}
}
}
return true;
}
bool QJpegXLHandler::read(QImage *image)
{
if (!ensureALLCounted()) {
return false;
}
if (m_currentimage_index == m_previousimage_index) {
*image = m_current_image;
return jumpToNextImage();
}
if (decode_one_frame()) {
*image = m_current_image;
return true;
} else {
return false;
}
}
bool QJpegXLHandler::write(const QImage &image)
{
if (image.format() == QImage::Format_Invalid) {
qWarning("No image data to save");
return false;
}
if ((image.width() > 32768) || (image.height() > 32768)) {
qWarning("Image is too large");
return false;
}
JxlEncoder *encoder = JxlEncoderCreate(nullptr);
if (!encoder) {
qWarning("Failed to create Jxl encoder");
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;
if (image.colorSpace().isValid()) {
if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) {
convert_color_profile = true;
} else {
convert_color_profile = false;
}
} else { // no profile or Qt-unsupported ICC profile
convert_color_profile = false;
iccprofile = image.colorSpace().iccProfile();
if (iccprofile.size() > 0) {
output_info.uses_original_profile = 1;
}
}
JxlPixelFormat pixel_format;
QImage::Format tmpformat;
JxlEncoderStatus status;
pixel_format.data_type = JXL_TYPE_UINT16;
pixel_format.endianness = JXL_NATIVE_ENDIAN;
pixel_format.align = 0;
if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA64;
pixel_format.num_channels = 4;
output_info.alpha_bits = 16;
output_info.num_extra_channels = 1;
} else {
tmpformat = QImage::Format_RGBX64;
pixel_format.num_channels = 3;
output_info.alpha_bits = 0;
}
const QImage tmpimage =
convert_color_profile ? image.convertToFormat(tmpformat).convertedToColorSpace(QColorSpace(QColorSpace::SRgb)) : image.convertToFormat(tmpformat);
const size_t xsize = tmpimage.width();
const size_t ysize = tmpimage.height();
const size_t buffer_size = 2 * pixel_format.num_channels * xsize * ysize;
if (xsize == 0 || ysize == 0 || tmpimage.isNull()) {
qWarning("Unable to allocate memory for output image");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
output_info.xsize = tmpimage.width();
output_info.ysize = tmpimage.height();
output_info.bits_per_sample = 16;
output_info.intensity_target = 255.0f;
output_info.orientation = JXL_ORIENT_IDENTITY;
output_info.num_color_channels = 3;
output_info.animation.tps_numerator = 10;
output_info.animation.tps_denominator = 1;
status = JxlEncoderSetBasicInfo(encoder, &output_info);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetBasicInfo failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
if (!convert_color_profile && iccprofile.size() > 0) {
status = JxlEncoderSetICCProfile(encoder, (const uint8_t *)iccprofile.constData(), iccprofile.size());
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetICCProfile failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
} else {
status = JxlEncoderSetColorEncoding(encoder, &color_profile);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetColorEncoding failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
}
if (image.hasAlphaChannel()) {
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size);
} else {
uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
if (!tmp_buffer) {
qWarning("Memory allocation error");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
uint16_t *dest_pixels = tmp_buffer;
for (int y = 0; y < tmpimage.height(); y++) {
const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
for (int x = 0; x < tmpimage.width(); x++) {
// R
*dest_pixels = *src_pixels;
dest_pixels++;
src_pixels++;
// G
*dest_pixels = *src_pixels;
dest_pixels++;
src_pixels++;
// B
*dest_pixels = *src_pixels;
dest_pixels++;
src_pixels += 2; // skipalpha
}
}
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size);
delete[] tmp_buffer;
}
if (status == JXL_ENC_ERROR) {
qWarning("JxlEncoderAddImageFrame failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
JxlEncoderCloseInput(encoder);
std::vector<uint8_t> compressed;
compressed.resize(4096);
size_t offset = 0;
uint8_t *next_out;
size_t avail_out;
do {
next_out = compressed.data() + offset;
avail_out = compressed.size() - offset;
status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
if (status == JXL_ENC_NEED_MORE_OUTPUT) {
offset = next_out - compressed.data();
compressed.resize(compressed.size() * 2);
} else if (status == JXL_ENC_ERROR) {
qWarning("JxlEncoderProcessOutput failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
} while (status != JXL_ENC_SUCCESS);
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
compressed.resize(next_out - compressed.data());
if (compressed.size() > 0) {
qint64 write_status = device()->write((const char *)compressed.data(), compressed.size());
if (write_status > 0) {
return true;
} else if (write_status == -1) {
qWarning("Write error: %s\n", qUtf8Printable(device()->errorString()));
}
}
return false;
}
QVariant QJpegXLHandler::option(ImageOption option) const
{
if (option == Quality) {
return m_quality;
}
if (!supportsOption(option) || !ensureParsed()) {
return QVariant();
}
switch (option) {
case Size:
return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
case Animation:
if (m_basicinfo.have_animation) {
return true;
} else {
return false;
}
default:
return QVariant();
}
}
void QJpegXLHandler::setOption(ImageOption option, const QVariant &value)
{
switch (option) {
case Quality:
m_quality = value.toInt();
if (m_quality > 100) {
m_quality = 100;
} else if (m_quality < 0) {
m_quality = 90;
}
return;
default:
break;
}
QImageIOHandler::setOption(option, value);
}
bool QJpegXLHandler::supportsOption(ImageOption option) const
{
return option == Quality || option == Size || option == Animation;
}
int QJpegXLHandler::imageCount() const
{
if (!ensureParsed()) {
return 0;
}
if (m_parseState == ParseJpegXLBasicInfoParsed) {
if (!m_basicinfo.have_animation) {
return 1;
}
if (!ensureALLCounted()) {
return 0;
}
}
if (!m_framedelays.isEmpty()) {
return m_framedelays.count();
}
return 0;
}
int QJpegXLHandler::currentImageNumber() const
{
if (m_parseState == ParseJpegXLNotParsed) {
return -1;
}
if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
return 0;
}
return m_currentimage_index;
}
bool QJpegXLHandler::jumpToNextImage()
{
if (!ensureALLCounted()) {
return false;
}
if (m_framedelays.count() > 1) {
m_currentimage_index++;
if (m_currentimage_index >= m_framedelays.count()) {
if (!rewind()) {
return false;
}
} else {
JxlDecoderSkipFrames(m_decoder, 1);
}
}
return true;
}
bool QJpegXLHandler::jumpToImage(int imageNumber)
{
if (!ensureALLCounted()) {
return false;
}
if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
return false;
}
if (imageNumber == m_currentimage_index) {
return true;
}
if (imageNumber > m_currentimage_index) {
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
m_currentimage_index = imageNumber;
return true;
}
if (!rewind()) {
return false;
}
if (imageNumber > 0) {
JxlDecoderSkipFrames(m_decoder, imageNumber);
}
m_currentimage_index = imageNumber;
return true;
}
int QJpegXLHandler::nextImageDelay() const
{
if (!ensureALLCounted()) {
return 0;
}
if (m_framedelays.count() < 2) {
return 0;
}
return m_next_image_delay;
}
int QJpegXLHandler::loopCount() const
{
if (!ensureParsed()) {
return 0;
}
if (m_basicinfo.have_animation) {
return 1;
} else {
return 0;
}
}
bool QJpegXLHandler::rewind()
{
m_currentimage_index = 0;
JxlDecoderReleaseInput(m_decoder);
JxlDecoderRewind(m_decoder);
if (m_runner) {
if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetParallelRunner failed");
m_parseState = ParseJpegXLError;
return false;
}
}
if (JxlDecoderSetInput(m_decoder, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetInput failed");
m_parseState = ParseJpegXLError;
return false;
}
if (m_basicinfo.uses_original_profile) {
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
m_parseState = ParseJpegXLError;
return false;
}
} else {
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
m_parseState = ParseJpegXLError;
return false;
}
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
if (status != JXL_DEC_COLOR_ENCODING) {
qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
m_parseState = ParseJpegXLError;
return false;
}
JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
}
return true;
}
QImageIOPlugin::Capabilities QJpegXLPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "jxl") {
return Capabilities(CanRead | CanWrite);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && QJpegXLHandler::canRead(device)) {
cap |= CanRead;
}
if (device->isWritable()) {
cap |= CanWrite;
}
return cap;
}
QImageIOHandler *QJpegXLPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new QJpegXLHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}

View File

@@ -0,0 +1,7 @@
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=QImageIOPlugins
X-KDE-ImageFormat=jxl
X-KDE-MimeType=image/jxl
X-KDE-Read=true
X-KDE-Write=true

View File

@@ -0,0 +1,4 @@
{
"Keys": [ "jxl" ],
"MimeTypes": [ "image/jxl" ]
}

96
src/imageformats/jxl_p.h Normal file
View File

@@ -0,0 +1,96 @@
/*
JPEG XL (JXL) support for QImage.
SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
SPDX-License-Identifier: BSD-2-Clause
*/
#ifndef KIMG_JXL_P_H
#define KIMG_JXL_P_H
#include <QByteArray>
#include <QColorSpace>
#include <QImage>
#include <QImageIOHandler>
#include <QImageIOPlugin>
#include <QVariant>
#include <QVector>
#include <jxl/decode.h>
class QJpegXLHandler : public QImageIOHandler
{
public:
QJpegXLHandler();
~QJpegXLHandler();
bool canRead() const override;
bool read(QImage *image) override;
bool write(const QImage &image) override;
static bool canRead(QIODevice *device);
QVariant option(ImageOption option) const override;
void setOption(ImageOption option, const QVariant &value) override;
bool supportsOption(ImageOption option) const override;
int imageCount() const override;
int currentImageNumber() const override;
bool jumpToNextImage() override;
bool jumpToImage(int imageNumber) override;
int nextImageDelay() const override;
int loopCount() const override;
private:
bool ensureParsed() const;
bool ensureALLCounted() const;
bool ensureDecoder();
bool countALLFrames();
bool decode_one_frame();
bool rewind();
enum ParseJpegXLState {
ParseJpegXLError = -1,
ParseJpegXLNotParsed = 0,
ParseJpegXLSuccess = 1,
ParseJpegXLBasicInfoParsed = 2,
};
ParseJpegXLState m_parseState;
int m_quality;
int m_currentimage_index;
int m_previousimage_index;
QByteArray m_rawData;
JxlDecoder *m_decoder;
void *m_runner;
JxlBasicInfo m_basicinfo;
QVector<int> m_framedelays;
int m_next_image_delay;
QImage m_current_image;
QColorSpace m_colorspace;
QImage::Format m_input_image_format;
QImage::Format m_target_image_format;
JxlPixelFormat m_input_pixel_format;
size_t m_buffer_size;
};
class QJpegXLPlugin : public QImageIOPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "jxl.json")
public:
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
};
#endif // KIMG_JXL_P_H

View File

@@ -18,6 +18,11 @@
static constexpr char s_magic[] = "application/x-krita";
static constexpr int s_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0
static constexpr int s_krita3_offset = 0x26;
static constexpr int s_krita4_offset = 0x2B;
static constexpr int s_krita4_64_offset = 0x40;
static constexpr int s_krita5_offset = 0x26;
static constexpr int s_krita5_64_offset = 0x3A;
KraHandler::KraHandler()
{
@@ -35,12 +40,14 @@ bool KraHandler::canRead() const
bool KraHandler::read(QImage *image)
{
KZip zip(device());
if (!zip.open(QIODevice::ReadOnly))
if (!zip.open(QIODevice::ReadOnly)) {
return false;
}
const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png"));
if (!entry || !entry->isFile())
if (!entry || !entry->isFile()) {
return false;
}
const KZipFileEntry *fileZipEntry = static_cast<const KZipFileEntry *>(entry);
@@ -56,9 +63,8 @@ bool KraHandler::canRead(QIODevice *device)
return false;
}
char buff[57];
if (device->peek(buff, sizeof(buff)) == sizeof(buff))
return memcmp(buff + 0x26, s_magic, s_magic_size) == 0;
ByteArray ba = device->peek(43 + s_magic_size);
return (ba.contains(s_magic));
return false;
}

View File

@@ -17,6 +17,11 @@
static constexpr char s_magic[] = "image/openraster";
static constexpr int s_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0
static constexpr int s_krita3_offset = 0x26;
static constexpr int s_krita4_offset = 0x2B;
static constexpr int s_krita4_64_offset = 0x40;
static constexpr int s_krita5_offset = 0x26;
static constexpr int s_krita5_64_offset = 0x3A;
OraHandler::OraHandler()
{
@@ -34,12 +39,14 @@ bool OraHandler::canRead() const
bool OraHandler::read(QImage *image)
{
KZip zip(device());
if (!zip.open(QIODevice::ReadOnly))
if (!zip.open(QIODevice::ReadOnly)) {
return false;
}
const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png"));
if (!entry || !entry->isFile())
if (!entry || !entry->isFile()) {
return false;
}
const KZipFileEntry *fileZipEntry = static_cast<const KZipFileEntry *>(entry);
@@ -55,9 +62,11 @@ bool OraHandler::canRead(QIODevice *device)
return false;
}
char buff[54];
if (device->peek(buff, sizeof(buff)) == sizeof(buff))
return memcmp(buff + 0x26, s_magic, s_magic_size) == 0;
if (device->peek(s_krita3_offset + s_magic_size).contains(s_magic)) return true;
if (device->peek(s_krita4_offset + s_magic_size).contains(s_magic)) return true;
if (device->peek(s_krita4_64_offset + s_magic_size).contains(s_magic)) return true;
if (device->peek(s_krita5_offset + s_magic_size).contains(s_magic)) return true;
if (device->peek(s_krita5_64_offset + s_magic_size).contains(s_magic)) return true;
return false;
}

View File

@@ -105,7 +105,9 @@ public:
static QDataStream &operator>>(QDataStream &s, RGB &rgb)
{
quint8 r, g, b;
quint8 r;
quint8 g;
quint8 b;
s >> r >> g >> b;
rgb.r = r;
@@ -126,24 +128,32 @@ static QDataStream &operator>>(QDataStream &s, Palette &pal)
static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
{
quint8 m, ver, enc, bpp;
quint8 m;
quint8 ver;
quint8 enc;
quint8 bpp;
s >> m >> ver >> enc >> bpp;
ph.Manufacturer = m;
ph.Version = ver;
ph.Encoding = enc;
ph.Bpp = bpp;
quint16 xmin, ymin, xmax, ymax;
quint16 xmin;
quint16 ymin;
quint16 xmax;
quint16 ymax;
s >> xmin >> ymin >> xmax >> ymax;
ph.XMin = xmin;
ph.YMin = ymin;
ph.XMax = xmax;
ph.YMax = ymax;
quint16 hdpi, ydpi;
quint16 hdpi;
quint16 ydpi;
s >> hdpi >> ydpi;
ph.HDpi = hdpi;
ph.YDpi = ydpi;
Palette colorMap;
quint8 res, np;
quint8 res;
quint8 np;
s >> colorMap >> res >> np;
ph.ColorMap = colorMap;
ph.Reserved = res;
@@ -154,7 +164,8 @@ static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
quint16 paletteinfo;
s >> paletteinfo;
ph.PaletteInfo = paletteinfo;
quint16 hscreensize, vscreensize;
quint16 hscreensize;
quint16 vscreensize;
s >> hscreensize;
ph.HScreenSize = hscreensize;
s >> vscreensize;
@@ -222,7 +233,8 @@ static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
{
quint32 i = 0;
quint32 size = buf.size();
quint8 byte, count;
quint8 byte;
quint8 count;
if (header.isCompressed()) {
// Uncompress the image data
@@ -300,10 +312,11 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
for (int i = 0; i < 4; i++) {
quint32 offset = i * header.BytesPerLine;
for (int x = 0; x < header.width(); ++x)
for (int x = 0; x < header.width(); ++x) {
if (buf[offset + (x / 8)] & (128 >> (x % 8))) {
pixbuf[x] = (int)(pixbuf[x]) + (1 << i);
}
}
}
uchar *p = img.scanLine(y);
@@ -343,8 +356,9 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
uchar *p = img.scanLine(y);
if (!p)
if (!p) {
return;
}
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
for (unsigned int x = 0; x < bpl; ++x) {
@@ -358,7 +372,9 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
// Read the palette
quint8 r, g, b;
quint8 r;
quint8 g;
quint8 b;
for (int i = 0; i < 256; ++i) {
s >> r >> g >> b;
img.setColor(i, qRgb(r, g, b));
@@ -400,7 +416,8 @@ static void writeLine(QDataStream &s, QByteArray &buf)
{
quint32 i = 0;
quint32 size = buf.size();
quint8 count, data;
quint8 count;
quint8 data;
char byte;
while (i < size) {
@@ -473,10 +490,11 @@ static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
}
for (int x = 0; x < header.width(); ++x) {
for (int i = 0; i < 4; ++i)
for (int i = 0; i < 4; ++i) {
if (*(p + x) & (1 << i)) {
buf[i][x / 8] = (int)(buf[i][x / 8]) | 1 << (7 - x % 8);
}
}
}
for (int i = 0; i < 4; ++i) {
@@ -618,8 +636,12 @@ bool PCXHandler::write(const QImage &image)
QImage img = image;
int w = img.width();
int h = img.height();
const int w = img.width();
const int h = img.height();
if (w > 65536 || h > 65536) {
return false;
}
// qDebug() << "Width: " << w;
// qDebug() << "Height: " << h;

View File

@@ -24,6 +24,7 @@
#include <algorithm>
#include <functional>
#include <qendian.h>
#include <utility>
/**
* Reads a PIC file header from a data stream.
@@ -225,7 +226,7 @@ bool SoftimagePICHandler::read(QImage *image)
}
QImage::Format fmt = QImage::Format_RGB32;
for (const PicChannel &channel : qAsConst(m_channels)) {
for (const PicChannel &channel : std::as_const(m_channels)) {
if (channel.size != 8) {
// we cannot read images that do not come in bytes
qDebug() << "Channel size was" << channel.size;
@@ -404,7 +405,7 @@ QVariant SoftimagePICHandler::option(ImageOption option) const
return QString();
case ImageFormat:
if (const_cast<SoftimagePICHandler *>(this)->readChannels()) {
for (const PicChannel &channel : qAsConst(m_channels)) {
for (const PicChannel &channel : std::as_const(m_channels)) {
if (channel.code & ALPHA) {
return QImage::Format_ARGB32;
}

View File

@@ -154,13 +154,16 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
// Allocate image
img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);
if (img.isNull())
if (img.isNull()) {
return false;
}
// Reconstruct image from RGB palette if we have a palette
// TODO: make generic so it works with 24bit or 32bit palettes
if (ras.ColorMapType == 1 && ras.Depth == 8) {
quint8 red, green, blue;
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = palette.value((int)input[y * ras.Width + x]);
@@ -172,7 +175,9 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
}
if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
quint8 red, green, blue;
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = input[y * 3 * ras.Width + x * 3 + 2];
@@ -184,7 +189,9 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
}
if (ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
quint8 red, green, blue;
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = input[y * 3 * ras.Width + x * 3];
@@ -196,7 +203,9 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
}
if (ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
quint8 red, green, blue;
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = input[y * 4 * ras.Width + x * 4 + 3];
@@ -208,7 +217,9 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
}
if (ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3) {
quint8 red, green, blue;
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = input[y * 4 * ras.Width + x * 4 + 1];
@@ -274,8 +285,9 @@ bool RASHandler::read(QImage *outImage)
RasHeader ras;
s >> ras;
if (ras.ColorMapLength > std::numeric_limits<int>::max())
if (ras.ColorMapLength > std::numeric_limits<int>::max()) {
return false;
}
// TODO: add support for old versions of RAS where Length may be zero in header
s.device()->seek(RasHeader::SIZE + ras.Length + ras.ColorMapLength);

View File

@@ -138,7 +138,8 @@ SGIImage::~SGIImage()
bool SGIImage::getRow(uchar *dest)
{
int n, i;
int n;
int i;
if (!_rle) {
for (i = 0; i < _xsize; i++) {
if (_pos >= _data.end()) {
@@ -184,7 +185,8 @@ bool SGIImage::readData(QImage &img)
quint32 *start = _starttab;
QByteArray lguard(_xsize, 0);
uchar *line = (uchar *)lguard.data();
unsigned x, y;
unsigned x;
unsigned y;
if (!_rle) {
_pos = _data.begin();
@@ -279,9 +281,9 @@ bool SGIImage::readImage(QImage &img)
// bytes per channel
_stream >> _bpc;
// qDebug() << "bytes per channel: " << int(_bpc);
if (_bpc == 1)
if (_bpc == 1) {
;
else if (_bpc == 2) {
} else if (_bpc == 2) {
// qDebug() << "dropping least significant byte";
} else {
return false;
@@ -328,8 +330,9 @@ bool SGIImage::readImage(QImage &img)
return false;
}
if (_zsize == 0)
if (_zsize == 0) {
return false;
}
if (_zsize == 2 || _zsize == 4) {
img = img.convertToFormat(QImage::Format_ARGB32);
@@ -337,8 +340,9 @@ bool SGIImage::readImage(QImage &img)
// qDebug() << "using first 4 of " << _zsize << " channels";
// Only let this continue if it won't cause a int overflow later
// this is most likely a broken file anyway
if (_ysize > std::numeric_limits<int>::max() / _zsize)
if (_ysize > std::numeric_limits<int>::max() / _zsize) {
return false;
}
}
_numrows = _ysize * _zsize;
@@ -363,13 +367,15 @@ bool SGIImage::readImage(QImage &img)
_data = _dev->readAll();
// sanity check
if (_rle)
for (uint o = 0; o < _numrows; o++)
if (_rle) {
for (uint o = 0; o < _numrows; o++) {
// don't change to greater-or-equal!
if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
// qDebug() << "image corrupt (sanity check failed)";
return false;
}
}
}
if (!readData(img)) {
// qDebug() << "image corrupt (incomplete scanline)";
@@ -390,7 +396,8 @@ void RLEData::write(QDataStream &s)
bool RLEData::operator<(const RLEData &b) const
{
uchar ac, bc;
uchar ac;
uchar bc;
for (int i = 0; i < qMin(size(), b.size()); i++) {
ac = at(i);
bc = b[i];
@@ -436,8 +443,13 @@ uchar SGIImage::intensity(uchar c)
uint SGIImage::compact(uchar *d, uchar *s)
{
uchar *dest = d, *src = s, patt, *t, *end = s + _xsize;
int i, n;
uchar *dest = d;
uchar *src = s;
uchar patt;
uchar *t;
uchar *end = s + _xsize;
int i;
int n;
while (src < end) {
for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++) {
n++;
@@ -480,7 +492,8 @@ bool SGIImage::scanData(const QImage &img)
uchar *line = (uchar *)lineguard.data();
uchar *buf = (uchar *)bufguard.data();
const QRgb *c;
unsigned x, y;
unsigned x;
unsigned y;
uint len;
for (y = 0; y < _ysize; y++) {
@@ -606,7 +619,8 @@ void SGIImage::writeVerbatim(const QImage &img)
writeHeader();
const QRgb *c;
unsigned x, y;
unsigned x;
unsigned y;
for (y = 0; y < _ysize; y++) {
c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
@@ -667,9 +681,16 @@ bool SGIImage::writeImage(const QImage &image)
return false;
}
const int w = img.width();
const int h = img.height();
if (w > 65536 || h > 65536) {
return false;
}
_bpc = 1;
_xsize = img.width();
_ysize = img.height();
_xsize = w;
_ysize = h;
_pixmin = ~0u;
_pixmax = 0;
_colormap = NORMAL;

View File

@@ -311,7 +311,9 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
}
// Convert image to internal format.
int y_start, y_step, y_end;
int y_start;
int y_step;
int y_end;
if (tga.flags & TGA_ORIGIN_UPPER) {
y_start = 0;
y_step = 1;
@@ -437,7 +439,7 @@ bool TGAHandler::write(const QImage &image)
s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4)
for (int y = 0; y < img.height(); y++)
for (int y = 0; y < img.height(); y++) {
for (int x = 0; x < img.width(); x++) {
const QRgb color = img.pixel(x, y);
s << quint8(qBlue(color));
@@ -447,6 +449,7 @@ bool TGAHandler::write(const QImage &image)
s << quint8(qAlpha(color));
}
}
}
return true;
}

View File

@@ -537,6 +537,7 @@ inline QRgb qRgba(const QRgb rgb, int a)
*/
XCFImageFormat::XCFImageFormat()
{
static_assert(sizeof(QRgb) == 4, "the code assumes sizeof(QRgb) == 4, if that's not your case, help us fix it :)");
}
/*!
@@ -626,12 +627,17 @@ bool XCFImageFormat::readXCF(QIODevice *device, QImage *outImage)
QStack<qint64> layer_offsets;
while (true) {
qint64 layer_offset = readOffsetPtr(xcf_io);
const qint64 layer_offset = readOffsetPtr(xcf_io);
if (layer_offset == 0) {
break;
}
if (layer_offset < 0) {
qCDebug(XCFPLUGIN) << "XCF: negative layer offset";
return false;
}
layer_offsets.push(layer_offset);
}
@@ -741,7 +747,9 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
xcf_image.palette.reserve(xcf_image.num_colors);
for (int i = 0; i < xcf_image.num_colors; i++) {
uchar r, g, b;
uchar r;
uchar g;
uchar b;
property >> r >> g >> b;
xcf_image.palette.push_back(qRgb(r, g, b));
}
@@ -888,6 +896,16 @@ bool XCFImageFormat::loadLayer(QDataStream &xcf_io, XCFImage &xcf_image)
layer.hierarchy_offset = readOffsetPtr(xcf_io);
layer.mask_offset = readOffsetPtr(xcf_io);
if (layer.hierarchy_offset < 0) {
qCDebug(XCFPLUGIN) << "XCF: negative layer hierarchy_offset";
return false;
}
if (layer.mask_offset < 0) {
qCDebug(XCFPLUGIN) << "XCF: negative layer mask_offset";
return false;
}
// Allocate the individual tile QImages based on the size and type
// of this layer.
@@ -1303,10 +1321,15 @@ bool XCFImageFormat::loadHierarchy(QDataStream &xcf_io, Layer &layer)
quint32 bpp;
xcf_io >> width >> height >> bpp;
qint64 offset = readOffsetPtr(xcf_io);
const qint64 offset = readOffsetPtr(xcf_io);
qCDebug(XCFPLUGIN) << "width" << width << "height" << height << "bpp" << bpp << "offset" << offset;
if (offset < 0) {
qCDebug(XCFPLUGIN) << "XCF: negative hierarchy offset";
return false;
}
const bool isMask = layer.assignBytes == assignMaskBytes;
// make sure bpp is correct and complain if it is not
@@ -1361,6 +1384,11 @@ bool XCFImageFormat::loadHierarchy(QDataStream &xcf_io, Layer &layer)
break;
}
if (bpp > 4) {
qCDebug(XCFPLUGIN) << "bpp is" << bpp << "We don't support layers with bpp > 4";
return false;
}
// GIMP stores images in a "mipmap"-like format (multiple levels of
// increasingly lower resolution). Only the top level is used here,
// however.
@@ -1402,6 +1430,11 @@ bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp)
xcf_io >> width >> height;
qint64 offset = readOffsetPtr(xcf_io);
if (offset < 0) {
qCDebug(XCFPLUGIN) << "XCF: negative level offset";
return false;
}
if (offset == 0) {
// offset 0 with rowsxcols != 0 is probably an error since it means we have tiles
// without data but just clear the bits for now instead of returning false
@@ -1426,6 +1459,11 @@ bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp)
qint64 saved_pos = xcf_io.device()->pos();
qint64 offset2 = readOffsetPtr(xcf_io);
if (offset2 < 0) {
qCDebug(XCFPLUGIN) << "XCF: negative level offset";
return false;
}
// Evidently, RLE can occasionally expand a tile instead of compressing it!
if (offset2 == 0) {
offset2 = offset + (uint)(TILE_WIDTH * TILE_HEIGHT * 4 * 1.5);
@@ -1471,6 +1509,11 @@ bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp)
xcf_io.device()->seek(saved_pos);
offset = readOffsetPtr(xcf_io);
if (offset < 0) {
qCDebug(XCFPLUGIN) << "XCF: negative level offset";
return false;
}
}
}
@@ -1491,13 +1534,18 @@ bool XCFImageFormat::loadMask(QDataStream &xcf_io, Layer &layer)
xcf_io >> width >> height >> name;
delete name;
delete[] name;
if (!loadChannelProperties(xcf_io, layer)) {
return false;
}
qint64 hierarchy_offset = readOffsetPtr(xcf_io);
const qint64 hierarchy_offset = readOffsetPtr(xcf_io);
if (hierarchy_offset < 0) {
qCDebug(XCFPLUGIN) << "XCF: negative mask hierarchy_offset";
return false;
}
xcf_io.device()->seek(hierarchy_offset);
layer.assignBytes = assignMaskBytes;
@@ -1905,11 +1953,13 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
if (xcf_image.x_resolution > 0 && xcf_image.y_resolution > 0) {
const float dpmx = xcf_image.x_resolution * INCHESPERMETER;
if (dpmx > std::numeric_limits<int>::max())
if (dpmx > std::numeric_limits<int>::max()) {
return false;
}
const float dpmy = xcf_image.y_resolution * INCHESPERMETER;
if (dpmy > std::numeric_limits<int>::max())
if (dpmy > std::numeric_limits<int>::max()) {
return false;
}
image.setDotsPerMeterX((int)dpmx);
image.setDotsPerMeterY((int)dpmy);
}
@@ -2593,7 +2643,8 @@ void XCFImageFormat::mergeRGBToRGB(const Layer &layer, uint i, uint j, int k, in
src_a = qMin(src_a, dst_a);
} break;
case GIMP_LAYER_MODE_SOFTLIGHT_LEGACY: {
uint tmpS, tmpM;
uint tmpS;
uint tmpM;
tmpM = INT_MULT(dst_r, src_r);
tmpS = 255 - INT_MULT((255 - dst_r), (255 - src_r));
@@ -2661,7 +2712,10 @@ void XCFImageFormat::mergeRGBToRGB(const Layer &layer, uint i, uint j, int k, in
src_a = INT_MULT(src_a, layer.mask_tiles[j][i].pixelIndex(k, l));
}
uchar new_r, new_g, new_b, new_a;
uchar new_r;
uchar new_g;
uchar new_b;
uchar new_a;
new_a = dst_a + INT_MULT(OPAQUE_OPACITY - dst_a, src_a);
const float src_ratio = new_a == 0 ? 1.0 : (float)src_a / new_a;
@@ -2767,7 +2821,8 @@ void XCFImageFormat::mergeGrayAToGray(const Layer &layer, uint i, uint j, int k,
}
} break;
case SOFTLIGHT_MODE: {
uint tmpS, tmpM;
uint tmpS;
uint tmpM;
tmpM = INT_MULT(dst, src);
tmpS = 255 - INT_MULT((255 - dst), (255 - src));
@@ -2919,7 +2974,8 @@ void XCFImageFormat::mergeGrayAToRGB(const Layer &layer, uint i, uint j, int k,
src_a = qMin(src_a, dst_a);
} break;
case SOFTLIGHT_MODE: {
uint tmpS, tmpM;
uint tmpS;
uint tmpM;
tmpM = INT_MULT(dst, src);
tmpS = 255 - INT_MULT((255 - dst), (255 - src));

View File

@@ -5,7 +5,7 @@ include(ECMMarkAsTest)
macro(kimageformats_executable_tests)
foreach(_testname ${ARGN})
add_executable(${_testname} ${_testname}.cpp)
target_link_libraries(${_testname} Qt5::Gui)
target_link_libraries(${_testname} Qt${QT_MAJOR_VERSION}::Gui)
ecm_mark_as_test(${_testname})
endforeach(_testname)
endmacro()

View File

@@ -38,6 +38,9 @@ int main(int argc, char **argv)
QCommandLineOption listformats(QStringList() << QStringLiteral("l") << QStringLiteral("list"), QStringLiteral("List supported image formats"));
parser.addOption(listformats);
QCommandLineOption listmimes(QStringList() << QStringLiteral("m") << QStringLiteral("listmime"), QStringLiteral("List supported image mime formats"));
parser.addOption(listmimes);
parser.process(app);
const QStringList files = parser.positionalArguments();
@@ -57,6 +60,21 @@ int main(int argc, char **argv)
return 0;
}
if (parser.isSet(listmimes)) {
QTextStream out(stdout);
out << "Input mime formats:\n";
const auto lstReaderSupportedMimes = QImageReader::supportedMimeTypes();
for (const QByteArray &fmt : lstReaderSupportedMimes) {
out << " " << fmt << '\n';
}
out << "Output mime formats:\n";
const auto lstWriterSupportedMimes = QImageWriter::supportedMimeTypes();
for (const QByteArray &fmt : lstWriterSupportedMimes) {
out << " " << fmt << '\n';
}
return 0;
}
if (files.count() != 2) {
QTextStream(stdout) << "Must provide exactly two files\n";
parser.showHelp(1);