Compare commits

...

80 Commits

Author SHA1 Message Date
ae6b724824 GIT_SILENT Upgrade ECM and KF version requirements for 5.93.0 release. 2022-04-02 10:00:12 +00:00
3e751dd80d Fix XCF parasites metadata in QImage and support to ICC profile
- Fix parasite "gimp-comment" not set due to null QImage
- Support to parasite "icc-profile" using Qt 5.14+ API
- Added parasite "gimp-image-metadata" as QImage metadata "XML:org.gimp.xml"
- Added a XCF with XML metadata and icc prifile embedded in autotest folder (generated by GIMP 2.10.30)
- Tested with Qt 5.15.2 and Qt 6.2.3 under Windows and Qt 6.2.3 under macOS
2022-03-23 23:34:33 +00:00
e69dff73e6 avif: encoder speed 7->6 2022-03-10 09:44:50 +01:00
64cfe52bee avif: fix jumpToImage 2022-03-10 09:39:53 +01:00
8732fc8487 avif: warn about non-recommended libavif configuration 2022-03-10 09:35:08 +01:00
d9729b7190 GIT_SILENT Upgrade ECM and KF version requirements for 5.92.0 release. 2022-03-05 11:15:00 +00:00
55d3c568b2 Add Qt6 Android CI 2022-03-01 16:04:48 +00:00
4afafee6c1 Add write tests for heif/avif/jxl
Unfortunately none of them pass since it seems they can't load a png,
save it to their format with loseless quality and read it back and get
exactly the same contents than the png
2022-02-18 00:05:01 +01:00
f04084e175 jxl: encoding improvements
Plug-in can save in 8bit depth now,
previously only 16bit was supported.
Memory and dimension limits were adjusted.
2022-02-16 10:26:56 +01:00
9911c9c2ea avif: adjust dimension and memory limits
With or height can be above 32k now (up to 64k), but
image should not have more than 256megapixels
(Default memory limit of libavif)
2022-02-11 16:01:07 +01:00
4ceef5164d GIT_SILENT Upgrade ECM and KF version requirements for 5.91.0 release. 2022-02-05 15:14:16 +00:00
3d2d91a08a Fix typo, should be qCWarning
GIT_SILENT
2022-02-03 16:34:06 +02:00
0a02458560 Check executables exist in PATH before passing them to QProcess
See:
https://kde.org/info/security/advisory-20220131-1.txt
https://mail.kde.org/pipermail/kde-devel/2022-January/000943.html
2022-02-03 11:32:24 +02:00
96836e849f Fix handling of null terminated ANI metadata with Qt6
In Qt5 converting a QByteArray to a QString stops at the first null byte,
in Qt6 the QByteArray size is respected, and trailing null bytes are
therefore included in the final QString. Explicitly determine the length
of the string data to deal with that.
2022-01-22 22:03:21 +01:00
f4edb7296f Add CI qt6 support 2022-01-08 09:27:14 +01:00
56376ffd66 GIT_SILENT Upgrade ECM and KF version requirements for 5.90.0 release. 2022-01-01 12:16:00 +00:00
f534254063 GIT_SILENT: It compiles fine without deprecated methods 2021-12-26 11:35:54 +01:00
32347725cb Fix typo 2021-12-21 20:28:05 +01:00
56e762c563 Make it compile against qt6 2021-12-21 20:10:11 +01:00
81603ed908 GIT_SILENT Upgrade ECM and KF version requirements for 5.89.0 release. 2021-12-04 17:00:59 +00:00
957c199c68 GIT_SILENT: add missing licences 2021-12-02 07:14:54 +01:00
f445e5dd0a avif: limit scope of variables 2021-11-29 17:35:51 +01:00
d3da56bba3 Add JXL to the list of supported formats 2021-11-17 20:06:11 +01:00
41c4b5930c Add plugin for JPEG XL (JXL) 2021-11-10 16:01:58 +01:00
fb66044714 Add FreeBSD CI 2021-10-07 00:28:35 +02:00
a43394a759 Add Android CI 2021-10-07 00:04:08 +02:00
904c251f50 GIT_SILENT Upgrade ECM and KF version requirements for 5.87.0 release. 2021-10-02 12:18:49 +00:00
2f9e09f04a Add Gitlab CI configuration 2021-09-29 21:55:22 +13:00
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
45cd128f73 GIT_SILENT Add CI configuration file 2021-09-05 14:03:42 +02:00
e89d21f12d GIT_SILENT Upgrade ECM and KF version requirements for 5.86.0 release. 2021-09-04 15:45:36 +00:00
1d2b51ddf1 Fix build with clang12 + libc++ 2021-08-30 20:29:23 +02:00
1080976abe GIT_SILENT: we can use std::as_const directly 2021-08-28 18:07:43 +02:00
abd550c60c GIT_SILENT: replace MacOSX with macOS 2021-08-25 14:56:21 +02:00
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
7642633551 SGIImage::writeImage: Properly fail if the image is too big 2021-08-19 17:29:44 +02:00
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
28099eed71 GIT_SILENT Upgrade Qt5 version requirement to 5.15.2. 2021-08-15 08:56:59 +00:00
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
a8f92e5525 PCXHandler::write: Properly fail if the image is too big 2021-08-12 16:43:52 +02:00
fbeef559b7 exr: Repair compability with openexr2
BUGS: 440084
2021-07-21 00:04:45 +02:00
7f56d835f0 GIT_SILENT Upgrade ECM and KF version requirements for 5.85.0 release. 2021-07-14 22:32:29 +00:00
8f87ce4cb2 Fix typos found by codespell
GIT_SILENT
2021-07-14 00:08:38 +02:00
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
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
1b2bf6e931 Remove CMAKE_CXX_STANDARD, now set to 17 in KDEFrameworkCompilerSettings in ECM
NO_CHANGELOG
2021-06-19 20:05:41 +02:00
894391b000 GIT_SILENT Upgrade ECM and KF version requirements for 5.84.0 release. 2021-06-19 15:58:16 +00:00
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
96b40da089 GIT_SILENT Upgrade ECM and KF version requirements for 5.83.0 release. 2021-06-05 08:55:28 +00:00
bf3f94da76 avif: Adjust for libavif breaking change 2021-06-04 14:37:10 +02:00
318dacda75 Remove compiler flags already defined in extra-cmake-modules
-DQT_NO_FOREACH

GIT_SILENT
2021-05-23 17:18:51 +02:00
e358bb0feb Bump required CMake version to 3.16
KF6 task: https://phabricator.kde.org/T14467
2021-05-17 13:21:23 +02:00
dca6e87c89 Enable HEIC plugin to save all ICC profiles 2021-05-14 12:30:28 +02:00
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
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
780f342825 GIT_SILENT Add auto generated files to .gitignore 2021-05-02 12:48:16 +02:00
297ed9a2fe xcf: Fix Stack-buffer-overflow WRITE on broken files
oss-fuzz/33742
2021-05-02 09:50:50 +00:00
55b4077f2c GIT_SILENT Upgrade ECM and KF version requirements for 5.82.0 release. 2021-05-01 09:42:14 +00:00
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
224f892b09 GIT_SILENT increase KF_DISABLE_DEPRECATED_BEFORE_AND_AT 2021-04-11 12:05:38 +02:00
64fa129ed6 GIT_SILENT Add auto generated files to .gitignore 2021-04-07 21:06:01 +02:00
3cb4021afc test: imageconverter: add a way to list mimes instead of formats 2021-04-05 09:44:03 +00:00
95a19a15c3 xcf: fix new[]/delete mismatch, as detected by ASAN 2021-04-04 17:30:52 +02:00
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
bc3c04c7ce GIT_SILENT Upgrade Qt5 version requirement to 5.15.0. 2021-04-04 14:44:40 +02:00
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
3b0c767f82 GIT_SILENT Upgrade ECM and KF version requirements for 5.81.0 release. 2021-04-03 09:33:41 +00:00
e80fcd7c30 GIT_SILENT increase KF_DISABLE_DEPRECATED_BEFORE_AND_AT 2021-03-27 11:37:36 +01:00
a73e3d44dd Add .git-blame-ignore-revs
GIT_SILENT
2021-03-09 02:18:09 +02:00
1169859b07 Run clang-format on all cpp/h files
NO_CHANGELOG
2021-03-08 20:15:33 +02:00
04e276dcb3 Add clang-format bits to CMakeLists.txt
GIT_SILENT
2021-03-08 20:14:48 +02:00
e3ab850712 Add a trailing comma to enum
Should help produce better diffs and clang-format won't squash the enum
on one line.

GIT_SILENT
2021-03-08 20:14:42 +02:00
503b3eee2b Fix Non-square Radiance/RGBE/.hdr images failing to load
The HDR QImageIOHandler plugin only supports the default image orientation (-Y +X) in .hdr files. It mixes up the width and height however, resulting in non-square images not loading.

This fix adds a check for the standard image orientation in the file and returns false (with error message) if that fails.
If it succeeds, it takes the height from the -Y component, and the width from the +X component, resulting in successful loading of the image.

Add autotest images for landscape and portrait HDR (Radiance RGBE) loader

BUGS: 433877
2021-03-04 22:57:23 +01:00
511a22f0b4 Check the input buffer before passing it to libheif 2021-03-02 11:46:13 +00:00
c532227d43 GIT_SILENT Upgrade ECM and KF version requirements for 5.80.0 release. 2021-02-28 18:59:05 +00:00
1462c3abd6 Check primaries returned from libavif
Due to various double vs float arithmetic,
some primaries could be rejected by Qt.
If necessary, we adjust the values so they
will be accepted by Qt.

Remove newline from the ends of error strings.
2021-02-27 19:11:56 +01:00
ca52d4ddf5 Add plugin for High Efficiency Image File Format (HEIF)
Code partially by Sirius Bakke
2021-02-25 11:52:00 +01:00
7ba4d6adda GIT_SILENT increase KF_DISABLE_DEPRECATED_BEFORE_AND_AT 2021-02-13 14:41:28 +01:00
0aaab103b1 Add compile_commands.json to .gitignore
See https://invent.kde.org/sdk/kdesrc-build/-/merge_requests/82

GIT_SILENT
2021-02-11 14:27:34 +02:00
8b9125c913 Quality option can be returned without parsing input file. 2021-02-08 10:00:53 +01:00
65 changed files with 3662 additions and 1448 deletions

3
.git-blame-ignore-revs Normal file
View File

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

5
.gitignore vendored
View File

@ -21,3 +21,8 @@ CMakeLists.txt.user*
*.unc-backup* *.unc-backup*
.cmake/ .cmake/
/.clang-format /.clang-format
/compile_commands.json
.clangd
.idea
/cmake-build*
.cache

9
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,9 @@
# 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
- 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

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.5) cmake_minimum_required(VERSION 3.16)
project(KImageFormats) project(KImageFormats)
set (CMAKE_CXX_STANDARD 14)
include(FeatureSummary) include(FeatureSummary)
find_package(ECM 5.79.0 NO_MODULE) find_package(ECM 5.93.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") 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) feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -15,12 +13,14 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs) include(KDEInstallDirs)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings) include(KDECMakeSettings)
include(KDEGitCommitHooks)
include(CheckIncludeFiles) include(CheckIncludeFiles)
include(FindPkgConfig)
set(REQUIRED_QT_VERSION 5.14.0) set(REQUIRED_QT_VERSION 5.15.2)
find_package(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) find_package(Qt${QT_MAJOR_VERSION}Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF5Archive) find_package(KF5Archive)
set_package_properties(KF5Archive PROPERTIES set_package_properties(KF5Archive PROPERTIES
@ -32,17 +32,20 @@ set_package_properties(KF5Archive PROPERTIES
# this available in PATH # this available in PATH
set(BUILD_EPS_PLUGIN FALSE) set(BUILD_EPS_PLUGIN FALSE)
if (UNIX) if (UNIX)
find_package(Qt5PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE) find_package(Qt${QT_MAJOR_VERSION}PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
set_package_properties(Qt5PrintSupport PROPERTIES set_package_properties(Qt${QT_MAJOR_VERSION}PrintSupport PROPERTIES
PURPOSE "Required for the QImage plugin for EPS images" PURPOSE "Required for the QImage plugin for EPS images"
TYPE OPTIONAL TYPE OPTIONAL
) )
if (Qt5PrintSupport_FOUND) if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
set(BUILD_EPS_PLUGIN TRUE) set(BUILD_EPS_PLUGIN TRUE)
endif() endif()
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 set_package_properties(OpenEXR PROPERTIES
TYPE OPTIONAL TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for OpenEXR images" PURPOSE "Required for the QImage plugin for OpenEXR images"
@ -54,11 +57,21 @@ set_package_properties(libavif PROPERTIES
PURPOSE "Required for the QImage plugin for AVIF images" PURPOSE "Required for the QImage plugin for AVIF images"
) )
add_definitions(-DQT_NO_FOREACH) option(KIMAGEFORMATS_HEIF "Enable plugin for HEIF format" OFF)
# 050d00 (5.13) triggers a BIC in qimageiohandler.h, in Qt 5.13, so do not enable that until we can require 5.14 if(KIMAGEFORMATS_HEIF)
# https://codereview.qt-project.org/c/qt/qtbase/+/279215 pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.10.0)
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050e00) endif()
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054B00) add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/HEIC images")
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) add_subdirectory(src)
if (BUILD_TESTING) if (BUILD_TESTING)
add_subdirectory(autotests) add_subdirectory(autotests)
@ -66,3 +79,5 @@ if (BUILD_TESTING)
endif() endif()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)

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) - AV1 Image File Format (AVIF)
- Encapsulated PostScript (eps) - Encapsulated PostScript (eps)
- JPEG XL (jxl)
- Personal Computer Exchange (pcx) - Personal Computer Exchange (pcx)
- SGI images (rgb, rgba, sgi, bw) - SGI images (rgb, rgba, sgi, bw)
- Softimage PIC (pic) - Softimage PIC (pic)

View File

@ -15,7 +15,7 @@ macro(kimageformats_read_tests)
if (NOT TARGET readtest) if (NOT TARGET readtest)
add_executable(readtest readtest.cpp) add_executable(readtest readtest.cpp)
target_link_libraries(readtest Qt5::Gui) target_link_libraries(readtest Qt${QT_MAJOR_VERSION}::Gui)
target_compile_definitions(readtest target_compile_definitions(readtest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read") PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
ecm_mark_as_test(readtest) ecm_mark_as_test(readtest)
@ -30,23 +30,35 @@ macro(kimageformats_read_tests)
endmacro() endmacro()
macro(kimageformats_write_tests) macro(kimageformats_write_tests)
cmake_parse_arguments(KIF_RT "" "FUZZ" "" ${ARGN})
set(_fuzzarg)
if (KIF_RT_FUZZ)
set(_fuzzarg -f ${KIF_RT_FUZZ})
endif()
if (NOT TARGET writetest) if (NOT TARGET writetest)
add_executable(writetest writetest.cpp) add_executable(writetest writetest.cpp)
target_link_libraries(writetest Qt5::Gui) target_link_libraries(writetest Qt${QT_MAJOR_VERSION}::Gui)
target_compile_definitions(writetest target_compile_definitions(writetest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write") PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write")
ecm_mark_as_test(writetest) ecm_mark_as_test(writetest)
endif() endif()
foreach(_testname ${ARGN}) foreach(_testname ${KIF_RT_UNPARSED_ARGUMENTS})
string(REGEX MATCH "-lossless$" _is_lossless "${_testname}") string(REGEX MATCH "-lossless$" _is_lossless "${_testname}")
string(REGEX MATCH "-nodatacheck" _is_no_data_check "${_testname}")
unset(lossless_arg) unset(lossless_arg)
unset(no_data_check_arg)
if (_is_lossless) if (_is_lossless)
set(lossless_arg "--lossless") set(lossless_arg "--lossless")
string(REGEX REPLACE "-lossless$" "" _testname "${_testname}") string(REGEX REPLACE "-lossless$" "" _testname "${_testname}")
endif() endif()
if (_is_no_data_check)
set(no_data_check_arg "--no-data-check")
string(REGEX REPLACE "-nodatacheck$" "" _testname "${_testname}")
endif()
add_test( add_test(
NAME kimageformats-write-${_testname} NAME kimageformats-write-${_testname}
COMMAND writetest ${lossless_arg} ${_testname} COMMAND writetest ${lossless_arg} ${no_data_check_arg} ${_fuzzarg} ${_testname}
) )
endforeach(_testname) endforeach(_testname)
endmacro() endmacro()
@ -74,6 +86,29 @@ if (TARGET avif)
kimageformats_read_tests( kimageformats_read_tests(
avif avif
) )
# because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference.
kimageformats_write_tests(FUZZ 1
avif-nodatacheck-lossless
)
endif()
if (LibHeif_FOUND)
kimageformats_read_tests(
heif
)
# because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference.
kimageformats_write_tests(FUZZ 1
heif-nodatacheck-lossless
)
endif()
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
kimageformats_read_tests(
jxl
)
kimageformats_write_tests(
jxl-nodatacheck-lossless
)
endif() endif()
# Allow some fuzziness when reading this formats, to allow for # Allow some fuzziness when reading this formats, to allow for
@ -105,19 +140,19 @@ if (OpenEXR_FOUND)
# FIXME: OpenEXR tests # FIXME: OpenEXR tests
endif() 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) if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
message(STATUS "Qt5Test not found, some autotests will not be built.") message(STATUS "Qt${QT_MAJOR_VERSION}Test not found, some autotests will not be built.")
return() return()
endif() endif()
add_executable(pictest pictest.cpp) 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) ecm_mark_as_test(pictest)
add_test(NAME kimageformats-pic COMMAND pictest) add_test(NAME kimageformats-pic COMMAND pictest)
add_executable(anitest anitest.cpp) 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) ecm_mark_as_test(anitest)
add_test(NAME kimageformats-ani COMMAND anitest) add_test(NAME kimageformats-ani COMMAND anitest)

37
autotests/fuzzyeq.cpp Normal file
View File

@ -0,0 +1,37 @@
/*
SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kdemail.net>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
template<class Trait>
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
{
Q_ASSERT(im1.format() == im2.format());
Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64);
const int height = im1.height();
const int width = im1.width();
for (int i = 0; i < height; ++i) {
const Trait *line1 = reinterpret_cast<const Trait *>(im1.scanLine(i));
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) {
return false;
}
} else {
if (line2[j] - line1[j] > fuzziness) {
return false;
}
}
}
}
return true;
}
// allow each byte to be different by up to 1, to allow for rounding errors
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
{
return (im1.depth() == 64) ? fuzzyeq<quint16>(im1, im2, fuzziness) : fuzzyeq<quint8>(im1, im2, fuzziness);
}

View File

@ -35,61 +35,27 @@ private:
QTest::addColumn<QImage::Format>("pngformat"); QTest::addColumn<QImage::Format>("pngformat");
QTest::addColumn<bool>("compress"); QTest::addColumn<bool>("compress");
QTest::newRow("4x4 no alpha RLE") QTest::newRow("4x4 no alpha RLE") << QFINDTESTDATA("pic/4x4-simple-color.pic") << QFINDTESTDATA("pic/4x4-simple-color.png") << QString() << false
<< QFINDTESTDATA("pic/4x4-simple-color.pic") << QImage::Format_RGB32 << true;
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QString()
<< false
<< QImage::Format_RGB32
<< true;
QTest::newRow("4x4 no alpha raw") QTest::newRow("4x4 no alpha raw") << QFINDTESTDATA("pic/4x4-simple-color-uncompressed.pic") << QFINDTESTDATA("pic/4x4-simple-color.png") << QString()
<< QFINDTESTDATA("pic/4x4-simple-color-uncompressed.pic") << false << QImage::Format_RGB32 << false;
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QString()
<< false
<< QImage::Format_RGB32
<< false;
QTest::newRow("Short comment") QTest::newRow("Short comment") << QFINDTESTDATA("pic/short-comment.pic") << QFINDTESTDATA("pic/4x4-simple-color.png")
<< QFINDTESTDATA("pic/short-comment.pic") << QStringLiteral("Test comment value") << false << QImage::Format_RGB32 << true;
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QStringLiteral("Test comment value")
<< false
<< QImage::Format_RGB32
<< true;
QTest::newRow("Long comment") QTest::newRow("Long comment") << QFINDTESTDATA("pic/long-comment.pic") << QFINDTESTDATA("pic/4x4-simple-color.png")
<< QFINDTESTDATA("pic/long-comment.pic") << QStringLiteral("Test comment value that goes right up to the end of the comment field and has no") << false
<< QFINDTESTDATA("pic/4x4-simple-color.png") << QImage::Format_RGB32 << true;
<< QStringLiteral("Test comment value that goes right up to the end of the comment field and has no")
<< false
<< QImage::Format_RGB32
<< true;
QTest::newRow("Long run-lengths") QTest::newRow("Long run-lengths") << QFINDTESTDATA("pic/long-runs.pic") << QFINDTESTDATA("pic/long-runs.png") << QString() << false
<< QFINDTESTDATA("pic/long-runs.pic") << QImage::Format_RGB32 << true;
<< QFINDTESTDATA("pic/long-runs.png")
<< QString()
<< false
<< QImage::Format_RGB32
<< true;
QTest::newRow("4x4 with alpha RLE") QTest::newRow("4x4 with alpha RLE") << QFINDTESTDATA("pic/4x4-alpha.pic") << QFINDTESTDATA("pic/4x4-alpha.png") << QString() << true
<< QFINDTESTDATA("pic/4x4-alpha.pic") << QImage::Format_ARGB32 << true;
<< QFINDTESTDATA("pic/4x4-alpha.png")
<< QString()
<< true
<< QImage::Format_ARGB32
<< true;
QTest::newRow("4x4 with alpha raw") QTest::newRow("4x4 with alpha raw") << QFINDTESTDATA("pic/4x4-alpha-uncompressed.pic") << QFINDTESTDATA("pic/4x4-alpha.png") << QString() << true
<< QFINDTESTDATA("pic/4x4-alpha-uncompressed.pic") << QImage::Format_ARGB32 << false;
<< QFINDTESTDATA("pic/4x4-alpha.png")
<< QString()
<< true
<< QImage::Format_ARGB32
<< false;
} }
private Q_SLOTS: private Q_SLOTS:
@ -106,13 +72,8 @@ private Q_SLOTS:
// so there is no actual data loss in converting to RGB16. // so there is no actual data loss in converting to RGB16.
// This just tests that the pic plugin can deal with different // This just tests that the pic plugin can deal with different
// input formats. // input formats.
QTest::newRow("altered format") QTest::newRow("altered format") << QFINDTESTDATA("pic/4x4-simple-color.pic") << QFINDTESTDATA("pic/4x4-simple-color.png") << QString() << false
<< QFINDTESTDATA("pic/4x4-simple-color.pic") << QImage::Format_RGB16 << true;
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QString()
<< false
<< QImage::Format_RGB16
<< true;
} }
void testRead_data() void testRead_data()
@ -148,17 +109,12 @@ private Q_SLOTS:
imgWriter.write(pngImage); imgWriter.write(pngImage);
if (expData != picData) { if (expData != picData) {
QString fileNameBase = QUuid::createUuid().toString() QString fileNameBase = QUuid::createUuid().toString().remove(QLatin1Char('{')).remove(QLatin1Char('}'));
.remove(QLatin1Char('{'))
.remove(QLatin1Char('}'));
QFile dumpFile(fileNameBase + QStringLiteral(".pic")); QFile dumpFile(fileNameBase + QStringLiteral(".pic"));
QVERIFY2(dumpFile.open(QIODevice::WriteOnly), qPrintable(dumpFile.errorString())); QVERIFY2(dumpFile.open(QIODevice::WriteOnly), qPrintable(dumpFile.errorString()));
dumpFile.write(picData); dumpFile.write(picData);
QString msg = QStringLiteral("Written data (") QString msg =
+ dumpFile.fileName() QStringLiteral("Written data (") + dumpFile.fileName() + QStringLiteral(") differed from expected data (") + picfile + QLatin1Char(')');
+ QStringLiteral(") differed from expected data (")
+ picfile
+ QLatin1Char(')');
QFAIL(qPrintable(msg)); QFAIL(qPrintable(msg));
} }
} }
@ -182,29 +138,20 @@ private Q_SLOTS:
QCOMPARE(inputImage.width(), expImage.width()); QCOMPARE(inputImage.width(), expImage.width());
QCOMPARE(inputImage.height(), expImage.height()); QCOMPARE(inputImage.height(), expImage.height());
QCOMPARE(inputImage.hasAlphaChannel(), alpha); QCOMPARE(inputImage.hasAlphaChannel(), alpha);
QCOMPARE(inputImage.format(), alpha ? QImage::Format_ARGB32 QCOMPARE(inputImage.format(), alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
: QImage::Format_RGB32);
expImage = expImage.convertToFormat(pngformat); expImage = expImage.convertToFormat(pngformat);
expImage = expImage.convertToFormat(alpha ? QImage::Format_ARGB32 expImage = expImage.convertToFormat(alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
: QImage::Format_RGB32);
if (inputImage != expImage) { if (inputImage != expImage) {
QString fileNameBase = QUuid::createUuid().toString() QString fileNameBase = QUuid::createUuid().toString().remove(QLatin1Char('{')).remove(QLatin1Char('}'));
.remove(QLatin1Char('{'))
.remove(QLatin1Char('}'));
QFile picDumpFile(fileNameBase + QStringLiteral("-expected.data")); QFile picDumpFile(fileNameBase + QStringLiteral("-expected.data"));
QVERIFY2(picDumpFile.open(QIODevice::WriteOnly), qPrintable(picDumpFile.errorString())); QVERIFY2(picDumpFile.open(QIODevice::WriteOnly), qPrintable(picDumpFile.errorString()));
picDumpFile.write(reinterpret_cast<const char *>(inputImage.bits()), picDumpFile.write(reinterpret_cast<const char *>(inputImage.bits()), inputImage.sizeInBytes());
inputImage.sizeInBytes());
QFile pngDumpFile(fileNameBase + QStringLiteral("-actual.data")); QFile pngDumpFile(fileNameBase + QStringLiteral("-actual.data"));
QVERIFY2(pngDumpFile.open(QIODevice::WriteOnly), qPrintable(pngDumpFile.errorString())); QVERIFY2(pngDumpFile.open(QIODevice::WriteOnly), qPrintable(pngDumpFile.errorString()));
pngDumpFile.write(reinterpret_cast<const char *>(expImage.bits()), pngDumpFile.write(reinterpret_cast<const char *>(expImage.bits()), expImage.sizeInBytes());
expImage.sizeInBytes()); QString msg = QStringLiteral("Read image (") + picDumpFile.fileName() + QStringLiteral(") differed from expected image (") + pngDumpFile.fileName()
QString msg = QStringLiteral("Read image (") + QLatin1Char(')');
+ picDumpFile.fileName()
+ QStringLiteral(") differed from expected image (")
+ pngDumpFile.fileName()
+ QLatin1Char(')');
QFAIL(qPrintable(msg)); QFAIL(qPrintable(msg));
} }
} }
@ -252,8 +199,7 @@ private Q_SLOTS:
QImageReader inputReader(picfile, "pic"); QImageReader inputReader(picfile, "pic");
QCOMPARE(inputReader.imageFormat(), QCOMPARE(inputReader.imageFormat(), alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
} }
}; };

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

View File

@ -16,57 +16,23 @@
#include "../tests/format-enum.h" #include "../tests/format-enum.h"
#include "fuzzyeq.cpp"
static void writeImageData(const char *name, const QString &filename, const QImage &image) static void writeImageData(const char *name, const QString &filename, const QImage &image)
{ {
QFile file(filename); QFile file(filename);
if (file.open(QIODevice::WriteOnly)) { if (file.open(QIODevice::WriteOnly)) {
qint64 written = file.write(reinterpret_cast<const char *>(image.bits()), image.sizeInBytes()); qint64 written = file.write(reinterpret_cast<const char *>(image.bits()), image.sizeInBytes());
if (written == image.sizeInBytes()) { if (written == image.sizeInBytes()) {
QTextStream(stdout) << " " << name QTextStream(stdout) << " " << name << " written to " << filename << "\n";
<< " written to " << filename << "\n";
} else { } else {
QTextStream(stdout) << " could not write " << name QTextStream(stdout) << " could not write " << name << " to " << filename << ":" << file.errorString() << "\n";
<< " to " << filename << ":"
<< file.errorString() << "\n";
} }
} else { } else {
QTextStream(stdout) << " could not open " QTextStream(stdout) << " could not open " << filename << ":" << file.errorString() << "\n";
<< filename << ":"
<< file.errorString() << "\n";
} }
} }
template<class Trait>
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
{
Q_ASSERT(im1.format() == im2.format());
Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64);
const int height = im1.height();
const int width = im1.width();
for (int i = 0; i < height; ++i) {
const Trait *line1 = reinterpret_cast<const Trait*>(im1.scanLine(i));
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)
return false;
} else {
if (line2[j] - line1[j] > fuzziness)
return false;
}
}
}
return true;
}
// allow each byte to be different by up to 1, to allow for rounding errors
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
{
return (im1.depth() == 64) ? fuzzyeq<quint16>(im1, im2, fuzziness)
: fuzzyeq<quint8>(im1, im2, fuzziness);
}
// Returns the original format if we support, or returns // Returns the original format if we support, or returns
// format which we preferred to use for `fuzzyeq()`. // format which we preferred to use for `fuzzyeq()`.
// We do only support formats with 8-bits/16-bits pre pixel. // We do only support formats with 8-bits/16-bits pre pixel.
@ -84,7 +50,7 @@ static QImage::Format preferredFormat(QImage::Format fmt)
} }
} }
int main(int argc, char ** argv) int main(int argc, char **argv)
{ {
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR)); QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
@ -97,10 +63,9 @@ int main(int argc, char ** argv)
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test")); parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test"));
QCommandLineOption fuzz( QCommandLineOption fuzz(QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"),
QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"), QStringLiteral("Allow for some deviation in ARGB data."),
QStringLiteral("Allow for some deviation in ARGB data."), QStringLiteral("max"));
QStringLiteral("max"));
parser.addOption(fuzz); parser.addOption(fuzz);
parser.process(app); parser.process(app);
@ -136,13 +101,14 @@ int main(int argc, char ** argv)
int failed = 0; int failed = 0;
QTextStream(stdout) << "********* " QTextStream(stdout) << "********* "
<< "Starting basic read tests for " << "Starting basic read tests for " << suffix << " images *********\n";
<< suffix << " images *********\n";
const QList<QByteArray> formats = QImageReader::supportedImageFormats(); const QList<QByteArray> formats = QImageReader::supportedImageFormats();
QStringList formatStrings; QStringList formatStrings;
formatStrings.reserve(formats.size()); formatStrings.reserve(formats.size());
std::transform(formats.begin(), formats.end(), std::back_inserter(formatStrings), [](const QByteArray &format) { return QString(format); }); std::transform(formats.begin(), formats.end(), std::back_inserter(formatStrings), [](const QByteArray &format) {
return QString(format);
});
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n"; QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
const QFileInfoList lstImgDir = imgdir.entryInfoList(); const QFileInfoList lstImgDir = imgdir.entryInfoList();
@ -159,40 +125,27 @@ int main(int argc, char ** argv)
QImage expImage; QImage expImage;
if (!expReader.read(&expImage)) { if (!expReader.read(&expImage)) {
QTextStream(stdout) << "ERROR: " << fi.fileName() QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
<< ": could not load " << expfilename
<< ": " << expReader.errorString()
<< "\n";
++failed; ++failed;
continue; continue;
} }
if (!inputReader.canRead()) { if (!inputReader.canRead()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n";
<< ": failed can read: "
<< inputReader.errorString()
<< "\n";
++failed; ++failed;
continue; continue;
} }
if (!inputReader.read(&inputImage)) { if (!inputReader.read(&inputImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
<< ": failed to load: "
<< inputReader.errorString()
<< "\n";
++failed; ++failed;
continue; continue;
} }
if (expImage.width() != inputImage.width()) { if (expImage.width() != inputImage.width()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
<< ": width was " << inputImage.width() << expImage.width() << "\n";
<< " but " << expfilename << " width was "
<< expImage.width() << "\n";
++failed; ++failed;
} else if (expImage.height() != inputImage.height()) { } else if (expImage.height() != inputImage.height()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() QTextStream(stdout) << "FAIL : " << fi.fileName() << ": height was " << inputImage.height() << " but " << expfilename << " height was "
<< ": height was " << inputImage.height() << expImage.height() << "\n";
<< " but " << expfilename << " height was "
<< expImage.height() << "\n";
++failed; ++failed;
} else { } else {
QImage::Format inputFormat = preferredFormat(inputImage.format()); QImage::Format inputFormat = preferredFormat(inputImage.format());
@ -200,42 +153,30 @@ int main(int argc, char ** argv)
QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32; QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32;
if (inputImage.format() != cmpFormat) { if (inputImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format())
<< ": converting " << fi.fileName() << " to " << formatToString(cmpFormat) << '\n';
<< " from " << formatToString(inputImage.format())
<< " to " << formatToString(cmpFormat) << '\n';
inputImage = inputImage.convertToFormat(cmpFormat); inputImage = inputImage.convertToFormat(cmpFormat);
} }
if (expImage.format() != cmpFormat) { if (expImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) << " to "
<< ": converting " << expfilename << formatToString(cmpFormat) << '\n';
<< " from " << formatToString(expImage.format())
<< " to " << formatToString(cmpFormat) << '\n';
expImage = expImage.convertToFormat(cmpFormat); expImage = expImage.convertToFormat(cmpFormat);
} }
if (fuzzyeq(inputImage, expImage, fuzziness)) { if (fuzzyeq(inputImage, expImage, fuzziness)) {
QTextStream(stdout) << "PASS : " << fi.fileName() << "\n"; QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";
++passed; ++passed;
} else { } else {
QTextStream(stdout) << "FAIL : " << fi.fileName() QTextStream(stdout) << "FAIL : " << fi.fileName() << ": differs from " << expfilename << "\n";
<< ": differs from " << expfilename << "\n"; writeImageData("expected data", fi.fileName() + QLatin1String("-expected.data"), expImage);
writeImageData("expected data", writeImageData("actual data", fi.fileName() + QLatin1String("-actual.data"), inputImage);
fi.fileName() + QLatin1String("-expected.data"),
expImage);
writeImageData("actual data",
fi.fileName() + QLatin1String("-actual.data"),
inputImage);
++failed; ++failed;
} }
} }
} }
QTextStream(stdout) << "Totals: " QTextStream(stdout) << "Totals: " << passed << " passed, " << failed << " failed\n";
<< passed << " passed, "
<< failed << " failed\n";
QTextStream(stdout) << "********* " QTextStream(stdout) << "********* "
<< "Finished basic read tests for " << "Finished basic read tests for " << suffix << " images *********\n";
<< suffix << " images *********\n";
return failed == 0 ? 0 : 1; return failed == 0 ? 0 : 1;
} }

View File

@ -16,7 +16,9 @@
#include <QImageWriter> #include <QImageWriter>
#include <QTextStream> #include <QTextStream>
int main(int argc, char ** argv) #include "fuzzyeq.cpp"
int main(int argc, char **argv)
{ {
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR)); QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
@ -29,10 +31,15 @@ int main(int argc, char ** argv)
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test.")); parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test."));
QCommandLineOption lossless( QCommandLineOption lossless(QStringList() << QStringLiteral("l") << QStringLiteral("lossless"),
QStringList() << QStringLiteral("l") << QStringLiteral("lossless"), QStringLiteral("Check that reading back the data gives the same image."));
QStringLiteral("Check that reading back the data gives the same image.")); QCommandLineOption ignoreDataCheck({QStringLiteral("no-data-check")}, QStringLiteral("Don't check that write data is exactly the same."));
QCommandLineOption fuzz(QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"),
QStringLiteral("Allow for some deviation in ARGB data."),
QStringLiteral("max"));
parser.addOption(lossless); parser.addOption(lossless);
parser.addOption(ignoreDataCheck);
parser.addOption(fuzz);
parser.process(app); parser.process(app);
@ -45,78 +52,91 @@ int main(int argc, char ** argv)
parser.showHelp(1); parser.showHelp(1);
} }
uchar fuzziness = 0;
if (parser.isSet(fuzz)) {
bool ok;
uint fuzzarg = parser.value(fuzz).toUInt(&ok);
if (!ok || fuzzarg > 255) {
QTextStream(stderr) << "Error: max fuzz argument must be a number between 0 and 255\n";
parser.showHelp(1);
}
fuzziness = uchar(fuzzarg);
}
QString suffix = args.at(0); QString suffix = args.at(0);
QByteArray format = suffix.toLatin1(); QByteArray format = suffix.toLatin1();
QDir imgdir(QStringLiteral(IMAGEDIR)); QDir imgdir(QStringLiteral(IMAGEDIR));
imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix)); if (parser.isSet(ignoreDataCheck)) {
imgdir.setNameFilters({QLatin1String("*.png")});
} else {
imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix));
}
imgdir.setFilter(QDir::Files); imgdir.setFilter(QDir::Files);
int passed = 0; int passed = 0;
int failed = 0; int failed = 0;
QTextStream(stdout) << "********* " QTextStream(stdout) << "********* "
<< "Starting basic write tests for " << "Starting basic write tests for " << suffix << " images *********\n";
<< suffix << " images *********\n";
const QFileInfoList lstImgDir = imgdir.entryInfoList(); const QFileInfoList lstImgDir = imgdir.entryInfoList();
for (const QFileInfo &fi : lstImgDir) { for (const QFileInfo &fi : lstImgDir) {
int suffixPos = fi.filePath().count() - suffix.count(); QString pngfile;
QString pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png")); if (parser.isSet(ignoreDataCheck)) {
pngfile = fi.filePath();
} else {
int suffixPos = fi.filePath().count() - suffix.count();
pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
}
QString pngfilename = QFileInfo(pngfile).fileName(); QString pngfilename = QFileInfo(pngfile).fileName();
QImageReader pngReader(pngfile, "png"); QImageReader pngReader(pngfile, "png");
QImage pngImage; QImage pngImage;
if (!pngReader.read(&pngImage)) { if (!pngReader.read(&pngImage)) {
QTextStream(stdout) << "ERROR: " << fi.fileName() QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << pngfilename << ": " << pngReader.errorString() << "\n";
<< ": could not load " << pngfilename
<< ": " << pngReader.errorString()
<< "\n";
++failed; ++failed;
continue; continue;
} }
QFile expFile(fi.filePath());
if (!expFile.open(QIODevice::ReadOnly)) {
QTextStream(stdout) << "ERROR: " << fi.fileName()
<< ": could not open " << fi.fileName()
<< ": " << expFile.errorString()
<< "\n";
++failed;
continue;
}
QByteArray expData = expFile.readAll();
if (expData.isEmpty()) {
// check if there was actually anything to read
expFile.reset();
char buf[1];
qint64 result = expFile.read(buf, 1);
if (result < 0) {
QTextStream(stdout) << "ERROR: " << fi.fileName()
<< ": could not load " << fi.fileName()
<< ": " << expFile.errorString()
<< "\n";
++failed;
continue;
}
}
QByteArray writtenData; QByteArray writtenData;
{ {
QBuffer buffer(&writtenData); QBuffer buffer(&writtenData);
QImageWriter imgWriter(&buffer, format.constData()); QImageWriter imgWriter(&buffer, format.constData());
if (parser.isSet(lossless)) {
imgWriter.setQuality(100);
}
if (!imgWriter.write(pngImage)) { if (!imgWriter.write(pngImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to write image data\n";
<< ": failed to write image data\n";
++failed; ++failed;
continue; continue;
} }
} }
if (expData != writtenData) { if (!parser.isSet(ignoreDataCheck)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() QFile expFile(fi.filePath());
<< ": written data differs from " << fi.fileName() << "\n"; if (!expFile.open(QIODevice::ReadOnly)) {
++failed; QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not open " << fi.fileName() << ": " << expFile.errorString() << "\n";
continue; ++failed;
continue;
}
QByteArray expData = expFile.readAll();
if (expData.isEmpty()) {
// check if there was actually anything to read
expFile.reset();
char buf[1];
qint64 result = expFile.read(buf, 1);
if (result < 0) {
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << fi.fileName() << ": " << expFile.errorString() << "\n";
++failed;
continue;
}
}
if (expData != writtenData) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": written data differs from " << fi.fileName() << "\n";
++failed;
continue;
}
} }
QImage reReadImage; QImage reReadImage;
@ -124,8 +144,7 @@ int main(int argc, char ** argv)
QBuffer buffer(&writtenData); QBuffer buffer(&writtenData);
QImageReader imgReader(&buffer, format.constData()); QImageReader imgReader(&buffer, format.constData());
if (!imgReader.read(&reReadImage)) { if (!imgReader.read(&reReadImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() QTextStream(stdout) << "FAIL : " << fi.fileName() << ": could not read back the written data\n";
<< ": could not read back the written data\n";
++failed; ++failed;
continue; continue;
} }
@ -133,9 +152,18 @@ int main(int argc, char ** argv)
} }
if (parser.isSet(lossless)) { if (parser.isSet(lossless)) {
if (pngImage != reReadImage) { if (!fuzzyeq(pngImage, reReadImage, fuzziness)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() QTextStream(stdout) << "FAIL : " << fi.fileName() << ": re-reading the data resulted in a different image\n";
<< ": re-reading the data resulted in a different image\n"; if (pngImage.size() == reReadImage.size()) {
for (int i = 0; i < pngImage.width(); ++i) {
for (int j = 0; j < pngImage.height(); ++j) {
if (pngImage.pixel(i, j) != reReadImage.pixel(i, j)) {
QTextStream(stdout) << "Pixel is different " << i << ',' << j << ' ' << pngImage.pixel(i, j) << ' ' << reReadImage.pixel(i, j)
<< '\n';
}
}
}
}
++failed; ++failed;
continue; continue;
} }
@ -145,12 +173,9 @@ int main(int argc, char ** argv)
++passed; ++passed;
} }
QTextStream(stdout) << "Totals: " QTextStream(stdout) << "Totals: " << passed << " passed, " << failed << " failed\n";
<< passed << " passed, "
<< failed << " failed\n";
QTextStream(stdout) << "********* " QTextStream(stdout) << "********* "
<< "Finished basic write tests for " << "Finished basic write tests for " << suffix << " images *********\n";
<< suffix << " images *********\n";
return failed == 0 ? 0 : 1; return failed == 0 ? 0 : 1;
} }

View File

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

View File

@ -4,116 +4,136 @@
function(kimageformats_add_plugin plugin) function(kimageformats_add_plugin plugin)
set(options) set(options)
set(oneValueArgs JSON)
set(multiValueArgs SOURCES) set(multiValueArgs SOURCES)
cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT KIF_ADD_PLUGIN_SOURCES) if(NOT KIF_ADD_PLUGIN_SOURCES)
message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter") message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter")
endif() 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}) 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") 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) install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
endfunction() endfunction()
################################## ##################################
kimageformats_add_plugin(kimg_ani JSON "ani.json" SOURCES ani.cpp) kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
################################## ##################################
if (TARGET avif) 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") 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() 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 (BUILD_EPS_PLUGIN)
if (Qt5PrintSupport_FOUND) if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
kimageformats_add_plugin(kimg_eps JSON "eps.json" SOURCES eps.cpp) kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
target_link_libraries(kimg_eps Qt5::PrintSupport) target_link_libraries(kimg_eps Qt${QT_MAJOR_VERSION}::PrintSupport)
install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif() endif()
endif() endif()
################################## ##################################
# need this for Qt's version of the plugin # 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) if(OpenEXR_FOUND)
kimageformats_add_plugin(kimg_exr JSON "exr.json" SOURCES exr.cpp) kimageformats_add_plugin(kimg_exr SOURCES exr.cpp)
target_link_libraries(kimg_exr OpenEXR::IlmImf) 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) 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() endif()
################################## ##################################
kimageformats_add_plugin(kimg_hdr JSON "hdr.json" SOURCES hdr.cpp) kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
################################## ##################################
kimageformats_add_plugin(kimg_pcx JSON "pcx.json" SOURCES pcx.cpp) if (LibHeif_FOUND)
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) 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_KSERVICESDIR}/qimageioplugins/)
endif()
################################## ##################################
kimageformats_add_plugin(kimg_pic JSON "pic.json" SOURCES pic.cpp) if (LibJXL_FOUND AND LibJXLThreads_FOUND)
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) 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_psd JSON "psd.json" SOURCES psd.cpp) kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
################################## ##################################
kimageformats_add_plugin(kimg_ras JSON "ras.json" SOURCES ras.cpp) kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
################################## ##################################
kimageformats_add_plugin(kimg_rgb JSON "rgb.json" SOURCES rgb.cpp) kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
################################## ##################################
kimageformats_add_plugin(kimg_tga JSON "tga.json" SOURCES tga.cpp) kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
################################## ##################################
kimageformats_add_plugin(kimg_xcf JSON "xcf.json" SOURCES xcf.cpp) kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/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) 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) 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) 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() endif()

View File

@ -9,12 +9,13 @@
#include <QDebug> #include <QDebug>
#include <QImage> #include <QImage>
#include <QScopeGuard> #include <QScopeGuard>
#include <QtEndian>
#include <QVariant> #include <QVariant>
#include <QtEndian>
#include <cstring>
namespace namespace
{ {
struct ChunkHeader { struct ChunkHeader {
char magic[4]; char magic[4];
quint32_le size; quint32_le size;
@ -33,16 +34,16 @@ struct AniHeader {
}; };
struct CurHeader { struct CurHeader {
quint16_le wReserved; // always 0 quint16_le wReserved; // always 0
quint16_le wResID; // always 2 quint16_le wResID; // always 2
quint16_le wNumImages; quint16_le wNumImages;
}; };
struct CursorDirEntry { struct CursorDirEntry {
quint8 bWidth; quint8 bWidth;
quint8 bHeight; quint8 bHeight;
quint8 bColorCount; quint8 bColorCount;
quint8 bReserved; // always 0 quint8 bReserved; // always 0
quint16_le wHotspotX; quint16_le wHotspotX;
quint16_le wHotspotY; quint16_le wHotspotY;
quint32_le dwBytesInImage; quint32_le dwBytesInImage;
@ -64,8 +65,7 @@ bool ANIHandler::canRead() const
const QByteArray nextFrame = device()->peek(sizeof(ChunkHeader)); const QByteArray nextFrame = device()->peek(sizeof(ChunkHeader));
if (nextFrame.size() == sizeof(ChunkHeader)) { if (nextFrame.size() == sizeof(ChunkHeader)) {
const auto *header = reinterpret_cast<const ChunkHeader *>(nextFrame.data()); const auto *header = reinterpret_cast<const ChunkHeader *>(nextFrame.data());
if (qstrncmp(header->magic, "icon", sizeof(header->magic)) == 0 if (qstrncmp(header->magic, "icon", sizeof(header->magic)) == 0 && header->size > 0) {
&& header->size > 0) {
setFormat("ani"); setFormat("ani");
return true; return true;
} }
@ -377,8 +377,7 @@ bool ANIHandler::ensureScanned() const
mutableThis->m_displayRate = aniHeader->iDispRate; mutableThis->m_displayRate = aniHeader->iDispRate;
} else if (chunkId == "rate" || chunkId == "seq ") { } else if (chunkId == "rate" || chunkId == "seq ") {
const QByteArray data = device()->read(chunkSize); const QByteArray data = device()->read(chunkSize);
if (static_cast<quint32_le>(data.size()) != chunkSize if (static_cast<quint32_le>(data.size()) != chunkSize || data.size() % sizeof(quint32_le) != 0) {
|| data.size() % sizeof(quint32_le) != 0) {
return false; return false;
} }
@ -407,8 +406,8 @@ bool ANIHandler::ensureScanned() const
mutableThis->m_imageSequence = list; mutableThis->m_imageSequence = list;
} }
} }
// IART and INAM are technically inside LIST->INFO but "INFO" is supposedly optional // IART and INAM are technically inside LIST->INFO but "INFO" is supposedly optional
// so just handle those two attributes wherever we encounter them // so just handle those two attributes wherever we encounter them
} else if (chunkId == "INAM" || chunkId == "IART") { } else if (chunkId == "INAM" || chunkId == "IART") {
const QByteArray value = device()->read(chunkSize); const QByteArray value = device()->read(chunkSize);
@ -422,7 +421,7 @@ bool ANIHandler::ensureScanned() const
} }
// FIXME encoding // FIXME encoding
const QString stringValue = QString::fromLocal8Bit(value); const QString stringValue = QString::fromLocal8Bit(value.constData(), std::strlen(value.constData()));
if (chunkId == "INAM") { if (chunkId == "INAM") {
mutableThis->m_name = stringValue; mutableThis->m_name = stringValue;
} else if (chunkId == "IART") { } else if (chunkId == "IART") {
@ -455,8 +454,7 @@ bool ANIHandler::ensureScanned() const
const QByteArray curHeaderData = device()->read(sizeof(CurHeader)); const QByteArray curHeaderData = device()->read(sizeof(CurHeader));
const QByteArray cursorDirEntryData = device()->read(sizeof(CursorDirEntry)); const QByteArray cursorDirEntryData = device()->read(sizeof(CursorDirEntry));
if (curHeaderData.length() == sizeof(CurHeader) if (curHeaderData.length() == sizeof(CurHeader) && cursorDirEntryData.length() == sizeof(CursorDirEntry)) {
&& cursorDirEntryData.length() == sizeof(CursorDirEntry)) {
auto *cursorDirEntry = reinterpret_cast<const CursorDirEntry *>(cursorDirEntryData.data()); auto *cursorDirEntry = reinterpret_cast<const CursorDirEntry *>(cursorDirEntryData.data());
mutableThis->m_size = QSize(cursorDirEntry->bWidth, cursorDirEntry->bHeight); mutableThis->m_size = QSize(cursorDirEntry->bWidth, cursorDirEntry->bHeight);
} }
@ -508,7 +506,7 @@ bool ANIHandler::ensureScanned() const
return false; 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"); qWarning("ANIHandler: number of actual frames does not match 'nFrames' in anih");
return false; return false;
} }

View File

@ -53,7 +53,6 @@ private:
QString m_name; QString m_name;
QString m_artist; QString m_artist;
QSize m_size; QSize m_size;
}; };
class ANIPlugin : public QImageIOPlugin class ANIPlugin : public QImageIOPlugin

View File

@ -6,22 +6,22 @@
SPDX-License-Identifier: BSD-2-Clause SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <QtGlobal>
#include <QThread> #include <QThread>
#include <QtGlobal>
#include <QColorSpace> #include <QColorSpace>
#include "avif_p.h" #include "avif_p.h"
#include <cfloat>
QAVIFHandler::QAVIFHandler()
QAVIFHandler::QAVIFHandler() : : m_parseState(ParseAvifNotParsed)
m_parseState(ParseAvifNotParsed), , m_quality(52)
m_quality(52), , m_container_width(0)
m_container_width(0), , m_container_height(0)
m_container_height(0), , m_rawAvifData(AVIF_DATA_EMPTY)
m_rawAvifData(AVIF_DATA_EMPTY), , m_decoder(nullptr)
m_decoder(nullptr), , m_must_jump_to_next_image(false)
m_must_jump_to_next_image(false)
{ {
} }
@ -56,7 +56,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
} }
avifROData input; avifROData input;
input.data = (const uint8_t *) header.constData(); input.data = (const uint8_t *)header.constData();
input.size = header.size(); input.size = header.size();
if (avifPeekCompatibleFileType(&input)) { if (avifPeekCompatibleFileType(&input)) {
@ -87,7 +87,7 @@ bool QAVIFHandler::ensureDecoder()
m_rawData = device()->readAll(); m_rawData = device()->readAll();
m_rawAvifData.data = (const uint8_t *) m_rawData.constData(); m_rawAvifData.data = (const uint8_t *)m_rawData.constData();
m_rawAvifData.size = m_rawData.size(); m_rawAvifData.size = m_rawData.size();
if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) { if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
@ -95,14 +95,21 @@ bool QAVIFHandler::ensureDecoder()
return false; return false;
} }
m_decoder = avifDecoderCreate(); 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; avifResult decodeResult;
decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size); decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
if (decodeResult != AVIF_RESULT_OK) { if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: avifDecoderSetIOMemory failed: %s\n", avifResultToString(decodeResult)); qWarning("ERROR: avifDecoderSetIOMemory failed: %s", avifResultToString(decodeResult));
avifDecoderDestroy(m_decoder); avifDecoderDestroy(m_decoder);
m_decoder = nullptr; m_decoder = nullptr;
@ -112,7 +119,7 @@ bool QAVIFHandler::ensureDecoder()
decodeResult = avifDecoderParse(m_decoder); decodeResult = avifDecoderParse(m_decoder);
if (decodeResult != AVIF_RESULT_OK) { if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to parse input: %s\n", avifResultToString(decodeResult)); qWarning("ERROR: Failed to parse input: %s", avifResultToString(decodeResult));
avifDecoderDestroy(m_decoder); avifDecoderDestroy(m_decoder);
m_decoder = nullptr; m_decoder = nullptr;
@ -123,11 +130,10 @@ bool QAVIFHandler::ensureDecoder()
decodeResult = avifDecoderNextImage(m_decoder); decodeResult = avifDecoderNextImage(m_decoder);
if (decodeResult == AVIF_RESULT_OK) { if (decodeResult == AVIF_RESULT_OK) {
m_container_width = m_decoder->image->width; m_container_width = m_decoder->image->width;
m_container_height = m_decoder->image->height; m_container_height = m_decoder->image->height;
if ((m_container_width > 32768) || (m_container_height > 32768)) { if ((m_container_width > 65535) || (m_container_height > 65535)) {
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height); qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
m_parseState = ParseAvifError; m_parseState = ParseAvifError;
return false; return false;
@ -139,6 +145,12 @@ bool QAVIFHandler::ensureDecoder()
return false; 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; m_parseState = ParseAvifSuccess;
if (decode_one_frame()) { if (decode_one_frame()) {
return true; return true;
@ -147,7 +159,7 @@ bool QAVIFHandler::ensureDecoder()
return false; return false;
} }
} else { } else {
qWarning("ERROR: Failed to decode image: %s\n", avifResultToString(decodeResult)); qWarning("ERROR: Failed to decode image: %s", avifResultToString(decodeResult));
} }
avifDecoderDestroy(m_decoder); avifDecoderDestroy(m_decoder);
@ -182,7 +194,7 @@ bool QAVIFHandler::decode_one_frame()
if (loadalpha) { if (loadalpha) {
resultformat = QImage::Format_RGBA8888; resultformat = QImage::Format_RGBA8888;
} else { } else {
resultformat = QImage::Format_RGB888; resultformat = QImage::Format_RGBX8888;
} }
} }
QImage result(m_decoder->image->width, m_decoder->image->height, resultformat); QImage result(m_decoder->image->width, m_decoder->image->height, resultformat);
@ -192,20 +204,22 @@ bool QAVIFHandler::decode_one_frame()
return false; return false;
} }
QColorSpace colorspace;
if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) { 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))); const QByteArray icc_data((const char *)m_decoder->image->icc.data, (int)m_decoder->image->icc.size);
if (! result.colorSpace().isValid()) { colorspace = QColorSpace::fromIccProfile(icc_data);
qWarning("Invalid QColorSpace created from ICC!\n"); if (!colorspace.isValid()) {
qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
} }
} else { } else {
float prim[8]; // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY float prim[8] = {0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f};
// outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY
avifColorPrimariesGetValues(m_decoder->image->colorPrimaries, prim); avifColorPrimariesGetValues(m_decoder->image->colorPrimaries, prim);
QPointF redPoint(prim[0], prim[1]); const QPointF redPoint(QAVIFHandler::CompatibleChromacity(prim[0], prim[1]));
QPointF greenPoint(prim[2], prim[3]); const QPointF greenPoint(QAVIFHandler::CompatibleChromacity(prim[2], prim[3]));
QPointF bluePoint(prim[4], prim[5]); const QPointF bluePoint(QAVIFHandler::CompatibleChromacity(prim[4], prim[5]));
QPointF whitePoint(prim[6], prim[7]); const QPointF whitePoint(QAVIFHandler::CompatibleChromacity(prim[6], prim[7]));
QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom; QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom;
float q_trc_gamma = 0.0f; float q_trc_gamma = 0.0f;
@ -229,38 +243,41 @@ bool QAVIFHandler::decode_one_frame()
case 0: case 0:
case 2: /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */ case 2: /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
case 13: case 13:
q_trc = QColorSpace::TransferFunction::SRgb; q_trc = QColorSpace::TransferFunction::SRgb;
break; break;
default: default:
qWarning("CICP colorPrimaries: %d, transferCharacteristics: %d\nThe colorspace is unsupported by this plug-in yet.", qWarning("CICP colorPrimaries: %d, transferCharacteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
m_decoder->image->colorPrimaries, m_decoder->image->transferCharacteristics); m_decoder->image->colorPrimaries,
m_decoder->image->transferCharacteristics);
q_trc = QColorSpace::TransferFunction::SRgb; q_trc = QColorSpace::TransferFunction::SRgb;
break; break;
} }
if (q_trc != QColorSpace::TransferFunction::Custom) { //we create new colorspace using Qt if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt
switch (m_decoder->image->colorPrimaries) { switch (m_decoder->image->colorPrimaries) {
/* AVIF_COLOR_PRIMARIES_BT709 */ /* AVIF_COLOR_PRIMARIES_BT709 */
case 0: case 0:
case 1: case 1:
case 2: /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */ 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; break;
/* AVIF_COLOR_PRIMARIES_SMPTE432 */ /* AVIF_COLOR_PRIMARIES_SMPTE432 */
case 12: case 12:
result.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma)); colorspace = QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma);
break; break;
default: 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; break;
} }
} }
if (! result.colorSpace().isValid()) { if (!colorspace.isValid()) {
qWarning("Invalid QColorSpace created from NCLX/CICP!\n"); qWarning("AVIF plugin created invalid QColorSpace from NCLX/CICP!");
} }
} }
result.setColorSpace(colorspace);
avifRGBImage rgb; avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, m_decoder->image); avifRGBImageSetDefaults(&rgb, m_decoder->image);
@ -269,20 +286,24 @@ bool QAVIFHandler::decode_one_frame()
rgb.format = AVIF_RGB_FORMAT_RGBA; rgb.format = AVIF_RGB_FORMAT_RGBA;
if (!loadalpha) { if (!loadalpha) {
rgb.ignoreAlpha = AVIF_TRUE;
result.fill(Qt::black);
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) { if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
resultformat = QImage::Format_Grayscale16; resultformat = QImage::Format_Grayscale16;
} }
} }
} else { } else {
rgb.depth = 8; 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) { if (loadalpha) {
rgb.format = AVIF_RGB_FORMAT_RGBA;
resultformat = QImage::Format_ARGB32; resultformat = QImage::Format_ARGB32;
} else { } else {
rgb.format = AVIF_RGB_FORMAT_RGB;
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) { if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
resultformat = QImage::Format_Grayscale8; resultformat = QImage::Format_Grayscale8;
} else { } else {
@ -296,37 +317,34 @@ bool QAVIFHandler::decode_one_frame()
avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb); avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb);
if (res != AVIF_RESULT_OK) { if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageYUVToRGB: %s\n", avifResultToString(res)); qWarning("ERROR in avifImageYUVToRGB: %s", avifResultToString(res));
return false; return false;
} }
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) { if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
(m_decoder->image->clap.horizOffD > 0) && (m_decoder->image->clap.vertOffD > 0)) { && (m_decoder->image->clap.vertOffD > 0)) {
int new_width, new_height, offx, offy; int new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
if (new_width > result.width()) { if (new_width > result.width()) {
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()) { if (new_height > result.height()) {
new_height = result.height(); new_height = result.height();
} }
if (new_width > 0 && new_height > 0) { if (new_width > 0 && new_height > 0) {
int offx =
offx = ((double)((int32_t) m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + ((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5;
(result.width() - new_width) / 2.0 + 0.5;
if (offx < 0) { if (offx < 0) {
offx = 0; offx = 0;
} else if (offx > (result.width() - new_width)) { } else if (offx > (result.width() - new_width)) {
offx = result.width() - new_width; offx = result.width() - new_width;
} }
offy = ((double)((int32_t) m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + int offy =
(result.height() - new_height) / 2.0 + 0.5; ((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5;
if (offy < 0) { if (offy < 0) {
offy = 0; offy = 0;
} else if (offy > (result.height() - new_height)) { } else if (offy > (result.height() - new_height)) {
@ -337,8 +355,8 @@ bool QAVIFHandler::decode_one_frame()
} }
} }
else { //Zero values, we need to avoid 0 divide. else { // Zero values, we need to avoid 0 divide.
qWarning("ERROR: Wrong values in avifCleanApertureBox\n"); qWarning("ERROR: Wrong values in avifCleanApertureBox");
} }
} }
@ -361,11 +379,15 @@ bool QAVIFHandler::decode_one_frame()
} }
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) { 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) { switch (m_decoder->image->imir.axis) {
case 0: //vertical #endif
case 0: // top-to-bottom
result = result.mirrored(false, true); result = result.mirrored(false, true);
break; break;
case 1: //horizontal case 1: // left-to-right
result = result.mirrored(true, false); result = result.mirrored(true, false);
break; break;
} }
@ -401,27 +423,51 @@ bool QAVIFHandler::read(QImage *image)
bool QAVIFHandler::write(const QImage &image) bool QAVIFHandler::write(const QImage &image)
{ {
if (image.format() == QImage::Format_Invalid) { if (image.format() == QImage::Format_Invalid) {
qWarning("No image data to save"); qWarning("No image data to save!");
return false; return false;
} }
if ((image.width() > 32768) || (image.height() > 32768)) { if ((image.width() > 0) && (image.height() > 0)) {
qWarning("Image is too large"); if ((image.width() > 65535) || (image.height() > 65535)) {
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
return false;
}
if (image.width() > ((16384 * 16384) / image.height())) {
qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels!", image.width(), image.height());
return false;
}
if ((image.width() > 32768) || (image.height() > 32768)) {
qWarning("Image (%dx%d) has a dimension above 32768 pixels, saved AVIF may not work in other software!", image.width(), image.height());
}
} else {
qWarning("Image has zero dimension!");
return false; return false;
} }
const char *encoder_name = avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE);
if (!encoder_name) {
qWarning("Cannot save AVIF images because libavif was built without AV1 encoders!");
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);
}
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100; int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
int minQuantizer = 0; int minQuantizer = 0;
int maxQuantizerAlpha = 0; int maxQuantizerAlpha = 0;
avifResult res; avifResult res;
bool save_grayscale; //true - monochrome, false - colors bool save_grayscale; // true - monochrome, false - colors
int save_depth; //8 or 10bit per channel int save_depth; // 8 or 10bit per channel
QImage::Format tmpformat; //format for temporary image QImage::Format tmpformat; // format for temporary image
avifImage *avif = nullptr; avifImage *avif = nullptr;
//grayscale detection // grayscale detection
switch (image.format()) { switch (image.format()) {
case QImage::Format_Mono: case QImage::Format_Mono:
case QImage::Format_MonoLSB: case QImage::Format_MonoLSB:
@ -437,7 +483,7 @@ bool QAVIFHandler::write(const QImage &image)
break; break;
} }
//depth detection // depth detection
switch (image.format()) { switch (image.format()) {
case QImage::Format_BGR30: case QImage::Format_BGR30:
case QImage::Format_A2BGR30_Premultiplied: case QImage::Format_A2BGR30_Premultiplied:
@ -458,15 +504,15 @@ bool QAVIFHandler::write(const QImage &image)
break; break;
} }
//quality settings // quality settings
if (maxQuantizer > 20) { if (maxQuantizer > 20) {
minQuantizer = maxQuantizer - 20; minQuantizer = maxQuantizer - 20;
if (maxQuantizer > 40) { //we decrease quality of alpha channel here if (maxQuantizer > 40) { // we decrease quality of alpha channel here
maxQuantizerAlpha = maxQuantizer - 40; maxQuantizerAlpha = maxQuantizer - 40;
} }
} }
if (save_grayscale && !image.hasAlphaChannel()) { //we are going to save grayscale image without alpha channel if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
if (save_depth > 8) { if (save_depth > 8) {
tmpformat = QImage::Format_Grayscale16; tmpformat = QImage::Format_Grayscale16;
} else { } else {
@ -494,7 +540,6 @@ bool QAVIFHandler::write(const QImage &image)
/* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */ /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
break; break;
} }
} }
if (save_depth > 8) { // QImage::Format_Grayscale16 if (save_depth > 8) { // QImage::Format_Grayscale16
@ -502,7 +547,7 @@ bool QAVIFHandler::write(const QImage &image)
const uint16_t *src16bit = reinterpret_cast<const uint16_t *>(tmpgrayimage.constScanLine(y)); const uint16_t *src16bit = reinterpret_cast<const uint16_t *>(tmpgrayimage.constScanLine(y));
uint16_t *dest16bit = reinterpret_cast<uint16_t *>(avif->yuvPlanes[0] + y * avif->yuvRowBytes[0]); uint16_t *dest16bit = reinterpret_cast<uint16_t *>(avif->yuvPlanes[0] + y * avif->yuvRowBytes[0]);
for (int x = 0; x < tmpgrayimage.width(); x++) { for (int x = 0; x < tmpgrayimage.width(); x++) {
int tmp_pixelval = (int)(((float)(*src16bit) / 65535.0f) * 1023.0f + 0.5f); //downgrade to 10 bits int tmp_pixelval = (int)(((float)(*src16bit) / 65535.0f) * 1023.0f + 0.5f); // downgrade to 10 bits
*dest16bit = qBound(0, tmp_pixelval, 1023); *dest16bit = qBound(0, tmp_pixelval, 1023);
dest16bit++; dest16bit++;
src16bit++; src16bit++;
@ -520,14 +565,14 @@ bool QAVIFHandler::write(const QImage &image)
} }
} }
} else { //we are going to save color image } else { // we are going to save color image
if (save_depth > 8) { if (save_depth > 8) {
if (image.hasAlphaChannel()) { if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA64; tmpformat = QImage::Format_RGBA64;
} else { } else {
tmpformat = QImage::Format_RGBX64; tmpformat = QImage::Format_RGBX64;
} }
} else { //8bit depth } else { // 8bit depth
if (image.hasAlphaChannel()) { if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA8888; tmpformat = QImage::Format_RGBA8888;
} else { } else {
@ -540,16 +585,17 @@ bool QAVIFHandler::write(const QImage &image)
avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420; avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
if (maxQuantizer < 20) { if (maxQuantizer < 20) {
if (maxQuantizer < 10) { if (maxQuantizer < 10) {
pixel_format = AVIF_PIXEL_FORMAT_YUV444; //best quality pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
} else { } else {
pixel_format = AVIF_PIXEL_FORMAT_YUV422; //high quality pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
} }
} }
avifMatrixCoefficients matrix_to_save = (avifMatrixCoefficients)1; //default for Qt 5.12 and 5.13; avifMatrixCoefficients matrix_to_save = (avifMatrixCoefficients)1; // default for Qt 5.12 and 5.13;
avifColorPrimaries primaries_to_save = (avifColorPrimaries)2; avifColorPrimaries primaries_to_save = (avifColorPrimaries)2;
avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2; avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2;
QByteArray iccprofile;
if (tmpcolorimage.colorSpace().isValid()) { if (tmpcolorimage.colorSpace().isValid()) {
switch (tmpcolorimage.colorSpace().primaries()) { switch (tmpcolorimage.colorSpace().primaries()) {
@ -600,11 +646,9 @@ bool QAVIFHandler::write(const QImage &image)
break; break;
} }
//in case primaries or trc were not identified // in case primaries or trc were not identified
if ((primaries_to_save == 2) || if ((primaries_to_save == 2) || (transfer_to_save == 2)) {
(transfer_to_save == 2)) { // upgrade image to higher bit depth
//upgrade image to higher bit depth
if (save_depth == 8) { if (save_depth == 8) {
save_depth = 10; save_depth = 10;
if (tmpcolorimage.hasAlphaChannel()) { if (tmpcolorimage.hasAlphaChannel()) {
@ -614,8 +658,7 @@ bool QAVIFHandler::write(const QImage &image)
} }
} }
if ((primaries_to_save == 2) && if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
(transfer_to_save != 2)) { //other primaries but known trc
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709 primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709 matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
@ -634,30 +677,39 @@ bool QAVIFHandler::write(const QImage &image)
transfer_to_save = (avifTransferCharacteristics)13; transfer_to_save = (avifTransferCharacteristics)13;
break; break;
} }
} else if ((primaries_to_save != 2) && } else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
(transfer_to_save == 2)) { //recognized primaries but other trc
transfer_to_save = (avifTransferCharacteristics)13; transfer_to_save = (avifTransferCharacteristics)13;
tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb)); tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
} else { //unrecognized profile } else { // unrecognized profile
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709 primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
transfer_to_save = (avifTransferCharacteristics)13; transfer_to_save = (avifTransferCharacteristics)13;
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709 matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb)); 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 = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
avif->matrixCoefficients = matrix_to_save; avif->matrixCoefficients = matrix_to_save;
avif->colorPrimaries = primaries_to_save; avif->colorPrimaries = primaries_to_save;
avif->transferCharacteristics = transfer_to_save; avif->transferCharacteristics = transfer_to_save;
if (iccprofile.size() > 0) {
avifImageSetProfileICC(avif, (const uint8_t *)iccprofile.constData(), iccprofile.size());
}
avifRGBImage rgb; avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, avif); avifRGBImageSetDefaults(&rgb, avif);
rgb.rowBytes = tmpcolorimage.bytesPerLine(); rgb.rowBytes = tmpcolorimage.bytesPerLine();
rgb.pixels = const_cast<uint8_t *>(tmpcolorimage.constBits()); rgb.pixels = const_cast<uint8_t *>(tmpcolorimage.constBits());
if (save_depth > 8) { //10bit depth if (save_depth > 8) { // 10bit depth
rgb.depth = 16; rgb.depth = 16;
if (tmpcolorimage.hasAlphaChannel()) { if (tmpcolorimage.hasAlphaChannel()) {
@ -667,7 +719,7 @@ bool QAVIFHandler::write(const QImage &image)
} }
rgb.format = AVIF_RGB_FORMAT_RGBA; rgb.format = AVIF_RGB_FORMAT_RGBA;
} else { //8bit depth } else { // 8bit depth
rgb.depth = 8; rgb.depth = 8;
if (tmpcolorimage.hasAlphaChannel()) { if (tmpcolorimage.hasAlphaChannel()) {
@ -680,7 +732,7 @@ bool QAVIFHandler::write(const QImage &image)
res = avifImageRGBToYUV(avif, &rgb); res = avifImageRGBToYUV(avif, &rgb);
if (res != AVIF_RESULT_OK) { if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageRGBToYUV: %s\n", avifResultToString(res)); qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
return false; return false;
} }
} }
@ -696,7 +748,7 @@ bool QAVIFHandler::write(const QImage &image)
encoder->maxQuantizerAlpha = maxQuantizerAlpha; encoder->maxQuantizerAlpha = maxQuantizerAlpha;
} }
encoder->speed = 8; encoder->speed = 6;
res = avifEncoderWrite(encoder, avif, &raw); res = avifEncoderWrite(encoder, avif, &raw);
avifEncoderDestroy(encoder); avifEncoderDestroy(encoder);
@ -709,26 +761,27 @@ bool QAVIFHandler::write(const QImage &image)
if (status > 0) { if (status > 0) {
return true; return true;
} else if (status == -1) { } else if (status == -1) {
qWarning("Write error: %s\n", qUtf8Printable(device()->errorString())); qWarning("Write error: %s", qUtf8Printable(device()->errorString()));
return false; return false;
} }
} else { } else {
qWarning("ERROR: Failed to encode: %s\n", avifResultToString(res)); qWarning("ERROR: Failed to encode: %s", avifResultToString(res));
} }
return false; return false;
} }
QVariant QAVIFHandler::option(ImageOption option) const QVariant QAVIFHandler::option(ImageOption option) const
{ {
if (option == Quality) {
return m_quality;
}
if (!supportsOption(option) || !ensureParsed()) { if (!supportsOption(option) || !ensureParsed()) {
return QVariant(); return QVariant();
} }
switch (option) { switch (option) {
case Quality:
return m_quality;
case Size: case Size:
return m_current_image.size(); return m_current_image.size();
case Animation: case Animation:
@ -761,9 +814,7 @@ void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
bool QAVIFHandler::supportsOption(ImageOption option) const bool QAVIFHandler::supportsOption(ImageOption option) const
{ {
return option == Quality return option == Quality || option == Size || option == Animation;
|| option == Size
|| option == Animation;
} }
int QAVIFHandler::imageCount() const int QAVIFHandler::imageCount() const
@ -801,23 +852,24 @@ bool QAVIFHandler::jumpToNextImage()
return true; 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); avifDecoderReset(m_decoder);
} }
avifResult decodeResult = avifDecoderNextImage(m_decoder); avifResult decodeResult = avifDecoderNextImage(m_decoder);
if (decodeResult != AVIF_RESULT_OK) { if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to decode Next image in sequence: %s\n", avifResultToString(decodeResult)); qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
m_parseState = ParseAvifError; m_parseState = ParseAvifError;
return false; return false;
} }
if ((m_container_width != m_decoder->image->width) || if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
(m_container_height != m_decoder->image->height)) { qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!",
qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!\n", m_decoder->image->width,
m_decoder->image->width, m_decoder->image->height, m_decoder->image->height,
m_container_width, m_container_height); m_container_width,
m_container_height);
m_parseState = ParseAvifError; m_parseState = ParseAvifError;
return false; return false;
@ -829,7 +881,6 @@ bool QAVIFHandler::jumpToNextImage()
m_parseState = ParseAvifError; m_parseState = ParseAvifError;
return false; return false;
} }
} }
bool QAVIFHandler::jumpToImage(int imageNumber) bool QAVIFHandler::jumpToImage(int imageNumber)
@ -838,7 +889,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
return false; return false;
} }
if (m_decoder->imageCount < 2) { //not an animation if (m_decoder->imageCount < 2) { // not an animation
if (imageNumber == 0) { if (imageNumber == 0) {
return true; return true;
} else { } else {
@ -846,27 +897,29 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
} }
} }
if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { //wrong index if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
return false; return false;
} }
if (imageNumber == m_decoder->imageCount) { // we are here already if (imageNumber == m_decoder->imageIndex) { // we are here already
m_must_jump_to_next_image = false;
return true; return true;
} }
avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber); avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber);
if (decodeResult != AVIF_RESULT_OK) { if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to decode %d th Image in sequence: %s\n", imageNumber, avifResultToString(decodeResult)); qWarning("ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult));
m_parseState = ParseAvifError; m_parseState = ParseAvifError;
return false; return false;
} }
if ((m_container_width != m_decoder->image->width) || if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
(m_container_height != m_decoder->image->height)) { qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!",
qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!\n", m_decoder->image->width,
m_decoder->image->width, m_decoder->image->height, m_decoder->image->height,
m_container_width, m_container_height); m_container_width,
m_container_height);
m_parseState = ParseAvifError; m_parseState = ParseAvifError;
return false; return false;
@ -910,6 +963,18 @@ int QAVIFHandler::loopCount() const
return 1; return 1;
} }
QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
{
chrX = qBound(qreal(0.0), chrX, qreal(1.0));
chrY = qBound(qreal(DBL_MIN), chrY, qreal(1.0));
if ((chrX + chrY) > qreal(1.0)) {
chrX = qreal(1.0) - chrY;
}
return QPointF(chrX, chrY);
}
QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{ {
if (format == "avif") { if (format == "avif") {

View File

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

View File

@ -9,72 +9,74 @@
#ifndef KIMG_AVIF_P_H #ifndef KIMG_AVIF_P_H
#define KIMG_AVIF_P_H #define KIMG_AVIF_P_H
#include <QImage>
#include <QVariant>
#include <qimageiohandler.h>
#include <QImageIOPlugin>
#include <QByteArray> #include <QByteArray>
#include <QImage>
#include <QImageIOPlugin>
#include <QPointF>
#include <QVariant>
#include <avif/avif.h> #include <avif/avif.h>
#include <qimageiohandler.h>
class QAVIFHandler : public QImageIOHandler class QAVIFHandler : public QImageIOHandler
{ {
public: public:
QAVIFHandler(); QAVIFHandler();
~QAVIFHandler(); ~QAVIFHandler();
bool canRead() const override; bool canRead() const override;
bool read (QImage *image) override; bool read(QImage *image) override;
bool write (const QImage &image) override; bool write(const QImage &image) override;
static bool canRead (QIODevice *device); static bool canRead(QIODevice *device);
QVariant option (ImageOption option) const override; QVariant option(ImageOption option) const override;
void setOption (ImageOption option, const QVariant &value) override; void setOption(ImageOption option, const QVariant &value) override;
bool supportsOption (ImageOption option) const override; bool supportsOption(ImageOption option) const override;
int imageCount() const override; int imageCount() const override;
int currentImageNumber() const override; int currentImageNumber() const override;
bool jumpToNextImage() override; bool jumpToNextImage() override;
bool jumpToImage (int imageNumber) override; bool jumpToImage(int imageNumber) override;
int nextImageDelay() const override; int nextImageDelay() const override;
int loopCount() const override;
int loopCount() const override;
private: private:
bool ensureParsed() const; static QPointF CompatibleChromacity(qreal chrX, qreal chrY);
bool ensureDecoder(); bool ensureParsed() const;
bool decode_one_frame(); bool ensureDecoder();
bool decode_one_frame();
enum ParseAvifState enum ParseAvifState {
{ ParseAvifError = -1,
ParseAvifError = -1, ParseAvifNotParsed = 0,
ParseAvifNotParsed = 0, ParseAvifSuccess = 1,
ParseAvifSuccess = 1 };
};
ParseAvifState m_parseState; ParseAvifState m_parseState;
int m_quality; int m_quality;
uint32_t m_container_width; uint32_t m_container_width;
uint32_t m_container_height; uint32_t m_container_height;
QByteArray m_rawData; QByteArray m_rawData;
avifROData m_rawAvifData; avifROData m_rawAvifData;
avifDecoder *m_decoder; avifDecoder *m_decoder;
QImage m_current_image; QImage m_current_image;
bool m_must_jump_to_next_image; bool m_must_jump_to_next_image;
}; };
class QAVIFPlugin : public QImageIOPlugin class QAVIFPlugin : public QImageIOPlugin
{ {
Q_OBJECT Q_OBJECT
Q_PLUGIN_METADATA (IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "avif.json") Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "avif.json")
public: public:
Capabilities capabilities (QIODevice *device, const QByteArray &format) const override; Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create (QIODevice *device, const QByteArray &format = QByteArray()) const override; QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
}; };
#endif // KIMG_AVIF_P_H #endif // KIMG_AVIF_P_H

View File

@ -9,13 +9,14 @@
*/ */
#include "eps_p.h" #include "eps_p.h"
#include <QCoreApplication>
#include <QImage> #include <QImage>
#include <QImageReader> #include <QImageReader>
#include <QPainter> #include <QPainter>
#include <QPrinter> #include <QPrinter>
#include <QProcess> #include <QProcess>
#include <QStandardPaths>
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QCoreApplication>
// logging category for this framework, default: log stuff >= warning // logging category for this framework, default: log stuff >= warning
Q_LOGGING_CATEGORY(EPSPLUGIN, "kf.imageformats.plugins.eps", QtWarningMsg) Q_LOGGING_CATEGORY(EPSPLUGIN, "kf.imageformats.plugins.eps", QtWarningMsg)
@ -51,19 +52,13 @@ static bool seekToCodeStart(QIODevice *io, qint64 &ps_offset, qint64 &ps_size)
return false; return false;
} }
ps_offset // Offset is in little endian ps_offset // Offset is in little endian
= qint64(((unsigned char)buf[0]) = qint64(((unsigned char)buf[0]) + ((unsigned char)buf[1] << 8) + ((unsigned char)buf[2] << 16) + ((unsigned char)buf[3] << 24));
+ ((unsigned char)buf[1] << 8)
+ ((unsigned char)buf[2] << 16)
+ ((unsigned char)buf[3] << 24));
if (io->read(buf, 4) != 4) { // Get size of PostScript code in the MS-DOS EPS file. if (io->read(buf, 4) != 4) { // Get size of PostScript code in the MS-DOS EPS file.
qCDebug(EPSPLUGIN) << "cannot read size of MS-DOS EPS file"; qCDebug(EPSPLUGIN) << "cannot read size of MS-DOS EPS file";
return false; return false;
} }
ps_size // Size is in little endian ps_size // Size is in little endian
= qint64(((unsigned char)buf[0]) = qint64(((unsigned char)buf[0]) + ((unsigned char)buf[1] << 8) + ((unsigned char)buf[2] << 16) + ((unsigned char)buf[3] << 24));
+ ((unsigned char)buf[1] << 8)
+ ((unsigned char)buf[2] << 16)
+ ((unsigned char)buf[3] << 24));
qCDebug(EPSPLUGIN) << "Offset: " << ps_offset << " Size: " << ps_size; qCDebug(EPSPLUGIN) << "Offset: " << ps_offset << " Size: " << ps_size;
if (!io->seek(ps_offset)) { // Get offset of PostScript code in the MS-DOS EPS file. if (!io->seek(ps_offset)) { // Get offset of PostScript code in the MS-DOS EPS file.
qCDebug(EPSPLUGIN) << "cannot seek in MS-DOS EPS file"; qCDebug(EPSPLUGIN) << "cannot seek in MS-DOS EPS file";
@ -100,9 +95,11 @@ static bool bbox(QIODevice *io, int *x1, int *y1, int *x2, int *y2)
if (strncmp(buf, BBOX, BBOX_LEN) == 0) { if (strncmp(buf, BBOX, BBOX_LEN) == 0) {
// Some EPS files have non-integer values for the bbox // Some EPS files have non-integer values for the bbox
// We don't support that currently, but at least we parse it // We don't support that currently, but at least we parse it
float _x1, _y1, _x2, _y2; float _x1;
if (sscanf(buf, "%*s %f %f %f %f", float _y1;
&_x1, &_y1, &_x2, &_y2) == 4) { float _x2;
float _y2;
if (sscanf(buf, "%*s %f %f %f %f", &_x1, &_y1, &_x2, &_y2) == 4) {
qCDebug(EPSPLUGIN) << "BBOX: " << _x1 << " " << _y1 << " " << _x2 << " " << _y2; qCDebug(EPSPLUGIN) << "BBOX: " << _x1 << " " << _y1 << " " << _x2 << " " << _y2;
*x1 = int(_x1); *x1 = int(_x1);
*y1 = int(_y1); *y1 = int(_y1);
@ -134,14 +131,18 @@ bool EPSHandler::read(QImage *image)
{ {
qCDebug(EPSPLUGIN) << "starting..."; qCDebug(EPSPLUGIN) << "starting...";
int x1, y1, x2, y2; int x1;
int y1;
int x2;
int y2;
#ifdef EPS_PERFORMANCE_DEBUG #ifdef EPS_PERFORMANCE_DEBUG
QTime dt; QTime dt;
dt.start(); dt.start();
#endif #endif
QIODevice *io = device(); QIODevice *io = device();
qint64 ps_offset, ps_size; qint64 ps_offset;
qint64 ps_size;
// find start of PostScript code // find start of PostScript code
if (!seekToCodeStart(io, ps_offset, ps_size)) { if (!seekToCodeStart(io, ps_offset, ps_size)) {
@ -176,29 +177,29 @@ bool EPSHandler::read(QImage *image)
// create GS command line // create GS command line
const QString gsExec = QStandardPaths::findExecutable(QStringLiteral("gs"));
if (gsExec.isEmpty()) {
qCWarning(EPSPLUGIN) << "Couldn't find gs exectuable (from GhostScript) in PATH.";
return false;
}
QStringList gsArgs; QStringList gsArgs;
gsArgs << QLatin1String("-sOutputFile=") + tmpFile.fileName() gsArgs << QLatin1String("-sOutputFile=") + tmpFile.fileName() << QStringLiteral("-q") << QStringLiteral("-g%1x%2").arg(wantedWidth).arg(wantedHeight)
<< QStringLiteral("-q") << QStringLiteral("-dSAFER") << QStringLiteral("-dPARANOIDSAFER") << QStringLiteral("-dNOPAUSE") << QStringLiteral("-sDEVICE=ppm")
<< QStringLiteral("-g%1x%2").arg(wantedWidth).arg(wantedHeight)
<< QStringLiteral("-dSAFER")
<< QStringLiteral("-dPARANOIDSAFER")
<< QStringLiteral("-dNOPAUSE")
<< QStringLiteral("-sDEVICE=ppm")
<< QStringLiteral("-c") << QStringLiteral("-c")
<< QStringLiteral("0 0 moveto " << QStringLiteral(
"1000 0 lineto " "0 0 moveto "
"1000 1000 lineto " "1000 0 lineto "
"0 1000 lineto " "1000 1000 lineto "
"1 1 254 255 div setrgbcolor fill " "0 1000 lineto "
"0 0 0 setrgbcolor") "1 1 254 255 div setrgbcolor fill "
<< QStringLiteral("-") "0 0 0 setrgbcolor")
<< QStringLiteral("-c") << QStringLiteral("-") << QStringLiteral("-c") << QStringLiteral("showpage quit");
<< QStringLiteral("showpage quit");
qCDebug(EPSPLUGIN) << "Running gs with args" << gsArgs; qCDebug(EPSPLUGIN) << "Running gs with args" << gsArgs;
QProcess converter; QProcess converter;
converter.setProcessChannelMode(QProcess::ForwardedErrorChannel); converter.setProcessChannelMode(QProcess::ForwardedErrorChannel);
converter.start(QStringLiteral("gs"), gsArgs); converter.start(gsExec, gsArgs);
if (!converter.waitForStarted(3000)) { if (!converter.waitForStarted(3000)) {
qCWarning(EPSPLUGIN) << "Reading EPS files requires gs (from GhostScript)"; qCWarning(EPSPLUGIN) << "Reading EPS files requires gs (from GhostScript)";
return false; return false;
@ -264,7 +265,8 @@ bool EPSHandler::write(const QImage &image)
psOut.setOutputFileName(tmpFile.fileName()); psOut.setOutputFileName(tmpFile.fileName());
psOut.setOutputFormat(QPrinter::PdfFormat); psOut.setOutputFormat(QPrinter::PdfFormat);
psOut.setFullPage(true); 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 // painting the pixmap to the "printer" which is a file
p.begin(&psOut); p.begin(&psOut);
@ -277,24 +279,16 @@ bool EPSHandler::write(const QImage &image)
// pdftops comes with Poppler and produces much smaller EPS files than GhostScript // pdftops comes with Poppler and produces much smaller EPS files than GhostScript
QStringList pdftopsArgs; QStringList pdftopsArgs;
pdftopsArgs << QStringLiteral("-eps") pdftopsArgs << QStringLiteral("-eps") << tmpFile.fileName() << QStringLiteral("-");
<< tmpFile.fileName()
<< QStringLiteral("-");
qCDebug(EPSPLUGIN) << "Running pdftops with args" << pdftopsArgs; qCDebug(EPSPLUGIN) << "Running pdftops with args" << pdftopsArgs;
converter.start(QStringLiteral("pdftops"), pdftopsArgs); converter.start(QStringLiteral("pdftops"), pdftopsArgs);
if (!converter.waitForStarted()) { if (!converter.waitForStarted()) {
// GhostScript produces huge files, and takes a long time doing so // GhostScript produces huge files, and takes a long time doing so
QStringList gsArgs; QStringList gsArgs;
gsArgs << QStringLiteral("-q") << QStringLiteral("-P-") gsArgs << QStringLiteral("-q") << QStringLiteral("-P-") << QStringLiteral("-dNOPAUSE") << QStringLiteral("-dBATCH") << QStringLiteral("-dSAFER")
<< QStringLiteral("-dNOPAUSE") << QStringLiteral("-dBATCH") << QStringLiteral("-sDEVICE=epswrite") << QStringLiteral("-sOutputFile=-") << QStringLiteral("-c") << QStringLiteral("save")
<< QStringLiteral("-dSAFER") << QStringLiteral("pop") << QStringLiteral("-f") << tmpFile.fileName();
<< QStringLiteral("-sDEVICE=epswrite")
<< QStringLiteral("-sOutputFile=-")
<< QStringLiteral("-c")
<< QStringLiteral("save") << QStringLiteral("pop")
<< QStringLiteral("-f")
<< tmpFile.fileName();
qCDebug(EPSPLUGIN) << "Failed to start pdftops; trying gs with args" << gsArgs; qCDebug(EPSPLUGIN) << "Failed to start pdftops; trying gs with args" << gsArgs;
converter.start(QStringLiteral("gs"), gsArgs); converter.start(QStringLiteral("gs"), gsArgs);

View File

@ -35,4 +35,3 @@ public:
Q_DECLARE_LOGGING_CATEGORY(EPSPLUGIN) Q_DECLARE_LOGGING_CATEGORY(EPSPLUGIN)
#endif // KIMG_EPS_P_H #endif // KIMG_EPS_P_H

View File

@ -9,41 +9,48 @@
#include "exr_p.h" #include "exr_p.h"
#include <ImfRgbaFile.h> #include <IexThrowErrnoExc.h>
#include <ImfStandardAttributes.h>
#include <ImathBox.h> #include <ImathBox.h>
#include <ImfInputFile.h> #include <ImfArray.h>
#include <ImfBoxAttribute.h> #include <ImfBoxAttribute.h>
#include <ImfChannelListAttribute.h> #include <ImfChannelListAttribute.h>
#include <ImfCompressionAttribute.h> #include <ImfCompressionAttribute.h>
#include <ImfConvert.h>
#include <ImfFloatAttribute.h> #include <ImfFloatAttribute.h>
#include <ImfInputFile.h>
#include <ImfInt64.h>
#include <ImfIntAttribute.h> #include <ImfIntAttribute.h>
#include <ImfLineOrderAttribute.h> #include <ImfLineOrderAttribute.h>
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
#include <ImfStringAttribute.h> #include <ImfStringAttribute.h>
#include <ImfVecAttribute.h> #include <ImfVecAttribute.h>
#include <ImfArray.h>
#include <ImfConvert.h>
#include <ImfVersion.h> #include <ImfVersion.h>
#include <IexThrowErrnoExc.h>
#include <iostream> #include <iostream>
#include <QImage>
#include <QDataStream> #include <QDataStream>
#include <QDebug> #include <QDebug>
#include <QImage>
#include <QImageIOPlugin> #include <QImageIOPlugin>
class K_IStream: public Imf::IStream class K_IStream : public Imf::IStream
{ {
public: public:
K_IStream(QIODevice *dev, const QByteArray &fileName): K_IStream(QIODevice *dev, const QByteArray &fileName)
IStream(fileName.data()), m_dev(dev) : IStream(fileName.data())
, m_dev(dev)
{ {
} }
bool read(char c[], int n) override; 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; Imf::Int64 tellg() override;
void seekg(Imf::Int64 pos) override; void seekg(Imf::Int64 pos) override;
#endif
void clear() override; void clear() override;
private: private:
@ -63,12 +70,20 @@ bool K_IStream::read(char c[], int n)
return false; return false;
} }
#if OPENEXR_VERSION_MAJOR > 2
uint64_t K_IStream::tellg()
#else
Imf::Int64 K_IStream::tellg() Imf::Int64 K_IStream::tellg()
#endif
{ {
return m_dev->pos(); return m_dev->pos();
} }
#if OPENEXR_VERSION_MAJOR > 2
void K_IStream::seekg(uint64_t pos)
#else
void K_IStream::seekg(Imf::Int64 pos) void K_IStream::seekg(Imf::Int64 pos)
#endif
{ {
m_dev->seek(pos); m_dev->seek(pos);
} }
@ -84,7 +99,10 @@ void K_IStream::clear()
*/ */
QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel) 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 // 1) Compensate for fogging by subtracting defog
// from the raw pixel values. // from the raw pixel values.
@ -117,30 +135,30 @@ QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
// maximum intensity). // maximum intensity).
// Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32) // Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32)
if (r > 1.0) { 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) { 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) { 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) { 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 // 5) Gamma-correct the pixel values, assuming that the
// screen's gamma is 0.4545 (or 1/2.2). // screen's gamma is 0.4545 (or 1/2.2).
r = Imath::Math<float>::pow(r, 0.4545); r = std::pow(r, 0.4545);
g = Imath::Math<float>::pow(g, 0.4545); g = std::pow(g, 0.4545);
b = Imath::Math<float>::pow(b, 0.4545); b = std::pow(b, 0.4545);
a = Imath::Math<float>::pow(a, 0.4545); a = std::pow(a, 0.4545);
// 6) Scale the values such that pixels middle gray // 6) Scale the values such that pixels middle gray
// pixels are mapped to 84.66 (or 3.5 f-stops below // pixels are mapped to 84.66 (or 3.5 f-stops below
// the display's maximum intensity). // the display's maximum intensity).
// //
// 7) Clamp the values to [0, 255]. // 7) Clamp the values to [0, 255].
return qRgba((unsigned char)(Imath::clamp(r * 84.66f, 0.f, 255.f)), return qRgba((unsigned char)(Imath::clamp(r * 84.66f, 0.f, 255.f)),
(unsigned char)(Imath::clamp(g * 84.66f, 0.f, 255.f)), (unsigned char)(Imath::clamp(g * 84.66f, 0.f, 255.f)),
(unsigned char)(Imath::clamp(b * 84.66f, 0.f, 255.f)), (unsigned char)(Imath::clamp(b * 84.66f, 0.f, 255.f)),
@ -163,13 +181,14 @@ bool EXRHandler::canRead() const
bool EXRHandler::read(QImage *outImage) bool EXRHandler::read(QImage *outImage)
{ {
try { try {
int width, height; int width;
int height;
K_IStream istr(device(), QByteArray()); K_IStream istr(device(), QByteArray());
Imf::RgbaInputFile file(istr); Imf::RgbaInputFile file(istr);
Imath::Box2i dw = file.dataWindow(); Imath::Box2i dw = file.dataWindow();
width = dw.max.x - dw.min.x + 1; width = dw.max.x - dw.min.x + 1;
height = dw.max.y - dw.min.y + 1; height = dw.max.y - dw.min.y + 1;
QImage image(width, height, QImage::Format_RGB32); QImage image(width, height, QImage::Format_RGB32);
@ -196,7 +215,7 @@ bool EXRHandler::read(QImage *outImage)
return true; return true;
} catch (const std::exception &exc) { } catch (const std::exception &exc) {
// qDebug() << exc.what(); // qDebug() << exc.what();
return false; return false;
} }
} }

View File

@ -20,13 +20,13 @@ typedef unsigned char uchar;
// From GIMP "tile.h" v1.2 // From GIMP "tile.h" v1.2
const uint TILE_WIDTH = 64; //!< Width of a tile in the XCF file. const uint TILE_WIDTH = 64; //!< Width of a tile in the XCF file.
const uint TILE_HEIGHT = 64; //!< Height of a tile in the XCF file. const uint TILE_HEIGHT = 64; //!< Height of a tile in the XCF file.
// From GIMP "paint_funcs.c" v1.2 // From GIMP "paint_funcs.c" v1.2
const int RANDOM_TABLE_SIZE = 4096; //!< Size of dissolve random number table. const int RANDOM_TABLE_SIZE = 4096; //!< Size of dissolve random number table.
const int RANDOM_SEED = 314159265; //!< Seed for dissolve random number table. const int RANDOM_SEED = 314159265; //!< Seed for dissolve random number table.
const double EPSILON = 0.0001; //!< Roundup in alpha blending. const double EPSILON = 0.0001; //!< Roundup in alpha blending.
// From GIMP "paint_funcs.h" v1.2 // From GIMP "paint_funcs.h" v1.2
@ -41,10 +41,9 @@ const uchar OPAQUE_OPACITY = 255; //!< Opaque value for 8-bit alpha component.
typedef enum { typedef enum {
RGB, RGB,
GRAY, GRAY,
INDEXED INDEXED,
} GimpImageBaseType; } GimpImageBaseType;
// From GIMP "libgimp/gimpenums.h" v2.4 // From GIMP "libgimp/gimpenums.h" v2.4
//! Effect to apply when layers are merged together. //! Effect to apply when layers are merged together.
@ -74,7 +73,6 @@ typedef enum {
GRAIN_MERGE_MODE GRAIN_MERGE_MODE
} LayerModeEffects; } LayerModeEffects;
// From GIMP "paint_funcs.c" v1.2 // From GIMP "paint_funcs.c" v1.2
/*! /*!
@ -163,9 +161,9 @@ static void RGBTOHSV(uchar &red, uchar &green, uchar &blue)
} }
} }
red = (uchar)h; red = (uchar)h;
green = (uchar)s; green = (uchar)s;
blue = (uchar)v; blue = (uchar)v;
} }
/*! /*!
@ -177,9 +175,9 @@ static void RGBTOHSV(uchar &red, uchar &green, uchar &blue)
static void HSVTORGB(uchar &hue, uchar &saturation, uchar &value) static void HSVTORGB(uchar &hue, uchar &saturation, uchar &value)
{ {
if (saturation == 0) { if (saturation == 0) {
hue = value; hue = value;
saturation = value; saturation = value;
//value = value; // value = value;
} else { } else {
double h = hue * 6. / 255.; double h = hue * 6. / 255.;
double s = saturation / 255.; double s = saturation / 255.;
@ -195,34 +193,34 @@ static void HSVTORGB(uchar &hue, uchar &saturation, uchar &value)
switch ((int)h) { switch ((int)h) {
case 0: case 0:
hue = (uchar)(v * 255); hue = (uchar)(v * 255);
saturation = (uchar)(t * 255); saturation = (uchar)(t * 255);
value = (uchar)(p * 255); value = (uchar)(p * 255);
break; break;
case 1: case 1:
hue = (uchar)(q * 255); hue = (uchar)(q * 255);
saturation = (uchar)(v * 255); saturation = (uchar)(v * 255);
value = (uchar)(p * 255); value = (uchar)(p * 255);
break; break;
case 2: case 2:
hue = (uchar)(p * 255); hue = (uchar)(p * 255);
saturation = (uchar)(v * 255); saturation = (uchar)(v * 255);
value = (uchar)(t * 255); value = (uchar)(t * 255);
break; break;
case 3: case 3:
hue = (uchar)(p * 255); hue = (uchar)(p * 255);
saturation = (uchar)(q * 255); saturation = (uchar)(q * 255);
value = (uchar)(v * 255); value = (uchar)(v * 255);
break; break;
case 4: case 4:
hue = (uchar)(t * 255); hue = (uchar)(t * 255);
saturation = (uchar)(p * 255); saturation = (uchar)(p * 255);
value = (uchar)(v * 255); value = (uchar)(v * 255);
break; break;
case 5: case 5:
hue = (uchar)(v * 255); hue = (uchar)(v * 255);
saturation = (uchar)(p * 255); saturation = (uchar)(p * 255);
value = (uchar)(q * 255); value = (uchar)(q * 255);
} }
} }
} }
@ -282,9 +280,9 @@ static void RGBTOHLS(uchar &red, uchar &green, uchar &blue)
} }
} }
red = (uchar)h; red = (uchar)h;
green = (uchar)l; green = (uchar)l;
blue = (uchar)s; blue = (uchar)s;
} }
/*! /*!
@ -330,8 +328,8 @@ static void HLSTORGB(uchar &hue, uchar &lightness, uchar &saturation)
double s = saturation; double s = saturation;
if (s == 0) { if (s == 0) {
hue = (uchar)l; hue = (uchar)l;
lightness = (uchar)l; lightness = (uchar)l;
saturation = (uchar)l; saturation = (uchar)l;
} else { } else {
double m1, m2; double m1, m2;
@ -344,8 +342,8 @@ static void HLSTORGB(uchar &hue, uchar &lightness, uchar &saturation)
m1 = (l / 127.5) - m2; m1 = (l / 127.5) - m2;
hue = HLSVALUE(m1, m2, h + 85); hue = HLSVALUE(m1, m2, h + 85);
lightness = HLSVALUE(m1, m2, h); lightness = HLSVALUE(m1, m2, h);
saturation = HLSVALUE(m1, m2, h - 85); saturation = HLSVALUE(m1, m2, h - 85);
} }
} }

View File

@ -8,8 +8,8 @@
#include "hdr_p.h" #include "hdr_p.h"
#include <QImage>
#include <QDataStream> #include <QDataStream>
#include <QImage>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QRegularExpressionMatch> #include <QRegularExpressionMatch>
@ -19,19 +19,18 @@ typedef unsigned char uchar;
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg) Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
namespace // Private. namespace // Private.
{ {
#define MAXLINE 1024
#define MAXLINE 1024 #define MINELEN 8 // minimum scanline length for encoding
#define MINELEN 8 // minimum scanline length for encoding #define MAXELEN 0x7fff // maximum scanline length for encoding
#define MAXELEN 0x7fff // maximum scanline length for encoding
static inline uchar ClipToByte(float value) static inline uchar ClipToByte(float value)
{ {
if (value > 255.0f) { if (value > 255.0f) {
return 255; return 255;
} }
//else if (value < 0.0f) return 0; // we know value is positive. // else if (value < 0.0f) return 0; // we know value is positive.
return uchar(value); return uchar(value);
} }
@ -39,8 +38,8 @@ static inline uchar ClipToByte(float value)
// if 'first' is true the first byte is already read // if 'first' is true the first byte is already read
static bool Read_Old_Line(uchar *image, int width, QDataStream &s) static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
{ {
int rshift = 0; int rshift = 0;
int i; int i;
while (width > 0) { while (width > 0) {
s >> image[0]; s >> image[0];
@ -54,7 +53,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) { if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) {
for (i = image[3] << rshift; i > 0; i--) { for (i = image[3] << rshift; i > 0; i--) {
//memcpy(image, image-4, 4); // memcpy(image, image-4, 4);
(uint &)image[0] = (uint &)image[0 - 4]; (uint &)image[0] = (uint &)image[0 - 4];
image += 4; image += 4;
width--; width--;
@ -81,9 +80,7 @@ static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
v = 1.0f / float(1 << -e); v = 1.0f / float(1 << -e);
} }
scanline[j] = qRgb(ClipToByte(float(image[0]) * v), scanline[j] = qRgb(ClipToByte(float(image[0]) * v), ClipToByte(float(image[1]) * v), ClipToByte(float(image[2]) * v));
ClipToByte(float(image[1]) * v),
ClipToByte(float(image[2]) * v));
image += 4; image += 4;
} }
@ -92,7 +89,8 @@ static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
// Load the HDR image. // Load the HDR image.
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img) static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
{ {
uchar val, code; uchar val;
uchar code;
// Create dst image. // Create dst image.
img = QImage(width, height, QImage::Format_RGB32); img = QImage(width, height, QImage::Format_RGB32);
@ -103,10 +101,10 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
QByteArray lineArray; QByteArray lineArray;
lineArray.resize(4 * width); lineArray.resize(4 * width);
uchar *image = (uchar *) lineArray.data(); uchar *image = (uchar *)lineArray.data();
for (int cline = 0; cline < height; cline++) { for (int cline = 0; cline < height; cline++) {
QRgb *scanline = (QRgb *) img.scanLine(cline); QRgb *scanline = (QRgb *)img.scanLine(cline);
// determine scanline type // determine scanline type
if ((width < MINELEN) || (MAXELEN < width)) { if ((width < MINELEN) || (MAXELEN < width)) {
@ -168,7 +166,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
} else { } else {
// non-run // non-run
while (code != 0) { while (code != 0) {
s >> image[i + j * 4]; s >> image[i + j * 4];
j++; j++;
code--; code--;
} }
@ -227,8 +225,14 @@ bool HDRHandler::read(QImage *outImage)
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line; qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
return false; return false;
} }
const int width = match.captured(2).toInt();
const int height = match.captured(4).toInt(); if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file.";
return false;
}
const int width = match.captured(4).toInt();
const int height = match.captured(2).toInt();
QDataStream s(device()); QDataStream s(device());

734
src/imageformats/heif.cpp Normal file
View File

@ -0,0 +1,734 @@
/*
High Efficiency Image File Format (HEIF) support for QImage.
SPDX-FileCopyrightText: 2020 Sirius Bakke <sirius@bakke.co>
SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "heif_p.h"
#include "libheif/heif_cxx.h"
#include <QColorSpace>
#include <QDebug>
#include <QPointF>
#include <QSysInfo>
#include <string.h>
namespace // Private.
{
struct HeifQIODeviceWriter : public heif::Context::Writer {
HeifQIODeviceWriter(QIODevice *device)
: m_ioDevice(device)
{
}
heif_error write(const void *data, size_t size) override
{
heif_error error;
error.code = heif_error_Ok;
error.subcode = heif_suberror_Unspecified;
error.message = errorOkMessage;
qint64 bytesWritten = m_ioDevice->write(static_cast<const char *>(data), size);
if (bytesWritten < static_cast<qint64>(size)) {
error.code = heif_error_Encoding_error;
error.message = QIODeviceWriteErrorMessage;
error.subcode = heif_suberror_Cannot_write_output_data;
}
return error;
}
static constexpr const char *errorOkMessage = "Success";
static constexpr const char *QIODeviceWriteErrorMessage = "Bytes written to QIODevice are smaller than input data size";
private:
QIODevice *m_ioDevice;
};
} // namespace
HEIFHandler::HEIFHandler()
: m_parseState(ParseHeicNotParsed)
, m_quality(100)
{
}
bool HEIFHandler::canRead() const
{
if (m_parseState == ParseHeicNotParsed && !canRead(device())) {
return false;
}
if (m_parseState != ParseHeicError) {
setFormat("heif");
return true;
}
return false;
}
bool HEIFHandler::read(QImage *outImage)
{
if (!ensureParsed()) {
return false;
}
*outImage = m_current_image;
return true;
}
bool HEIFHandler::write(const QImage &image)
{
if (image.format() == QImage::Format_Invalid || image.isNull()) {
qWarning("No image data to save");
return false;
}
int save_depth; // 8 or 10bit per channel
QImage::Format tmpformat; // format for temporary image
const bool save_alpha = image.hasAlphaChannel();
switch (image.format()) {
case QImage::Format_BGR30:
case QImage::Format_A2BGR30_Premultiplied:
case QImage::Format_RGB30:
case QImage::Format_A2RGB30_Premultiplied:
case QImage::Format_Grayscale16:
case QImage::Format_RGBX64:
case QImage::Format_RGBA64:
case QImage::Format_RGBA64_Premultiplied:
save_depth = 10;
break;
default:
if (image.depth() > 32) {
save_depth = 10;
} else {
save_depth = 8;
}
break;
}
heif_chroma chroma;
if (save_depth > 8) {
if (save_alpha) {
tmpformat = QImage::Format_RGBA64;
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
} else {
tmpformat = QImage::Format_RGBX64;
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE;
}
} else {
if (save_alpha) {
tmpformat = QImage::Format_RGBA8888;
chroma = heif_chroma_interleaved_RGBA;
} else {
tmpformat = QImage::Format_RGB888;
chroma = heif_chroma_interleaved_RGB;
}
}
const QImage tmpimage = image.convertToFormat(tmpformat);
try {
heif::Context ctx;
heif::Image heifImage;
heifImage.create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma);
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);
int stride = 0;
uint8_t *const dst = heifImage.get_plane(heif_channel_interleaved, &stride);
size_t rowbytes;
switch (save_depth) {
case 10:
if (save_alpha) {
for (int y = 0; y < tmpimage.height(); y++) {
const uint16_t *src_word = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
uint16_t *dest_word = reinterpret_cast<uint16_t *>(dst + (y * stride));
for (int x = 0; x < tmpimage.width(); x++) {
int tmp_pixelval;
// R
tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
*dest_word = qBound(0, tmp_pixelval, 1023);
src_word++;
dest_word++;
// G
tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
*dest_word = qBound(0, tmp_pixelval, 1023);
src_word++;
dest_word++;
// B
tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
*dest_word = qBound(0, tmp_pixelval, 1023);
src_word++;
dest_word++;
// A
tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
*dest_word = qBound(0, tmp_pixelval, 1023);
src_word++;
dest_word++;
}
}
} else { // no alpha channel
for (int y = 0; y < tmpimage.height(); y++) {
const uint16_t *src_word = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
uint16_t *dest_word = reinterpret_cast<uint16_t *>(dst + (y * stride));
for (int x = 0; x < tmpimage.width(); x++) {
int tmp_pixelval;
// R
tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
*dest_word = qBound(0, tmp_pixelval, 1023);
src_word++;
dest_word++;
// G
tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
*dest_word = qBound(0, tmp_pixelval, 1023);
src_word++;
dest_word++;
// B
tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
*dest_word = qBound(0, tmp_pixelval, 1023);
src_word++;
dest_word++;
// X
src_word++;
}
}
}
break;
case 8:
rowbytes = save_alpha ? (tmpimage.width() * 4) : (tmpimage.width() * 3);
for (int y = 0; y < tmpimage.height(); y++) {
memcpy(dst + (y * stride), tmpimage.constScanLine(y), rowbytes);
}
break;
default:
qWarning() << "Unsupported depth:" << save_depth;
return false;
break;
}
heif::Encoder encoder(heif_compression_HEVC);
encoder.set_lossy_quality(m_quality);
if (m_quality > 90) {
if (m_quality == 100) {
encoder.set_lossless(true);
}
encoder.set_string_parameter("chroma", "444");
}
heif::Context::EncodingOptions encodingOptions;
encodingOptions.save_alpha_channel = save_alpha;
if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) {
qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations.";
if (save_alpha) {
// This helps to save alpha channel when image has odd dimension
encodingOptions.macOS_compatibility_workaround = 0;
}
}
ctx.encode_image(heifImage, encoder, encodingOptions);
HeifQIODeviceWriter writer(device());
ctx.write(writer);
} catch (const heif::Error &err) {
qWarning() << "libheif error:" << err.get_message().c_str();
return false;
}
return true;
}
bool HEIFHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("HEIFHandler::canRead() called with no device");
return false;
}
const QByteArray header = device->peek(28);
return HEIFHandler::isSupportedBMFFType(header);
}
bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
{
if (header.size() < 28) {
return false;
}
const char *buffer = header.constData();
if (qstrncmp(buffer + 4, "ftyp", 4) == 0) {
if (qstrncmp(buffer + 8, "heic", 4) == 0) {
return true;
}
if (qstrncmp(buffer + 8, "heis", 4) == 0) {
return true;
}
if (qstrncmp(buffer + 8, "heix", 4) == 0) {
return true;
}
/* we want to avoid loading AVIF files via this plugin */
if (qstrncmp(buffer + 8, "mif1", 4) == 0) {
for (int offset = 16; offset <= 24; offset += 4) {
if (qstrncmp(buffer + offset, "avif", 4) == 0) {
return false;
}
}
return true;
}
if (qstrncmp(buffer + 8, "mif2", 4) == 0) {
return true;
}
if (qstrncmp(buffer + 8, "msf1", 4) == 0) {
return true;
}
}
return false;
}
QVariant HEIFHandler::option(ImageOption option) const
{
if (option == Quality) {
return m_quality;
}
if (!supportsOption(option) || !ensureParsed()) {
return QVariant();
}
switch (option) {
case Size:
return m_current_image.size();
break;
default:
return QVariant();
break;
}
}
void HEIFHandler::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 = 100;
}
break;
default:
QImageIOHandler::setOption(option, value);
break;
}
}
bool HEIFHandler::supportsOption(ImageOption option) const
{
return option == Quality || option == Size;
}
bool HEIFHandler::ensureParsed() const
{
if (m_parseState == ParseHeicSuccess) {
return true;
}
if (m_parseState == ParseHeicError) {
return false;
}
HEIFHandler *that = const_cast<HEIFHandler *>(this);
return that->ensureDecoder();
}
bool HEIFHandler::ensureDecoder()
{
if (m_parseState != ParseHeicNotParsed) {
if (m_parseState == ParseHeicSuccess) {
return true;
}
return false;
}
const QByteArray buffer = device()->readAll();
if (!HEIFHandler::isSupportedBMFFType(buffer)) {
m_parseState = ParseHeicError;
return false;
}
try {
heif::Context ctx;
ctx.read_from_memory_without_copy((const void *)(buffer.constData()), buffer.size());
heif::ImageHandle handle = ctx.get_primary_image_handle();
const bool hasAlphaChannel = handle.has_alpha_channel();
const int bit_depth = handle.get_luma_bits_per_pixel();
heif_chroma chroma;
QImage::Format target_image_format;
if (bit_depth == 10 || bit_depth == 12) {
if (hasAlphaChannel) {
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
target_image_format = QImage::Format_RGBA64;
} else {
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE;
target_image_format = QImage::Format_RGBX64;
}
} else if (bit_depth == 8) {
if (hasAlphaChannel) {
chroma = heif_chroma_interleaved_RGBA;
target_image_format = QImage::Format_ARGB32;
} else {
chroma = heif_chroma_interleaved_RGB;
target_image_format = QImage::Format_RGB32;
}
} else {
m_parseState = ParseHeicError;
if (bit_depth > 0) {
qWarning() << "Unsupported bit depth:" << bit_depth;
} else {
qWarning() << "Undefined bit depth.";
}
return false;
}
heif::Image img = handle.decode_image(heif_colorspace_RGB, chroma);
const int imageWidth = img.get_width(heif_channel_interleaved);
const int imageHeight = img.get_height(heif_channel_interleaved);
QSize imageSize(imageWidth, imageHeight);
if (!imageSize.isValid()) {
m_parseState = ParseHeicError;
qWarning() << "HEIC image size invalid:" << imageSize;
return false;
}
int stride = 0;
const uint8_t *const src = img.get_plane(heif_channel_interleaved, &stride);
if (!src || stride <= 0) {
m_parseState = ParseHeicError;
qWarning() << "HEIC data pixels information not valid!";
return false;
}
m_current_image = QImage(imageSize, target_image_format);
if (m_current_image.isNull()) {
m_parseState = ParseHeicError;
qWarning() << "Unable to allocate memory!";
return false;
}
switch (bit_depth) {
case 12:
if (hasAlphaChannel) {
for (int y = 0; y < imageHeight; y++) {
const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
for (int x = 0; x < imageWidth; x++) {
int tmpvalue;
// R
tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// G
tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// B
tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// A
tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
}
}
} else { // no alpha channel
for (int y = 0; y < imageHeight; y++) {
const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
for (int x = 0; x < imageWidth; x++) {
int tmpvalue;
// R
tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// G
tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// B
tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// X = 0xffff
*dest_data = 0xffff;
dest_data++;
}
}
}
break;
case 10:
if (hasAlphaChannel) {
for (int y = 0; y < imageHeight; y++) {
const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
for (int x = 0; x < imageWidth; x++) {
int tmpvalue;
// R
tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// G
tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// B
tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// A
tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
}
}
} else { // no alpha channel
for (int y = 0; y < imageHeight; y++) {
const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
for (int x = 0; x < imageWidth; x++) {
int tmpvalue;
// R
tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// G
tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// B
tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
tmpvalue = qBound(0, tmpvalue, 65535);
*dest_data = (uint16_t)tmpvalue;
src_word++;
dest_data++;
// X = 0xffff
*dest_data = 0xffff;
dest_data++;
}
}
}
break;
case 8:
if (hasAlphaChannel) {
for (int y = 0; y < imageHeight; y++) {
const uint8_t *src_byte = src + (y * stride);
uint32_t *dest_pixel = reinterpret_cast<uint32_t *>(m_current_image.scanLine(y));
for (int x = 0; x < imageWidth; x++) {
int red = *src_byte++;
int green = *src_byte++;
int blue = *src_byte++;
int alpha = *src_byte++;
*dest_pixel = qRgba(red, green, blue, alpha);
dest_pixel++;
}
}
} else { // no alpha channel
for (int y = 0; y < imageHeight; y++) {
const uint8_t *src_byte = src + (y * stride);
uint32_t *dest_pixel = reinterpret_cast<uint32_t *>(m_current_image.scanLine(y));
for (int x = 0; x < imageWidth; x++) {
int red = *src_byte++;
int green = *src_byte++;
int blue = *src_byte++;
*dest_pixel = qRgb(red, green, blue);
dest_pixel++;
}
}
}
break;
default:
m_parseState = ParseHeicError;
qWarning() << "Unsupported bit depth:" << bit_depth;
return false;
break;
}
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle());
struct heif_error err;
if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
int rawProfileSize = (int)heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle());
if (rawProfileSize > 0) {
QByteArray ba(rawProfileSize, 0);
err = heif_image_handle_get_raw_color_profile(handle.get_raw_image_handle(), ba.data());
if (err.code) {
qWarning() << "icc profile loading failed";
} else {
m_current_image.setColorSpace(QColorSpace::fromIccProfile(ba));
if (!m_current_image.colorSpace().isValid()) {
qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!";
}
}
} else {
qWarning() << "icc profile is empty";
}
} else if (profileType == heif_color_profile_type_nclx) {
struct heif_color_profile_nclx *nclx = nullptr;
err = heif_image_handle_get_nclx_color_profile(handle.get_raw_image_handle(), &nclx);
if (err.code || !nclx) {
qWarning() << "nclx profile loading failed";
} else {
const QPointF redPoint(nclx->color_primary_red_x, nclx->color_primary_red_y);
const QPointF greenPoint(nclx->color_primary_green_x, nclx->color_primary_green_y);
const QPointF bluePoint(nclx->color_primary_blue_x, nclx->color_primary_blue_y);
const QPointF whitePoint(nclx->color_primary_white_x, nclx->color_primary_white_y);
QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom;
float q_trc_gamma = 0.0f;
switch (nclx->transfer_characteristics) {
case 4:
q_trc = QColorSpace::TransferFunction::Gamma;
q_trc_gamma = 2.2f;
break;
case 5:
q_trc = QColorSpace::TransferFunction::Gamma;
q_trc_gamma = 2.8f;
break;
case 8:
q_trc = QColorSpace::TransferFunction::Linear;
break;
case 2:
case 13:
q_trc = QColorSpace::TransferFunction::SRgb;
break;
default:
qWarning("CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
nclx->color_primaries,
nclx->transfer_characteristics);
q_trc = QColorSpace::TransferFunction::SRgb;
break;
}
if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt
switch (nclx->color_primaries) {
case 1:
case 2:
m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma));
break;
case 12:
m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma));
break;
default:
m_current_image.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma));
break;
}
}
heif_nclx_color_profile_free(nclx);
if (!m_current_image.colorSpace().isValid()) {
qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!";
}
}
} else {
m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb));
}
} catch (const heif::Error &err) {
m_parseState = ParseHeicError;
qWarning() << "libheif error:" << err.get_message().c_str();
return false;
}
m_parseState = ParseHeicSuccess;
return true;
}
QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "heif" || format == "heic") {
Capabilities format_cap;
if (heif_have_decoder_for_format(heif_compression_HEVC)) {
format_cap |= CanRead;
}
if (heif_have_encoder_for_format(heif_compression_HEVC)) {
format_cap |= CanWrite;
}
return format_cap;
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && HEIFHandler::canRead(device) && heif_have_decoder_for_format(heif_compression_HEVC)) {
cap |= CanRead;
}
if (device->isWritable() && heif_have_encoder_for_format(heif_compression_HEVC)) {
cap |= CanWrite;
}
return cap;
}
QImageIOHandler *HEIFPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new HEIFHandler;
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=heif
X-KDE-MimeType=image/heif
X-KDE-Read=true
X-KDE-Write=true

View File

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

58
src/imageformats/heif_p.h Normal file
View File

@ -0,0 +1,58 @@
/*
High Efficiency Image File Format (HEIF) support for QImage.
SPDX-FileCopyrightText: 2020 Sirius Bakke <sirius@bakke.co>
SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIMG_HEIF_P_H
#define KIMG_HEIF_P_H
#include <QByteArray>
#include <QImage>
#include <QImageIOPlugin>
class HEIFHandler : public QImageIOHandler
{
public:
HEIFHandler();
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;
private:
static bool isSupportedBMFFType(const QByteArray &header);
bool ensureParsed() const;
bool ensureDecoder();
enum ParseHeicState {
ParseHeicError = -1,
ParseHeicNotParsed = 0,
ParseHeicSuccess = 1,
};
ParseHeicState m_parseState;
int m_quality;
QImage m_current_image;
};
class HEIFPlugin : public QImageIOPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "heif.json")
public:
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
};
#endif // KIMG_HEIF_P_H

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

@ -0,0 +1,977 @@
/*
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>
#include <string.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 > 65535 || m_basicinfo.ysize > 65535) {
qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError;
return false;
}
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 > ((8192 * 8192) / m_basicinfo.ysize)) {
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;
}
} else {
/* On 64bit systems
* We skip images bigger than 16384 x 16384 pixels.
* It is an artificial limit not to use extreme amount of memory */
if (m_basicinfo.xsize > ((16384 * 16384) / m_basicinfo.ysize)) {
qWarning("JXL image (%dx%d) is bigger than security limit 256 megapixels", 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() > 0) && (image.height() > 0)) {
if ((image.width() > 65535) || (image.height() > 65535)) {
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
return false;
}
if (sizeof(void *) <= 4) {
if (image.width() > ((8192 * 8192) / image.height())) {
qWarning("Image (%dx%d) is too large save via 32bit build of JXL plug-in", image.width(), image.height());
return false;
}
} else {
if (image.width() > ((16384 * 16384) / image.height())) {
qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels", image.width(), image.height());
return false;
}
}
} else {
qWarning("Image has zero dimension!");
return false;
}
int save_depth = 8; // 8 or 16
// depth detection
switch (image.format()) {
case QImage::Format_BGR30:
case QImage::Format_A2BGR30_Premultiplied:
case QImage::Format_RGB30:
case QImage::Format_A2RGB30_Premultiplied:
case QImage::Format_Grayscale16:
case QImage::Format_RGBX64:
case QImage::Format_RGBA64:
case QImage::Format_RGBA64_Premultiplied:
save_depth = 16;
break;
case QImage::Format_RGB32:
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied:
case QImage::Format_RGB888:
case QImage::Format_RGBX8888:
case QImage::Format_RGBA8888:
case QImage::Format_RGBA8888_Premultiplied:
save_depth = 8;
break;
default:
if (image.depth() > 32) {
save_depth = 16;
} else {
save_depth = 8;
}
break;
}
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() && (m_quality < 100)) {
if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) {
convert_color_profile = true;
} else {
convert_color_profile = false;
}
} else { // lossless or no profile or Qt-unsupported ICC profile
convert_color_profile = false;
iccprofile = image.colorSpace().iccProfile();
if (iccprofile.size() > 0 || m_quality == 100) {
output_info.uses_original_profile = 1;
}
}
JxlPixelFormat pixel_format;
QImage::Format tmpformat;
JxlEncoderStatus status;
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;
output_info.animation.tps_denominator = 1;
if (save_depth > 8) { // 16bit depth
pixel_format.data_type = JXL_TYPE_UINT16;
output_info.bits_per_sample = 16;
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;
}
} else { // 8bit depth
pixel_format.data_type = JXL_TYPE_UINT8;
output_info.bits_per_sample = 8;
if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA8888;
pixel_format.num_channels = 4;
output_info.alpha_bits = 8;
output_info.num_extra_channels = 1;
} else {
tmpformat = QImage::Format_RGB888;
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 = (save_depth > 8) ? (2 * pixel_format.num_channels * xsize * ysize) : (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();
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() || ((save_depth == 8) && (xsize % 4 == 0))) {
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size);
} else {
if (save_depth > 8) { // 16bit depth without alpha channel
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;
} else { // 8bit depth without alpha channel
uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
if (!tmp_buffer8) {
qWarning("Memory allocation error");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
uchar *dest_pixels8 = tmp_buffer8;
const size_t rowbytes = 3 * xsize;
for (int y = 0; y < tmpimage.height(); y++) {
memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
dest_pixels8 += rowbytes;
}
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer8, buffer_size);
delete[] tmp_buffer8;
}
}
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

@ -12,9 +12,9 @@
#include <kzip.h> #include <kzip.h>
#include <QImage>
#include <QIODevice>
#include <QFile> #include <QFile>
#include <QIODevice>
#include <QImage>
static constexpr char s_magic[] = "application/x-krita"; 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_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0
@ -35,12 +35,16 @@ bool KraHandler::canRead() const
bool KraHandler::read(QImage *image) bool KraHandler::read(QImage *image)
{ {
KZip zip(device()); KZip zip(device());
if (!zip.open(QIODevice::ReadOnly)) return false; if (!zip.open(QIODevice::ReadOnly)) {
return false;
}
const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png")); const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png"));
if (!entry || !entry->isFile()) return false; if (!entry || !entry->isFile()) {
return false;
}
const KZipFileEntry* fileZipEntry = static_cast<const KZipFileEntry*>(entry); const KZipFileEntry *fileZipEntry = static_cast<const KZipFileEntry *>(entry);
image->loadFromData(fileZipEntry->data(), "PNG"); image->loadFromData(fileZipEntry->data(), "PNG");
@ -55,8 +59,9 @@ bool KraHandler::canRead(QIODevice *device)
} }
char buff[57]; char buff[57];
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
return memcmp(buff + 0x26, s_magic, s_magic_size) == 0; return memcmp(buff + 0x26, s_magic, s_magic_size) == 0;
}
return false; return false;
} }

View File

@ -16,7 +16,7 @@ public:
KraHandler(); KraHandler();
bool canRead() const override; bool canRead() const override;
bool read(QImage *image) override; bool read(QImage *image) override;
static bool canRead(QIODevice *device); static bool canRead(QIODevice *device);
}; };
@ -31,6 +31,4 @@ public:
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override; QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
}; };
#endif #endif

View File

@ -34,12 +34,16 @@ bool OraHandler::canRead() const
bool OraHandler::read(QImage *image) bool OraHandler::read(QImage *image)
{ {
KZip zip(device()); KZip zip(device());
if (!zip.open(QIODevice::ReadOnly)) return false; if (!zip.open(QIODevice::ReadOnly)) {
return false;
}
const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png")); const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png"));
if (!entry || !entry->isFile()) return false; if (!entry || !entry->isFile()) {
return false;
}
const KZipFileEntry* fileZipEntry = static_cast<const KZipFileEntry*>(entry); const KZipFileEntry *fileZipEntry = static_cast<const KZipFileEntry *>(entry);
image->loadFromData(fileZipEntry->data(), "PNG"); image->loadFromData(fileZipEntry->data(), "PNG");
@ -54,8 +58,9 @@ bool OraHandler::canRead(QIODevice *device)
} }
char buff[54]; char buff[54];
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
return memcmp(buff + 0x26, s_magic, s_magic_size) == 0; return memcmp(buff + 0x26, s_magic, s_magic_size) == 0;
}
return false; return false;
} }

View File

@ -16,12 +16,11 @@ public:
OraHandler(); OraHandler();
bool canRead() const override; bool canRead() const override;
bool read(QImage *image) override; bool read(QImage *image) override;
static bool canRead(QIODevice *device); static bool canRead(QIODevice *device);
}; };
class OraPlugin : public QImageIOPlugin class OraPlugin : public QImageIOPlugin
{ {
Q_OBJECT Q_OBJECT
@ -31,6 +30,4 @@ public:
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override; QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
}; };
#endif #endif

View File

@ -12,8 +12,7 @@
#include <QDebug> #include <QDebug>
#include <QImage> #include <QImage>
#pragma pack(push, 1)
#pragma pack(push,1)
class RGB class RGB
{ {
public: public:
@ -29,7 +28,6 @@ public:
c.b = qBlue(color); c.b = qBlue(color);
return c; return c;
} }
}; };
class Palette class Palette
@ -37,7 +35,7 @@ class Palette
public: public:
void setColor(int i, const QRgb color) void setColor(int i, const QRgb color)
{ {
RGB &c = rgb[ i ]; RGB &c = rgb[i];
c.r = qRed(color); c.r = qRed(color);
c.g = qGreen(color); c.g = qGreen(color);
c.b = qBlue(color); c.b = qBlue(color);
@ -45,10 +43,10 @@ public:
QRgb color(int i) const QRgb color(int i) const
{ {
return qRgb(rgb[ i ].r, rgb[ i ].g, rgb[ i ].b); return qRgb(rgb[i].r, rgb[i].g, rgb[i].b);
} }
class RGB rgb[ 16 ]; class RGB rgb[16];
}; };
class PCXHEADER class PCXHEADER
@ -69,8 +67,8 @@ public:
return (Encoding == 1); return (Encoding == 1);
} }
quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx
quint8 Version; // Version information· quint8 Version; // Version information·
// 0 = Version 2.5 of PC Paintbrush· // 0 = Version 2.5 of PC Paintbrush·
// 2 = Version 2.8 w/palette information· // 2 = Version 2.8 w/palette information·
// 3 = Version 2.8 w/o palette information· // 3 = Version 2.8 w/o palette information·
@ -80,8 +78,8 @@ public:
// and PC Paintbrush +, includes // and PC Paintbrush +, includes
// Publisher's Paintbrush . Includes // Publisher's Paintbrush . Includes
// 24-bit .PCX files· // 24-bit .PCX files·
quint8 Encoding; // 1 = .PCX run length encoding quint8 Encoding; // 1 = .PCX run length encoding
quint8 Bpp; // Number of bits to represent a pixel quint8 Bpp; // Number of bits to represent a pixel
// (per Plane) - 1, 2, 4, or 8· // (per Plane) - 1, 2, 4, or 8·
quint16 XMin; quint16 XMin;
quint16 YMin; quint16 YMin;
@ -89,17 +87,17 @@ public:
quint16 YMax; quint16 YMax;
quint16 HDpi; quint16 HDpi;
quint16 YDpi; quint16 YDpi;
Palette ColorMap; Palette ColorMap;
quint8 Reserved; // Should be set to 0. quint8 Reserved; // Should be set to 0.
quint8 NPlanes; // Number of color planes quint8 NPlanes; // Number of color planes
quint16 BytesPerLine; // Number of bytes to allocate for a scanline quint16 BytesPerLine; // Number of bytes to allocate for a scanline
// plane. MUST be an EVEN number. Do NOT // plane. MUST be an EVEN number. Do NOT
// calculate from Xmax-Xmin.· // calculate from Xmax-Xmin.·
quint16 PaletteInfo; // How to interpret palette- 1 = Color/BW, quint16 PaletteInfo; // How to interpret palette- 1 = Color/BW,
// 2 = Grayscale ( ignored in PB IV/ IV + )· // 2 = Grayscale ( ignored in PB IV/ IV + )·
quint16 HScreenSize; // Horizontal screen size in pixels. New field quint16 HScreenSize; // Horizontal screen size in pixels. New field
// found only in PB IV/IV Plus // found only in PB IV/IV Plus
quint16 VScreenSize; // Vertical screen size in pixels. New field quint16 VScreenSize; // Vertical screen size in pixels. New field
// found only in PB IV/IV Plus // found only in PB IV/IV Plus
}; };
@ -107,7 +105,9 @@ public:
static QDataStream &operator>>(QDataStream &s, RGB &rgb) static QDataStream &operator>>(QDataStream &s, RGB &rgb)
{ {
quint8 r, g, b; quint8 r;
quint8 g;
quint8 b;
s >> r >> g >> b; s >> r >> g >> b;
rgb.r = r; rgb.r = r;
@ -120,7 +120,7 @@ static QDataStream &operator>>(QDataStream &s, RGB &rgb)
static QDataStream &operator>>(QDataStream &s, Palette &pal) static QDataStream &operator>>(QDataStream &s, Palette &pal)
{ {
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
s >> pal.rgb[ i ]; s >> pal.rgb[i];
} }
return s; return s;
@ -128,35 +128,48 @@ static QDataStream &operator>>(QDataStream &s, Palette &pal)
static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph) 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; s >> m >> ver >> enc >> bpp;
ph.Manufacturer = m; ph.Manufacturer = m;
ph.Version = ver; ph.Version = ver;
ph.Encoding = enc; ph.Encoding = enc;
ph.Bpp = bpp; ph.Bpp = bpp;
quint16 xmin, ymin, xmax, ymax; quint16 xmin;
quint16 ymin;
quint16 xmax;
quint16 ymax;
s >> xmin >> ymin >> xmax >> ymax; s >> xmin >> ymin >> xmax >> ymax;
ph.XMin = xmin; ph.XMin = xmin;
ph.YMin = ymin; ph.YMin = ymin;
ph.XMax = xmax; ph.XMax = xmax;
ph.YMax = ymax; ph.YMax = ymax;
quint16 hdpi, ydpi; quint16 hdpi;
quint16 ydpi;
s >> hdpi >> ydpi; s >> hdpi >> ydpi;
ph.HDpi = hdpi; ph.HDpi = hdpi;
ph.YDpi = ydpi; ph.YDpi = ydpi;
Palette colorMap; Palette colorMap;
quint8 res, np; quint8 res;
quint8 np;
s >> colorMap >> res >> np; s >> colorMap >> res >> np;
ph.ColorMap = colorMap; ph.ColorMap = colorMap;
ph.Reserved = res; ph.Reserved = res;
ph.NPlanes = np; ph.NPlanes = np;
quint16 bytesperline; quint16 bytesperline;
s >> bytesperline; ph.BytesPerLine = bytesperline; s >> bytesperline;
ph.BytesPerLine = bytesperline;
quint16 paletteinfo; quint16 paletteinfo;
s >> paletteinfo; ph.PaletteInfo = paletteinfo; s >> paletteinfo;
quint16 hscreensize, vscreensize; ph.PaletteInfo = paletteinfo;
s >> hscreensize; ph.HScreenSize = hscreensize; quint16 hscreensize;
s >> vscreensize; ph.VScreenSize = vscreensize; quint16 vscreensize;
s >> hscreensize;
ph.HScreenSize = hscreensize;
s >> vscreensize;
ph.VScreenSize = vscreensize;
// Skip the rest of the header // Skip the rest of the header
quint8 byte; quint8 byte;
@ -177,7 +190,7 @@ static QDataStream &operator<<(QDataStream &s, const RGB rgb)
static QDataStream &operator<<(QDataStream &s, const Palette &pal) static QDataStream &operator<<(QDataStream &s, const Palette &pal)
{ {
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
s << pal.rgb[ i ]; s << pal.rgb[i];
} }
return s; return s;
@ -220,7 +233,8 @@ static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
{ {
quint32 i = 0; quint32 i = 0;
quint32 size = buf.size(); quint32 size = buf.size();
quint8 byte, count; quint8 byte;
quint8 count;
if (header.isCompressed()) { if (header.isCompressed()) {
// Uncompress the image data // Uncompress the image data
@ -232,14 +246,14 @@ static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
s >> byte; s >> byte;
} }
while (count-- && i < size) { while (count-- && i < size) {
buf[ i++ ] = byte; buf[i++] = byte;
} }
} }
} else { } else {
// Image is not compressed (possible?) // Image is not compressed (possible?)
while (i < size) { while (i < size) {
s >> byte; s >> byte;
buf[ i++ ] = byte; buf[i++] = byte;
} }
} }
} }
@ -266,7 +280,7 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
uchar *p = img.scanLine(y); uchar *p = img.scanLine(y);
unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine); unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
for (unsigned int x = 0; x < bpl; ++x) { for (unsigned int x = 0; x < bpl; ++x) {
p[ x ] = buf[x]; p[x] = buf[x];
} }
} }
@ -298,10 +312,11 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
quint32 offset = i * header.BytesPerLine; 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))) { if (buf[offset + (x / 8)] & (128 >> (x % 8))) {
pixbuf[ x ] = (int)(pixbuf[ x ]) + (1 << i); pixbuf[x] = (int)(pixbuf[x]) + (1 << i);
} }
}
} }
uchar *p = img.scanLine(y); uchar *p = img.scanLine(y);
@ -309,7 +324,7 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
qWarning() << "Failed to get scanline for" << y << "might be out of bounds"; qWarning() << "Failed to get scanline for" << y << "might be out of bounds";
} }
for (int x = 0; x < header.width(); ++x) { for (int x = 0; x < header.width(); ++x) {
p[ x ] = pixbuf[ x ]; p[x] = pixbuf[x];
} }
} }
@ -341,22 +356,25 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
uchar *p = img.scanLine(y); uchar *p = img.scanLine(y);
if (!p) if (!p) {
return; return;
}
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width()); unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
for (unsigned int x = 0; x < bpl; ++x) { for (unsigned int x = 0; x < bpl; ++x) {
p[ x ] = buf[ x ]; p[x] = buf[x];
} }
} }
quint8 flag; quint8 flag;
s >> flag; s >> flag;
// qDebug() << "Palette Flag: " << flag; // qDebug() << "Palette Flag: " << flag;
if (flag == 12 && (header.Version == 5 || header.Version == 2)) { if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
// Read the palette // Read the palette
quint8 r, g, b; quint8 r;
quint8 g;
quint8 b;
for (int i = 0; i < 256; ++i) { for (int i = 0; i < 256; ++i) {
s >> r >> g >> b; s >> r >> g >> b;
img.setColor(i, qRgb(r, g, b)); img.setColor(i, qRgb(r, g, b));
@ -389,7 +407,7 @@ static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
uint *p = (uint *)img.scanLine(y); uint *p = (uint *)img.scanLine(y);
for (int x = 0; x < header.width(); ++x) { for (int x = 0; x < header.width(); ++x) {
p[ x ] = qRgb(r_buf[ x ], g_buf[ x ], b_buf[ x ]); p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
} }
} }
} }
@ -398,14 +416,15 @@ static void writeLine(QDataStream &s, QByteArray &buf)
{ {
quint32 i = 0; quint32 i = 0;
quint32 size = buf.size(); quint32 size = buf.size();
quint8 count, data; quint8 count;
quint8 data;
char byte; char byte;
while (i < size) { while (i < size) {
count = 1; count = 1;
byte = buf[ i++ ]; byte = buf[i++];
while ((i < size) && (byte == buf[ i ]) && (count < 63)) { while ((i < size) && (byte == buf[i]) && (count < 63)) {
++i; ++i;
++count; ++count;
} }
@ -438,7 +457,7 @@ static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
// Invert as QImage uses reverse palette for monochrome images? // Invert as QImage uses reverse palette for monochrome images?
for (int i = 0; i < header.BytesPerLine; ++i) { for (int i = 0; i < header.BytesPerLine; ++i) {
buf[ i ] = ~p[ i ]; buf[i] = ~p[i];
} }
writeLine(s, buf); writeLine(s, buf);
@ -457,28 +476,29 @@ static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
s << header; s << header;
QByteArray buf[ 4 ]; QByteArray buf[4];
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
buf[ i ].resize(header.BytesPerLine); buf[i].resize(header.BytesPerLine);
} }
for (int y = 0; y < header.height(); ++y) { for (int y = 0; y < header.height(); ++y) {
quint8 *p = img.scanLine(y); quint8 *p = img.scanLine(y);
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
buf[ i ].fill(0); buf[i].fill(0);
} }
for (int x = 0; x < header.width(); ++x) { 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)) { if (*(p + x) & (1 << i)) {
buf[ i ][ x / 8 ] = (int)(buf[ i ][ x / 8 ]) | 1 << (7 - x % 8); buf[i][x / 8] = (int)(buf[i][x / 8]) | 1 << (7 - x % 8);
} }
}
} }
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
writeLine(s, buf[ i ]); writeLine(s, buf[i]);
} }
} }
} }
@ -497,7 +517,7 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
quint8 *p = img.scanLine(y); quint8 *p = img.scanLine(y);
for (int i = 0; i < header.BytesPerLine; ++i) { for (int i = 0; i < header.BytesPerLine; ++i) {
buf[ i ] = p[ i ]; buf[i] = p[i];
} }
writeLine(s, buf); writeLine(s, buf);
@ -530,9 +550,9 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
for (int x = 0; x < header.width(); ++x) { for (int x = 0; x < header.width(); ++x) {
QRgb rgb = *p++; QRgb rgb = *p++;
r_buf[ x ] = qRed(rgb); r_buf[x] = qRed(rgb);
g_buf[ x ] = qGreen(rgb); g_buf[x] = qGreen(rgb);
b_buf[ x ] = qBlue(rgb); b_buf[x] = qBlue(rgb);
} }
writeLine(s, r_buf); writeLine(s, r_buf);
@ -571,19 +591,19 @@ bool PCXHandler::read(QImage *outImage)
return false; return false;
} }
// int w = header.width(); // int w = header.width();
// int h = header.height(); // int h = header.height();
// qDebug() << "Manufacturer: " << header.Manufacturer; // qDebug() << "Manufacturer: " << header.Manufacturer;
// qDebug() << "Version: " << header.Version; // qDebug() << "Version: " << header.Version;
// qDebug() << "Encoding: " << header.Encoding; // qDebug() << "Encoding: " << header.Encoding;
// qDebug() << "Bpp: " << header.Bpp; // qDebug() << "Bpp: " << header.Bpp;
// qDebug() << "Width: " << w; // qDebug() << "Width: " << w;
// qDebug() << "Height: " << h; // qDebug() << "Height: " << h;
// qDebug() << "Window: " << header.XMin << "," << header.XMax << "," // qDebug() << "Window: " << header.XMin << "," << header.XMax << ","
// << header.YMin << "," << header.YMax << endl; // << header.YMin << "," << header.YMax << endl;
// qDebug() << "BytesPerLine: " << header.BytesPerLine; // qDebug() << "BytesPerLine: " << header.BytesPerLine;
// qDebug() << "NPlanes: " << header.NPlanes; // qDebug() << "NPlanes: " << header.NPlanes;
QImage img; QImage img;
@ -597,9 +617,9 @@ bool PCXHandler::read(QImage *outImage)
readImage24(img, s, header); readImage24(img, s, header);
} }
// qDebug() << "Image Bytes: " << img.numBytes(); // qDebug() << "Image Bytes: " << img.numBytes();
// qDebug() << "Image Bytes Per Line: " << img.bytesPerLine(); // qDebug() << "Image Bytes Per Line: " << img.bytesPerLine();
// qDebug() << "Image Depth: " << img.depth(); // qDebug() << "Image Depth: " << img.depth();
if (!img.isNull()) { if (!img.isNull()) {
*outImage = img; *outImage = img;
@ -616,14 +636,18 @@ bool PCXHandler::write(const QImage &image)
QImage img = image; QImage img = image;
int w = img.width(); const int w = img.width();
int h = img.height(); const int h = img.height();
// qDebug() << "Width: " << w; if (w > 65536 || h > 65536) {
// qDebug() << "Height: " << h; return false;
// qDebug() << "Depth: " << img.depth(); }
// qDebug() << "BytesPerLine: " << img.bytesPerLine();
// qDebug() << "Color Count: " << img.colorCount(); // qDebug() << "Width: " << w;
// qDebug() << "Height: " << h;
// qDebug() << "Depth: " << img.depth();
// qDebug() << "BytesPerLine: " << img.bytesPerLine();
// qDebug() << "Color Count: " << img.colorCount();
PCXHEADER header; PCXHEADER header;

View File

@ -21,9 +21,10 @@
#include <QDebug> #include <QDebug>
#include <QImage> #include <QImage>
#include <QVariant> #include <QVariant>
#include <qendian.h>
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include <qendian.h>
#include <utility>
/** /**
* Reads a PIC file header from a data stream. * Reads a PIC file header from a data stream.
@ -34,7 +35,7 @@
* *
* @relates PicHeader * @relates PicHeader
*/ */
static QDataStream &operator>> (QDataStream &s, PicHeader &header) static QDataStream &operator>>(QDataStream &s, PicHeader &header)
{ {
s.setFloatingPointPrecision(QDataStream::SinglePrecision); s.setFloatingPointPrecision(QDataStream::SinglePrecision);
s >> header.magic; s >> header.magic;
@ -71,7 +72,7 @@ static QDataStream &operator>> (QDataStream &s, PicHeader &header)
* *
* @relates PicHeader * @relates PicHeader
*/ */
static QDataStream &operator<< (QDataStream &s, const PicHeader &header) static QDataStream &operator<<(QDataStream &s, const PicHeader &header)
{ {
s.setFloatingPointPrecision(QDataStream::SinglePrecision); s.setFloatingPointPrecision(QDataStream::SinglePrecision);
s << header.magic; s << header.magic;
@ -107,7 +108,7 @@ static QDataStream &operator<< (QDataStream &s, const PicHeader &header)
* *
* @relates PicChannel * @relates PicChannel
*/ */
static QDataStream &operator>> (QDataStream &s, QList<PicChannel> &channels) static QDataStream &operator>>(QDataStream &s, QList<PicChannel> &channels)
{ {
const unsigned maxChannels = 8; const unsigned maxChannels = 8;
unsigned count = 0; unsigned count = 0;
@ -142,7 +143,7 @@ static QDataStream &operator>> (QDataStream &s, QList<PicChannel> &channels)
* *
* @relates PicChannel * @relates PicChannel
*/ */
static QDataStream &operator<< (QDataStream &s, const QList<PicChannel> &channels) static QDataStream &operator<<(QDataStream &s, const QList<PicChannel> &channels)
{ {
Q_ASSERT(channels.size() > 0); Q_ASSERT(channels.size() > 0);
for (int i = 0; i < channels.size() - 1; ++i) { for (int i = 0; i < channels.size() - 1; ++i) {
@ -160,8 +161,8 @@ static QDataStream &operator<< (QDataStream &s, const QList<PicChannel> &channel
static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<PicChannel> &channels) static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<PicChannel> &channels)
{ {
for(const PicChannel &channel : channels) { for (const PicChannel &channel : channels) {
auto readPixel = [&] (QDataStream &str) -> QRgb { auto readPixel = [&](QDataStream &str) -> QRgb {
quint8 red = 0; quint8 red = 0;
if (channel.code & RED) { if (channel.code & RED) {
str >> red; str >> red;
@ -180,16 +181,14 @@ static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<P
} }
return qRgba(red, green, blue, alpha); return qRgba(red, green, blue, alpha);
}; };
auto updatePixel = [&] (QRgb oldPixel, QRgb newPixel) -> QRgb { auto updatePixel = [&](QRgb oldPixel, QRgb newPixel) -> QRgb {
return qRgba( return qRgba(qRed((channel.code & RED) ? newPixel : oldPixel),
qRed((channel.code & RED) ? newPixel : oldPixel), qGreen((channel.code & GREEN) ? newPixel : oldPixel),
qGreen((channel.code & GREEN) ? newPixel : oldPixel), qBlue((channel.code & BLUE) ? newPixel : oldPixel),
qBlue((channel.code & BLUE) ? newPixel : oldPixel), qAlpha((channel.code & ALPHA) ? newPixel : oldPixel));
qAlpha((channel.code & ALPHA) ? newPixel : oldPixel));
}; };
if (channel.encoding == MixedRLE) { if (channel.encoding == MixedRLE) {
bool success = decodeRLEData(RLEVariant::PIC, stream, row, width, bool success = decodeRLEData(RLEVariant::PIC, stream, row, width, readPixel, updatePixel);
readPixel, updatePixel);
if (!success) { if (!success) {
qDebug() << "decodeRLEData failed"; qDebug() << "decodeRLEData failed";
return false; return false;
@ -227,7 +226,7 @@ bool SoftimagePICHandler::read(QImage *image)
} }
QImage::Format fmt = QImage::Format_RGB32; 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) { if (channel.size != 8) {
// we cannot read images that do not come in bytes // we cannot read images that do not come in bytes
qDebug() << "Channel size was" << channel.size; qDebug() << "Channel size was" << channel.size;
@ -245,10 +244,10 @@ bool SoftimagePICHandler::read(QImage *image)
return false; return false;
} }
img.fill(qRgb(0,0,0)); img.fill(qRgb(0, 0, 0));
for (int y = 0; y < m_header.height; y++) { for (int y = 0; y < m_header.height; y++) {
QRgb *row = reinterpret_cast<QRgb*>(img.scanLine(y)); QRgb *row = reinterpret_cast<QRgb *>(img.scanLine(y));
if (!readRow(m_dataStream, row, m_header.width, m_channels)) { if (!readRow(m_dataStream, row, m_header.width, m_channels)) {
qDebug() << "readRow failed"; qDebug() << "readRow failed";
m_state = Error; m_state = Error;
@ -265,9 +264,7 @@ bool SoftimagePICHandler::read(QImage *image)
bool SoftimagePICHandler::write(const QImage &_image) bool SoftimagePICHandler::write(const QImage &_image)
{ {
bool alpha = _image.hasAlphaChannel(); bool alpha = _image.hasAlphaChannel();
const QImage image = _image.convertToFormat( const QImage image = _image.convertToFormat(alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
alpha ? QImage::Format_ARGB32
: QImage::Format_RGB32);
if (image.width() < 0 || image.height() < 0) { if (image.width() < 0 || image.height() < 0) {
qDebug() << "Image size invalid:" << image.width() << image.height(); qDebug() << "Image size invalid:" << image.width() << image.height();
@ -292,22 +289,17 @@ bool SoftimagePICHandler::write(const QImage &_image)
stream << channels; stream << channels;
for (int r = 0; r < image.height(); r++) { for (int r = 0; r < image.height(); r++) {
const QRgb *row = reinterpret_cast<const QRgb*>(image.scanLine(r)); const QRgb *row = reinterpret_cast<const QRgb *>(image.scanLine(r));
/* Write the RGB part of the scanline */ /* Write the RGB part of the scanline */
auto rgbEqual = [] (QRgb p1, QRgb p2) -> bool { auto rgbEqual = [](QRgb p1, QRgb p2) -> bool {
return qRed(p1) == qRed(p2) && return qRed(p1) == qRed(p2) && qGreen(p1) == qGreen(p2) && qBlue(p1) == qBlue(p2);
qGreen(p1) == qGreen(p2) &&
qBlue(p1) == qBlue(p2);
}; };
auto writeRgb = [] (QDataStream &str, QRgb pixel) -> void { auto writeRgb = [](QDataStream &str, QRgb pixel) -> void {
str << quint8(qRed(pixel)) str << quint8(qRed(pixel)) << quint8(qGreen(pixel)) << quint8(qBlue(pixel));
<< quint8(qGreen(pixel))
<< quint8(qBlue(pixel));
}; };
if (m_compression) { if (m_compression) {
encodeRLEData(RLEVariant::PIC, stream, row, image.width(), encodeRLEData(RLEVariant::PIC, stream, row, image.width(), rgbEqual, writeRgb);
rgbEqual, writeRgb);
} else { } else {
for (int i = 0; i < image.width(); ++i) { for (int i = 0; i < image.width(); ++i) {
writeRgb(stream, row[i]); writeRgb(stream, row[i]);
@ -316,15 +308,14 @@ bool SoftimagePICHandler::write(const QImage &_image)
/* Write the alpha channel */ /* Write the alpha channel */
if (alpha) { if (alpha) {
auto alphaEqual = [] (QRgb p1, QRgb p2) -> bool { auto alphaEqual = [](QRgb p1, QRgb p2) -> bool {
return qAlpha(p1) == qAlpha(p2); return qAlpha(p1) == qAlpha(p2);
}; };
auto writeAlpha = [] (QDataStream &str, QRgb pixel) -> void { auto writeAlpha = [](QDataStream &str, QRgb pixel) -> void {
str << quint8(qAlpha(pixel)); str << quint8(qAlpha(pixel));
}; };
if (m_compression) { if (m_compression) {
encodeRLEData(RLEVariant::PIC, stream, row, image.width(), encodeRLEData(RLEVariant::PIC, stream, row, image.width(), alphaEqual, writeAlpha);
alphaEqual, writeAlpha);
} else { } else {
for (int i = 0; i < image.width(); ++i) { for (int i = 0; i < image.width(); ++i) {
writeAlpha(stream, row[i]); writeAlpha(stream, row[i]);
@ -341,7 +332,7 @@ bool SoftimagePICHandler::canRead(QIODevice *device)
if (device->peek(data, 4) != 4) { if (device->peek(data, 4) != 4) {
return false; return false;
} }
return qFromBigEndian<qint32>(reinterpret_cast<uchar*>(data)) == PIC_MAGIC_NUMBER; return qFromBigEndian<qint32>(reinterpret_cast<uchar *>(data)) == PIC_MAGIC_NUMBER;
} }
bool SoftimagePICHandler::readHeader() bool SoftimagePICHandler::readHeader()
@ -374,67 +365,62 @@ bool SoftimagePICHandler::readChannels()
void SoftimagePICHandler::setOption(ImageOption option, const QVariant &value) void SoftimagePICHandler::setOption(ImageOption option, const QVariant &value)
{ {
switch (option) { switch (option) {
case CompressionRatio: case CompressionRatio:
m_compression = value.toBool(); m_compression = value.toBool();
break; break;
case Description: { case Description: {
m_description.clear(); m_description.clear();
const QStringList entries = value.toString().split(QStringLiteral("\n\n")); const QStringList entries = value.toString().split(QStringLiteral("\n\n"));
for (const QString &entry : entries) { for (const QString &entry : entries) {
if (entry.startsWith(QStringLiteral("Description: "))) { if (entry.startsWith(QStringLiteral("Description: "))) {
m_description = entry.mid(13).simplified().toUtf8(); m_description = entry.mid(13).simplified().toUtf8();
}
} }
break;
} }
default: break;
break; }
default:
break;
} }
} }
QVariant SoftimagePICHandler::option(ImageOption option) const QVariant SoftimagePICHandler::option(ImageOption option) const
{ {
const_cast<SoftimagePICHandler*>(this)->readHeader(); const_cast<SoftimagePICHandler *>(this)->readHeader();
switch (option) { switch (option) {
case Size: case Size:
if (const_cast<SoftimagePICHandler*>(this)->readHeader()) { if (const_cast<SoftimagePICHandler *>(this)->readHeader()) {
return QSize(m_header.width, m_header.height); return QSize(m_header.width, m_header.height);
} else { } else {
return QVariant(); return QVariant();
}
case CompressionRatio:
return m_compression;
case Description:
if (const_cast<SoftimagePICHandler *>(this)->readHeader()) {
QString descStr = QString::fromUtf8(m_header.comment);
if (!descStr.isEmpty()) {
return QString(QStringLiteral("Description: ") + descStr + QStringLiteral("\n\n"));
} }
case CompressionRatio: }
return m_compression; return QString();
case Description: case ImageFormat:
if (const_cast<SoftimagePICHandler*>(this)->readHeader()) { if (const_cast<SoftimagePICHandler *>(this)->readChannels()) {
QString descStr = QString::fromUtf8(m_header.comment); for (const PicChannel &channel : std::as_const(m_channels)) {
if (!descStr.isEmpty()) { if (channel.code & ALPHA) {
return QString(QStringLiteral("Description: ") + return QImage::Format_ARGB32;
descStr +
QStringLiteral("\n\n"));
} }
} }
return QString(); return QImage::Format_RGB32;
case ImageFormat: }
if (const_cast<SoftimagePICHandler*>(this)->readChannels()) { return QVariant();
for (const PicChannel &channel : qAsConst(m_channels)) { default:
if (channel.code & ALPHA) { return QVariant();
return QImage::Format_ARGB32;
}
}
return QImage::Format_RGB32;
}
return QVariant();
default:
return QVariant();
} }
} }
bool SoftimagePICHandler::supportsOption(ImageOption option) const bool SoftimagePICHandler::supportsOption(ImageOption option) const
{ {
return (option == CompressionRatio || return (option == CompressionRatio || option == Description || option == ImageFormat || option == Size);
option == Description ||
option == ImageFormat ||
option == Size);
} }
QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const

View File

@ -8,8 +8,8 @@
#ifndef KIMG_PIC_P_H #ifndef KIMG_PIC_P_H
#define KIMG_PIC_P_H #define KIMG_PIC_P_H
#include <QImageIOPlugin>
#include <QDataStream> #include <QDataStream>
#include <QImageIOPlugin>
/** /**
* The magic number at the start of a SoftImage PIC file. * The magic number at the start of a SoftImage PIC file.
@ -25,7 +25,7 @@ enum PicFields {
NoPicture = 0, /**< No picture */ NoPicture = 0, /**< No picture */
OddScanlines = 1, /**< Odd scanlines */ OddScanlines = 1, /**< Odd scanlines */
EvenScanlines = 2, /**< Even scanlines */ EvenScanlines = 2, /**< Even scanlines */
BothScanlines = 3 /**< Every scanline */ BothScanlines = 3, /**< Every scanline */
}; };
/** /**
@ -33,7 +33,7 @@ enum PicFields {
*/ */
enum PicChannelEncoding { enum PicChannelEncoding {
Uncompressed = 0, /**< Image is uncompressed */ Uncompressed = 0, /**< Image is uncompressed */
MixedRLE = 2 /**< Run length compression */ MixedRLE = 2, /**< Run length compression */
}; };
/** /**
@ -43,7 +43,7 @@ enum PicChannelCode {
RED = 0x80, /**< Red channel */ RED = 0x80, /**< Red channel */
GREEN = 0x40, /**< Green channel */ GREEN = 0x40, /**< Green channel */
BLUE = 0x20, /**< Blue channel */ BLUE = 0x20, /**< Blue channel */
ALPHA = 0x10 /**< Alpha channel */ ALPHA = 0x10, /**< Alpha channel */
}; };
/** /**
@ -70,9 +70,12 @@ struct PicHeader {
, height(_height) , height(_height)
, ratio(1.0f) , ratio(1.0f)
, fields(BothScanlines) , fields(BothScanlines)
{} {
}
/** Construct an invalid header. */ /** Construct an invalid header. */
PicHeader() {} PicHeader()
{
}
quint32 magic; /**< Should be PIC_MAGIC_NUMBER */ quint32 magic; /**< Should be PIC_MAGIC_NUMBER */
float version; /**< Version of something (header? file format?) (ignored) */ float version; /**< Version of something (header? file format?) (ignored) */
@ -88,9 +91,9 @@ struct PicHeader {
/** /**
* Returns true if the @p magic and @p id fields are set correctly. * Returns true if the @p magic and @p id fields are set correctly.
*/ */
bool isValid() const { bool isValid() const
return magic == PIC_MAGIC_NUMBER {
&& id == "PICT"; return magic == PIC_MAGIC_NUMBER && id == "PICT";
} }
/** /**
@ -123,7 +126,8 @@ struct PicChannel {
: size(_size) : size(_size)
, encoding(_encoding) , encoding(_encoding)
, code(_code) , code(_code)
{} {
}
/** /**
* Constructs a default channel description for a SoftImage PIC file. * Constructs a default channel description for a SoftImage PIC file.
* *
@ -135,7 +139,8 @@ struct PicChannel {
*/ */
PicChannel() PicChannel()
: size(8) : size(8)
{} {
}
}; };
class SoftimagePICHandler : public QImageIOHandler class SoftimagePICHandler : public QImageIOHandler
@ -155,13 +160,14 @@ public:
Error, Error,
Ready, Ready,
ReadHeader, ReadHeader,
ReadChannels ReadChannels,
}; };
SoftimagePICHandler() SoftimagePICHandler()
: m_state(Ready) : m_state(Ready)
, m_compression(true) , m_compression(true)
{} {
}
bool readHeader(); bool readHeader();
bool readChannels(); bool readChannels();

View File

@ -29,9 +29,8 @@ typedef quint32 uint;
typedef quint16 ushort; typedef quint16 ushort;
typedef quint8 uchar; typedef quint8 uchar;
namespace // Private. namespace // Private.
{ {
enum ColorMode { enum ColorMode {
CM_BITMAP = 0, CM_BITMAP = 0,
CM_GRAYSCALE = 1, CM_GRAYSCALE = 1,
@ -40,7 +39,7 @@ enum ColorMode {
CM_CMYK = 4, CM_CMYK = 4,
CM_MULTICHANNEL = 7, CM_MULTICHANNEL = 7,
CM_DUOTONE = 8, CM_DUOTONE = 8,
CM_LABCOLOR = 9 CM_LABCOLOR = 9,
}; };
struct PSDHeader { struct PSDHeader {
@ -54,7 +53,7 @@ struct PSDHeader {
ushort color_mode; ushort color_mode;
}; };
static QDataStream &operator>> (QDataStream &s, PSDHeader &header) static QDataStream &operator>>(QDataStream &s, PSDHeader &header)
{ {
s >> header.signature; s >> header.signature;
s >> header.version; s >> header.version;
@ -72,7 +71,7 @@ static QDataStream &operator>> (QDataStream &s, PSDHeader &header)
// Check that the header is a valid PSD. // Check that the header is a valid PSD.
static bool IsValid(const PSDHeader &header) static bool IsValid(const PSDHeader &header)
{ {
if (header.signature != 0x38425053) { // '8BPS' if (header.signature != 0x38425053) { // '8BPS'
return false; return false;
} }
return true; return true;
@ -104,26 +103,31 @@ static void skip_section(QDataStream &s)
s.skipRawData(section_length); s.skipRawData(section_length);
} }
template <class Trait> template<class Trait>
static Trait readPixel(QDataStream &stream) { static Trait readPixel(QDataStream &stream)
{
Trait pixel; Trait pixel;
stream >> pixel; stream >> pixel;
return pixel; return pixel;
} }
static QRgb updateRed(QRgb oldPixel, quint8 redPixel) { static QRgb updateRed(QRgb oldPixel, quint8 redPixel)
{
return qRgba(redPixel, qGreen(oldPixel), qBlue(oldPixel), qAlpha(oldPixel)); return qRgba(redPixel, qGreen(oldPixel), qBlue(oldPixel), qAlpha(oldPixel));
} }
static QRgb updateGreen(QRgb oldPixel, quint8 greenPixel) { static QRgb updateGreen(QRgb oldPixel, quint8 greenPixel)
{
return qRgba(qRed(oldPixel), greenPixel, qBlue(oldPixel), qAlpha(oldPixel)); return qRgba(qRed(oldPixel), greenPixel, qBlue(oldPixel), qAlpha(oldPixel));
} }
static QRgb updateBlue(QRgb oldPixel, quint8 bluePixel) { static QRgb updateBlue(QRgb oldPixel, quint8 bluePixel)
{
return qRgba(qRed(oldPixel), qGreen(oldPixel), bluePixel, qAlpha(oldPixel)); return qRgba(qRed(oldPixel), qGreen(oldPixel), bluePixel, qAlpha(oldPixel));
} }
static QRgb updateAlpha(QRgb oldPixel, quint8 alphaPixel) { static QRgb updateAlpha(QRgb oldPixel, quint8 alphaPixel)
{
return qRgba(qRed(oldPixel), qGreen(oldPixel), qBlue(oldPixel), alphaPixel); return qRgba(qRed(oldPixel), qGreen(oldPixel), qBlue(oldPixel), alphaPixel);
} }
typedef QRgb(*channelUpdater)(QRgb,quint8); typedef QRgb (*channelUpdater)(QRgb, quint8);
// Load the PSD image. // Load the PSD image.
static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
@ -151,13 +155,11 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
quint32 channel_num = header.channel_count; quint32 channel_num = header.channel_count;
QImage::Format fmt = header.depth == 8 ? QImage::Format_RGB32 QImage::Format fmt = header.depth == 8 ? QImage::Format_RGB32 : QImage::Format_RGBX64;
: QImage::Format_RGBX64;
// Clear the image. // Clear the image.
if (channel_num >= 4) { if (channel_num >= 4) {
// Enable alpha. // Enable alpha.
fmt = header.depth == 8 ? QImage::Format_ARGB32 fmt = header.depth == 8 ? QImage::Format_ARGB32 : QImage::Format_RGBA64;
: QImage::Format_RGBA64;
// Ignore the other channels. // Ignore the other channels.
channel_num = 4; channel_num = 4;
@ -168,7 +170,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height); qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
return false; return false;
} }
img.fill(qRgb(0,0,0)); img.fill(qRgb(0, 0, 0));
const quint32 pixel_count = header.height * header.width; const quint32 pixel_count = header.height * header.width;
const quint32 channel_size = pixel_count * header.depth / 8; const quint32 channel_size = pixel_count * header.depth / 8;
@ -179,26 +181,27 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
return false; return false;
} }
QRgb *image_data = reinterpret_cast<QRgb*>(img.bits()); QRgb *image_data = reinterpret_cast<QRgb *>(img.bits());
if (!image_data) { if (!image_data) {
return false; return false;
} }
static const channelUpdater updaters[4] = { static const channelUpdater updaters[4] = {updateRed, updateGreen, updateBlue, updateAlpha};
updateRed,
updateGreen,
updateBlue,
updateAlpha
};
typedef QRgba64(*channelUpdater16)(QRgba64, quint16); typedef QRgba64 (*channelUpdater16)(QRgba64, quint16);
static const channelUpdater16 updaters64[4] = { static const channelUpdater16 updaters64[4] = {[](QRgba64 oldPixel, quint16 redPixel) {
[](QRgba64 oldPixel, quint16 redPixel) {return qRgba64((oldPixel & ~(0xFFFFull << 0)) | (quint64( redPixel) << 0));}, return qRgba64((oldPixel & ~(0xFFFFull << 0)) | (quint64(redPixel) << 0));
[](QRgba64 oldPixel, quint16 greenPixel){return qRgba64((oldPixel & ~(0xFFFFull << 16)) | (quint64(greenPixel) << 16));}, },
[](QRgba64 oldPixel, quint16 bluePixel) {return qRgba64((oldPixel & ~(0xFFFFull << 32)) | (quint64( bluePixel) << 32));}, [](QRgba64 oldPixel, quint16 greenPixel) {
[](QRgba64 oldPixel, quint16 alphaPixel){return qRgba64((oldPixel & ~(0xFFFFull << 48)) | (quint64(alphaPixel) << 48));} return qRgba64((oldPixel & ~(0xFFFFull << 16)) | (quint64(greenPixel) << 16));
}; },
[](QRgba64 oldPixel, quint16 bluePixel) {
return qRgba64((oldPixel & ~(0xFFFFull << 32)) | (quint64(bluePixel) << 32));
},
[](QRgba64 oldPixel, quint16 alphaPixel) {
return qRgba64((oldPixel & ~(0xFFFFull << 48)) | (quint64(alphaPixel) << 48));
}};
if (compression) { if (compression) {
// Skip row lengths. // Skip row lengths.
@ -210,14 +213,10 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
for (unsigned short channel = 0; channel < channel_num; channel++) { for (unsigned short channel = 0; channel < channel_num; channel++) {
bool success = false; bool success = false;
if (header.depth == 8) { if (header.depth == 8) {
success = decodeRLEData(RLEVariant::PackBits, stream, success = decodeRLEData(RLEVariant::PackBits, stream, image_data, channel_size, &readPixel<quint8>, updaters[channel]);
image_data, channel_size,
&readPixel<quint8>, updaters[channel]);
} else if (header.depth == 16) { } else if (header.depth == 16) {
QRgba64 *image_data = reinterpret_cast<QRgba64*>(img.bits()); QRgba64 *image_data = reinterpret_cast<QRgba64 *>(img.bits());
success = decodeRLEData(RLEVariant::PackBits16, stream, success = decodeRLEData(RLEVariant::PackBits16, stream, image_data, channel_size, &readPixel<quint8>, updaters64[channel]);
image_data, channel_size,
&readPixel<quint8>, updaters64[channel]);
} }
if (!success) { if (!success) {
@ -232,7 +231,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
image_data[i] = updaters[channel](image_data[i], readPixel<quint8>(stream)); image_data[i] = updaters[channel](image_data[i], readPixel<quint8>(stream));
} }
} else if (header.depth == 16) { } else if (header.depth == 16) {
QRgba64 *image_data = reinterpret_cast<QRgba64*>(img.bits()); QRgba64 *image_data = reinterpret_cast<QRgba64 *>(img.bits());
for (unsigned i = 0; i < pixel_count; ++i) { for (unsigned i = 0; i < pixel_count; ++i) {
image_data[i] = updaters64[channel](image_data[i], readPixel<quint16>(stream)); image_data[i] = updaters64[channel](image_data[i], readPixel<quint16>(stream));
} }
@ -273,19 +272,19 @@ bool PSDHandler::read(QImage *image)
// Check image file format. // Check image file format.
if (s.atEnd() || !IsValid(header)) { if (s.atEnd() || !IsValid(header)) {
// qDebug() << "This PSD file is not valid."; // qDebug() << "This PSD file is not valid.";
return false; return false;
} }
// Check if it's a supported format. // Check if it's a supported format.
if (!IsSupported(header)) { if (!IsSupported(header)) {
// qDebug() << "This PSD file is not supported."; // qDebug() << "This PSD file is not supported.";
return false; return false;
} }
QImage img; QImage img;
if (!LoadPSD(s, header, img)) { if (!LoadPSD(s, header, img)) {
// qDebug() << "Error loading PSD file."; // qDebug() << "Error loading PSD file.";
return false; return false;
} }

View File

@ -32,4 +32,3 @@ public:
}; };
#endif // KIMG_PSD_P_H #endif // KIMG_PSD_P_H

View File

@ -9,11 +9,11 @@
#include "ras_p.h" #include "ras_p.h"
#include <QImage>
#include <QDataStream> #include <QDataStream>
#include <QDebug> #include <QDebug>
#include <QImage>
namespace // Private. namespace // Private.
{ {
// format info from http://www.fileformat.info/format/sunraster/egff.htm // format info from http://www.fileformat.info/format/sunraster/egff.htm
@ -22,19 +22,19 @@ quint32 rasMagicBigEndian = 0x59a66a95;
// quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files // quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files
enum RASType { enum RASType {
RAS_TYPE_OLD = 0x0, RAS_TYPE_OLD = 0x0,
RAS_TYPE_STANDARD = 0x1, RAS_TYPE_STANDARD = 0x1,
RAS_TYPE_BYTE_ENCODED = 0x2, RAS_TYPE_BYTE_ENCODED = 0x2,
RAS_TYPE_RGB_FORMAT = 0x3, RAS_TYPE_RGB_FORMAT = 0x3,
RAS_TYPE_TIFF_FORMAT = 0x4, RAS_TYPE_TIFF_FORMAT = 0x4,
RAS_TYPE_IFF_FORMAT = 0x5, RAS_TYPE_IFF_FORMAT = 0x5,
RAS_TYPE_EXPERIMENTAL = 0xFFFF RAS_TYPE_EXPERIMENTAL = 0xFFFF,
}; };
enum RASColorMapType { enum RASColorMapType {
RAS_COLOR_MAP_TYPE_NONE = 0x0, RAS_COLOR_MAP_TYPE_NONE = 0x0,
RAS_COLOR_MAP_TYPE_RGB = 0x1, RAS_COLOR_MAP_TYPE_RGB = 0x1,
RAS_COLOR_MAP_TYPE_RAW = 0x2 RAS_COLOR_MAP_TYPE_RAW = 0x2,
}; };
struct RasHeader { struct RasHeader {
@ -46,10 +46,12 @@ struct RasHeader {
quint32 Type; quint32 Type;
quint32 ColorMapType; quint32 ColorMapType;
quint32 ColorMapLength; quint32 ColorMapLength;
enum { SIZE = 32 }; // 8 fields of four bytes each enum {
SIZE = 32,
}; // 8 fields of four bytes each
}; };
static QDataStream &operator>> (QDataStream &s, RasHeader &head) static QDataStream &operator>>(QDataStream &s, RasHeader &head)
{ {
s >> head.MagicNumber; s >> head.MagicNumber;
s >> head.Width; s >> head.Width;
@ -79,8 +81,7 @@ static bool IsSupported(const RasHeader &head)
// check for an appropriate depth // check for an appropriate depth
// we support 8bit+palette, 24bit and 32bit ONLY! // we support 8bit+palette, 24bit and 32bit ONLY!
// TODO: add support for 1bit // TODO: add support for 1bit
if (!((head.Depth == 8 && head.ColorMapType == 1) if (!((head.Depth == 8 && head.ColorMapType == 1) || head.Depth == 24 || head.Depth == 32)) {
|| head.Depth == 24 || head.Depth == 32)) {
return false; return false;
} }
// the Type field adds support for RLE(BGR), RGB and other encodings // the Type field adds support for RLE(BGR), RGB and other encodings
@ -141,7 +142,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
QVector<quint8> input(ras.Length); QVector<quint8> input(ras.Length);
int i = 0; int i = 0;
while (! s.atEnd() && i < input.size()) { while (!s.atEnd() && i < input.size()) {
s >> input[i]; s >> input[i];
// I guess we need to find out if we're at the end of a line // I guess we need to find out if we're at the end of a line
if (paddingrequired && i != 0 && !(i % (ras.Width * bpp))) { if (paddingrequired && i != 0 && !(i % (ras.Width * bpp))) {
@ -153,13 +154,16 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
// Allocate image // Allocate image
img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32); img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);
if (img.isNull()) if (img.isNull()) {
return false; return false;
}
// Reconstruct image from RGB palette if we have a palette // Reconstruct image from RGB palette if we have a palette
// TODO: make generic so it works with 24bit or 32bit palettes // TODO: make generic so it works with 24bit or 32bit palettes
if (ras.ColorMapType == 1 && ras.Depth == 8) { 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 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) { for (quint32 x = 0; x < ras.Width; x++) {
red = palette.value((int)input[y * ras.Width + x]); red = palette.value((int)input[y * ras.Width + x]);
@ -168,11 +172,12 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
img.setPixel(x, y, qRgb(red, green, blue)); img.setPixel(x, y, qRgb(red, green, blue));
} }
} }
} }
if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) { 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 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) { for (quint32 x = 0; x < ras.Width; x++) {
red = input[y * 3 * ras.Width + x * 3 + 2]; 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) { 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 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) { for (quint32 x = 0; x < ras.Width; x++) {
red = input[y * 3 * ras.Width + x * 3]; 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)) { 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 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) { for (quint32 x = 0; x < ras.Width; x++) {
red = input[y * 4 * ras.Width + x * 4 + 3]; 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) { 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 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) { for (quint32 x = 0; x < ras.Width; x++) {
red = input[y * 4 * ras.Width + x * 4 + 1]; red = input[y * 4 * ras.Width + x * 4 + 1];
@ -274,21 +285,22 @@ bool RASHandler::read(QImage *outImage)
RasHeader ras; RasHeader ras;
s >> ras; s >> ras;
if (ras.ColorMapLength > std::numeric_limits<int>::max()) if (ras.ColorMapLength > std::numeric_limits<int>::max()) {
return false; return false;
}
// TODO: add support for old versions of RAS where Length may be zero in header // TODO: add support for old versions of RAS where Length may be zero in header
s.device()->seek(RasHeader::SIZE + ras.Length + ras.ColorMapLength); s.device()->seek(RasHeader::SIZE + ras.Length + ras.ColorMapLength);
// Check image file format. Type 2 is RLE, which causing seeking to be silly. // Check image file format. Type 2 is RLE, which causing seeking to be silly.
if (!s.atEnd() && ras.Type != 2) { if (!s.atEnd() && ras.Type != 2) {
// qDebug() << "This RAS file is not valid, or an older version of the format."; // qDebug() << "This RAS file is not valid, or an older version of the format.";
return false; return false;
} }
// Check supported file types. // Check supported file types.
if (!IsSupported(ras)) { if (!IsSupported(ras)) {
// qDebug() << "This RAS file is not supported."; // qDebug() << "This RAS file is not supported.";
return false; return false;
} }
@ -296,7 +308,7 @@ bool RASHandler::read(QImage *outImage)
bool result = LoadRAS(s, ras, img); bool result = LoadRAS(s, ras, img);
if (result == false) { if (result == false) {
// qDebug() << "Error loading RAS file."; // qDebug() << "Error loading RAS file.";
return false; return false;
} }
@ -306,7 +318,6 @@ bool RASHandler::read(QImage *outImage)
QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{ {
if (format == "ras") { if (format == "ras") {
return Capabilities(CanRead); return Capabilities(CanRead);
} }

View File

@ -24,14 +24,17 @@
#include <QMap> #include <QMap>
#include <QVector> #include <QVector>
#include <QImage>
#include <QDebug> #include <QDebug>
#include <QImage>
class RLEData : public QVector<uchar> class RLEData : public QVector<uchar>
{ {
public: public:
RLEData() {} RLEData()
RLEData(const uchar *d, uint l, uint o) : _offset(o) {
}
RLEData(const uchar *d, uint l, uint o)
: _offset(o)
{ {
for (uint i = 0; i < l; i++) { for (uint i = 0; i < l; i++) {
append(d[i]); append(d[i]);
@ -51,7 +54,11 @@ private:
class RLEMap : public QMap<RLEData, uint> class RLEMap : public QMap<RLEData, uint>
{ {
public: public:
RLEMap() : _counter(0), _offset(0) {} RLEMap()
: _counter(0)
, _offset(0)
{
}
uint insert(const uchar *d, uint l); uint insert(const uchar *d, uint l);
QVector<const RLEData *> vector(); QVector<const RLEData *> vector();
void setBaseOffset(uint o) void setBaseOffset(uint o)
@ -74,7 +81,12 @@ public:
bool writeImage(const QImage &); bool writeImage(const QImage &);
private: private:
enum { NORMAL, DITHERED, SCREEN, COLORMAP }; // colormap enum {
NORMAL,
DITHERED,
SCREEN,
COLORMAP,
}; // colormap
QIODevice *_dev; QIODevice *_dev;
QDataStream _stream; QDataStream _stream;
@ -108,9 +120,9 @@ private:
uchar intensity(uchar); uchar intensity(uchar);
}; };
SGIImage::SGIImage(QIODevice *io) : SGIImage::SGIImage(QIODevice *io)
_starttab(nullptr), : _starttab(nullptr)
_lengthtab(nullptr) , _lengthtab(nullptr)
{ {
_dev = io; _dev = io;
_stream.setDevice(_dev); _stream.setDevice(_dev);
@ -126,7 +138,8 @@ SGIImage::~SGIImage()
bool SGIImage::getRow(uchar *dest) bool SGIImage::getRow(uchar *dest)
{ {
int n, i; int n;
int i;
if (!_rle) { if (!_rle) {
for (i = 0; i < _xsize; i++) { for (i = 0; i < _xsize; i++) {
if (_pos >= _data.end()) { if (_pos >= _data.end()) {
@ -172,7 +185,8 @@ bool SGIImage::readData(QImage &img)
quint32 *start = _starttab; quint32 *start = _starttab;
QByteArray lguard(_xsize, 0); QByteArray lguard(_xsize, 0);
uchar *line = (uchar *)lguard.data(); uchar *line = (uchar *)lguard.data();
unsigned x, y; unsigned x;
unsigned y;
if (!_rle) { if (!_rle) {
_pos = _data.begin(); _pos = _data.begin();
@ -249,7 +263,7 @@ bool SGIImage::readImage(QImage &img)
qint16 u16; qint16 u16;
qint32 u32; qint32 u32;
// qDebug() << "reading rgb "; // qDebug() << "reading rgb ";
// magic // magic
_stream >> u16; _stream >> u16;
@ -259,42 +273,42 @@ bool SGIImage::readImage(QImage &img)
// verbatim/rle // verbatim/rle
_stream >> _rle; _stream >> _rle;
// qDebug() << (_rle ? "RLE" : "verbatim"); // qDebug() << (_rle ? "RLE" : "verbatim");
if (_rle > 1) { if (_rle > 1) {
return false; return false;
} }
// bytes per channel // bytes per channel
_stream >> _bpc; _stream >> _bpc;
// qDebug() << "bytes per channel: " << int(_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"; // qDebug() << "dropping least significant byte";
} else { } else {
return false; return false;
} }
// number of dimensions // number of dimensions
_stream >> _dim; _stream >> _dim;
// qDebug() << "dimensions: " << _dim; // qDebug() << "dimensions: " << _dim;
if (_dim < 1 || _dim > 3) { if (_dim < 1 || _dim > 3) {
return false; return false;
} }
_stream >> _xsize >> _ysize >> _zsize >> _pixmin >> _pixmax >> u32; _stream >> _xsize >> _ysize >> _zsize >> _pixmin >> _pixmax >> u32;
// qDebug() << "x: " << _xsize; // qDebug() << "x: " << _xsize;
// qDebug() << "y: " << _ysize; // qDebug() << "y: " << _ysize;
// qDebug() << "z: " << _zsize; // qDebug() << "z: " << _zsize;
// name // name
_stream.readRawData(_imagename, 80); _stream.readRawData(_imagename, 80);
_imagename[79] = '\0'; _imagename[79] = '\0';
_stream >> _colormap; _stream >> _colormap;
// qDebug() << "colormap: " << _colormap; // qDebug() << "colormap: " << _colormap;
if (_colormap != NORMAL) { if (_colormap != NORMAL) {
return false; // only NORMAL supported return false; // only NORMAL supported
} }
for (int i = 0; i < 404; i++) { for (int i = 0; i < 404; i++) {
@ -302,7 +316,7 @@ bool SGIImage::readImage(QImage &img)
} }
if (_dim == 1) { if (_dim == 1) {
// qDebug() << "1-dimensional images aren't supported yet"; // qDebug() << "1-dimensional images aren't supported yet";
return false; return false;
} }
@ -316,17 +330,19 @@ bool SGIImage::readImage(QImage &img)
return false; return false;
} }
if (_zsize == 0 ) if (_zsize == 0) {
return false; return false;
}
if (_zsize == 2 || _zsize == 4) { if (_zsize == 2 || _zsize == 4) {
img = img.convertToFormat(QImage::Format_ARGB32); img = img.convertToFormat(QImage::Format_ARGB32);
} else if (_zsize > 4) { } else if (_zsize > 4) {
// qDebug() << "using first 4 of " << _zsize << " channels"; // qDebug() << "using first 4 of " << _zsize << " channels";
// Only let this continue if it won't cause a int overflow later // Only let this continue if it won't cause a int overflow later
// this is most likely a broken file anyway // 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; return false;
}
} }
_numrows = _ysize * _zsize; _numrows = _ysize * _zsize;
@ -351,16 +367,18 @@ bool SGIImage::readImage(QImage &img)
_data = _dev->readAll(); _data = _dev->readAll();
// sanity check // sanity check
if (_rle) if (_rle) {
for (uint o = 0; o < _numrows; o++) for (uint o = 0; o < _numrows; o++) {
// don't change to greater-or-equal! // don't change to greater-or-equal!
if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) { if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
// qDebug() << "image corrupt (sanity check failed)"; // qDebug() << "image corrupt (sanity check failed)";
return false; return false;
} }
}
}
if (!readData(img)) { if (!readData(img)) {
// qDebug() << "image corrupt (incomplete scanline)"; // qDebug() << "image corrupt (incomplete scanline)";
return false; return false;
} }
@ -378,7 +396,8 @@ void RLEData::write(QDataStream &s)
bool RLEData::operator<(const RLEData &b) const bool RLEData::operator<(const RLEData &b) const
{ {
uchar ac, bc; uchar ac;
uchar bc;
for (int i = 0; i < qMin(size(), b.size()); i++) { for (int i = 0; i < qMin(size(), b.size()); i++) {
ac = at(i); ac = at(i);
bc = b[i]; bc = b[i];
@ -424,8 +443,13 @@ uchar SGIImage::intensity(uchar c)
uint SGIImage::compact(uchar *d, uchar *s) uint SGIImage::compact(uchar *d, uchar *s)
{ {
uchar *dest = d, *src = s, patt, *t, *end = s + _xsize; uchar *dest = d;
int i, n; uchar *src = s;
uchar patt;
uchar *t;
uchar *end = s + _xsize;
int i;
int n;
while (src < end) { while (src < end) {
for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++) { for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++) {
n++; n++;
@ -468,7 +492,8 @@ bool SGIImage::scanData(const QImage &img)
uchar *line = (uchar *)lineguard.data(); uchar *line = (uchar *)lineguard.data();
uchar *buf = (uchar *)bufguard.data(); uchar *buf = (uchar *)bufguard.data();
const QRgb *c; const QRgb *c;
unsigned x, y; unsigned x;
unsigned y;
uint len; uint len;
for (y = 0; y < _ysize; y++) { for (y = 0; y < _ysize; y++) {
@ -567,7 +592,7 @@ void SGIImage::writeHeader()
void SGIImage::writeRle() void SGIImage::writeRle()
{ {
_rle = 1; _rle = 1;
// qDebug() << "writing RLE data"; // qDebug() << "writing RLE data";
writeHeader(); writeHeader();
uint i; uint i;
@ -590,11 +615,12 @@ void SGIImage::writeRle()
void SGIImage::writeVerbatim(const QImage &img) void SGIImage::writeVerbatim(const QImage &img)
{ {
_rle = 0; _rle = 0;
// qDebug() << "writing verbatim data"; // qDebug() << "writing verbatim data";
writeHeader(); writeHeader();
const QRgb *c; const QRgb *c;
unsigned x, y; unsigned x;
unsigned y;
for (y = 0; y < _ysize; y++) { for (y = 0; y < _ysize; y++) {
c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1)); c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
@ -637,7 +663,7 @@ void SGIImage::writeVerbatim(const QImage &img)
bool SGIImage::writeImage(const QImage &image) bool SGIImage::writeImage(const QImage &image)
{ {
// qDebug() << "writing "; // TODO add filename // qDebug() << "writing "; // TODO add filename
QImage img = image; QImage img = image;
if (img.allGray()) { if (img.allGray()) {
_dim = 2, _zsize = 1; _dim = 2, _zsize = 1;
@ -651,13 +677,20 @@ bool SGIImage::writeImage(const QImage &image)
img = img.convertToFormat(QImage::Format_RGB32); img = img.convertToFormat(QImage::Format_RGB32);
if (img.isNull()) { if (img.isNull()) {
// qDebug() << "can't convert image to depth 32"; // qDebug() << "can't convert image to depth 32";
return false;
}
const int w = img.width();
const int h = img.height();
if (w > 65536 || h > 65536) {
return false; return false;
} }
_bpc = 1; _bpc = 1;
_xsize = img.width(); _xsize = w;
_ysize = img.height(); _ysize = h;
_pixmin = ~0u; _pixmin = ~0u;
_pixmax = 0; _pixmax = 0;
_colormap = NORMAL; _colormap = NORMAL;
@ -666,7 +699,7 @@ bool SGIImage::writeImage(const QImage &image)
_rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32)); _rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32));
if (!scanData(img)) { if (!scanData(img)) {
// qDebug() << "this can't happen"; // qDebug() << "this can't happen";
return false; return false;
} }
@ -678,11 +711,11 @@ bool SGIImage::writeImage(const QImage &image)
rle_size += _rlevector[i]->size(); rle_size += _rlevector[i]->size();
} }
// qDebug() << "minimum intensity: " << _pixmin; // qDebug() << "minimum intensity: " << _pixmin;
// qDebug() << "maximum intensity: " << _pixmax; // qDebug() << "maximum intensity: " << _pixmax;
// qDebug() << "saved scanlines: " << _numrows - _rlemap.size(); // qDebug() << "saved scanlines: " << _numrows - _rlemap.size();
// qDebug() << "total savings: " << (verbatim_size - rle_size) << " bytes"; // qDebug() << "total savings: " << (verbatim_size - rle_size) << " bytes";
// qDebug() << "compression: " << (rle_size * 100.0 / verbatim_size) << '%'; // qDebug() << "compression: " << (rle_size * 100.0 / verbatim_size) << '%';
if (verbatim_size <= rle_size) { if (verbatim_size <= rle_size) {
writeVerbatim(img); writeVerbatim(img);
@ -746,8 +779,7 @@ bool RGBHandler::canRead(QIODevice *device)
QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{ {
if (format == "rgb" || format == "rgba" || if (format == "rgb" || format == "rgba" || format == "bw" || format == "sgi") {
format == "bw" || format == "sgi") {
return Capabilities(CanRead | CanWrite); return Capabilities(CanRead | CanWrite);
} }
if (!format.isEmpty()) { if (!format.isEmpty()) {

View File

@ -33,4 +33,3 @@ public:
}; };
#endif // KIMG_RGB_P_H #endif // KIMG_RGB_P_H

View File

@ -8,8 +8,8 @@
#ifndef KIMAGEFORMATS_RLE_P_H #ifndef KIMAGEFORMATS_RLE_P_H
#define KIMAGEFORMATS_RLE_P_H #define KIMAGEFORMATS_RLE_P_H
#include <QDebug>
#include <QDataStream> #include <QDataStream>
#include <QDebug>
/** /**
* The RLEVariant to use. * The RLEVariant to use.
@ -37,7 +37,7 @@ enum class RLEVariant {
* of size 128, 130 of size 127, down to 255 of * of size 128, 130 of size 127, down to 255 of
* size 2. * size 2.
*/ */
PIC PIC,
}; };
/** /**
@ -64,12 +64,7 @@ enum class RLEVariant {
* into @p buf, @c false otherwise. * into @p buf, @c false otherwise.
*/ */
template<typename Item, typename Func1, typename Func2> template<typename Item, typename Func1, typename Func2>
static inline bool decodeRLEData(RLEVariant variant, static inline bool decodeRLEData(RLEVariant variant, QDataStream &stream, Item *dest, quint32 length, Func1 readData, Func2 updateItem)
QDataStream &stream,
Item *dest,
quint32 length,
Func1 readData,
Func2 updateItem)
{ {
unsigned offset = 0; // in dest unsigned offset = 0; // in dest
bool is_msb = true; // only used for 16-bit PackBits, data is big-endian bool is_msb = true; // only used for 16-bit PackBits, data is big-endian
@ -154,7 +149,6 @@ static inline bool decodeRLEData(RLEVariant variant,
return stream.status() == QDataStream::Ok; return stream.status() == QDataStream::Ok;
} }
/** /**
* Encodes data in run-length encoding format. * Encodes data in run-length encoding format.
* *
@ -170,18 +164,10 @@ static inline bool decodeRLEData(RLEVariant variant,
* and writes the item to the data stream. * and writes the item to the data stream.
*/ */
template<typename Item, typename Func1, typename Func2> template<typename Item, typename Func1, typename Func2>
static inline void encodeRLEData(RLEVariant variant, static inline void encodeRLEData(RLEVariant variant, QDataStream &stream, const Item *data, unsigned length, Func1 itemsEqual, Func2 writeItem)
QDataStream &stream,
const Item *data,
unsigned length,
Func1 itemsEqual,
Func2 writeItem)
{ {
unsigned offset = 0; unsigned offset = 0;
const unsigned maxEncodableChunk = const unsigned maxEncodableChunk = (variant == RLEVariant::PIC) ? 65535u : 128;
(variant == RLEVariant::PIC)
? 65535u
: 128;
while (offset < length) { while (offset < length) {
const Item *chunkStart = data + offset; const Item *chunkStart = data + offset;
unsigned maxChunk = qMin(length - offset, maxEncodableChunk); unsigned maxChunk = qMin(length - offset, maxEncodableChunk);
@ -220,10 +206,7 @@ static inline void encodeRLEData(RLEVariant variant,
} }
chunkLength = 1; chunkLength = 1;
chunkEnd = chunkStart + 1; chunkEnd = chunkStart + 1;
while (chunkLength < maxChunk && while (chunkLength < maxChunk && (chunkLength + 1u == maxChunk || !itemsEqual(*chunkEnd, *(chunkEnd + 1)))) {
(chunkLength + 1u == maxChunk ||
!itemsEqual(*chunkEnd, *(chunkEnd+1))))
{
++chunkEnd; ++chunkEnd;
++chunkLength; ++chunkLength;
} }

View File

@ -20,27 +20,26 @@
#include <assert.h> #include <assert.h>
#include <QImage>
#include <QDataStream> #include <QDataStream>
#include <QDebug> #include <QDebug>
#include <QImage>
typedef quint32 uint; typedef quint32 uint;
typedef quint16 ushort; typedef quint16 ushort;
typedef quint8 uchar; typedef quint8 uchar;
namespace // Private. namespace // Private.
{ {
// Header format of saved files. // Header format of saved files.
uchar targaMagic[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; uchar targaMagic[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
enum TGAType { enum TGAType {
TGA_TYPE_INDEXED = 1, TGA_TYPE_INDEXED = 1,
TGA_TYPE_RGB = 2, TGA_TYPE_RGB = 2,
TGA_TYPE_GREY = 3, TGA_TYPE_GREY = 3,
TGA_TYPE_RLE_INDEXED = 9, TGA_TYPE_RLE_INDEXED = 9,
TGA_TYPE_RLE_RGB = 10, TGA_TYPE_RLE_RGB = 10,
TGA_TYPE_RLE_GREY = 11 TGA_TYPE_RLE_GREY = 11,
}; };
#define TGA_INTERLEAVE_MASK 0xc0 #define TGA_INTERLEAVE_MASK 0xc0
@ -48,11 +47,11 @@ enum TGAType {
#define TGA_INTERLEAVE_2WAY 0x40 #define TGA_INTERLEAVE_2WAY 0x40
#define TGA_INTERLEAVE_4WAY 0x80 #define TGA_INTERLEAVE_4WAY 0x80
#define TGA_ORIGIN_MASK 0x30 #define TGA_ORIGIN_MASK 0x30
#define TGA_ORIGIN_LEFT 0x00 #define TGA_ORIGIN_LEFT 0x00
#define TGA_ORIGIN_RIGHT 0x10 #define TGA_ORIGIN_RIGHT 0x10
#define TGA_ORIGIN_LOWER 0x00 #define TGA_ORIGIN_LOWER 0x00
#define TGA_ORIGIN_UPPER 0x20 #define TGA_ORIGIN_UPPER 0x20
/** Tga Header. */ /** Tga Header. */
struct TgaHeader { struct TgaHeader {
@ -69,10 +68,12 @@ struct TgaHeader {
uchar pixel_size; uchar pixel_size;
uchar flags; uchar flags;
enum { SIZE = 18 }; // const static int SIZE = 18; enum {
SIZE = 18,
}; // const static int SIZE = 18;
}; };
static QDataStream &operator>> (QDataStream &s, TgaHeader &head) static QDataStream &operator>>(QDataStream &s, TgaHeader &head)
{ {
s >> head.id_length; s >> head.id_length;
s >> head.colormap_type; s >> head.colormap_type;
@ -88,30 +89,23 @@ static QDataStream &operator>> (QDataStream &s, TgaHeader &head)
s >> head.flags; s >> head.flags;
/*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type; /*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type;
qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size; qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size;
qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize: " << head.pixel_size << " - flags: " << head.flags;*/ qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize:
" << head.pixel_size << " - flags: " << head.flags;*/
return s; return s;
} }
static bool IsSupported(const TgaHeader &head) static bool IsSupported(const TgaHeader &head)
{ {
if (head.image_type != TGA_TYPE_INDEXED && if (head.image_type != TGA_TYPE_INDEXED && head.image_type != TGA_TYPE_RGB && head.image_type != TGA_TYPE_GREY && head.image_type != TGA_TYPE_RLE_INDEXED
head.image_type != TGA_TYPE_RGB && && head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) {
head.image_type != TGA_TYPE_GREY &&
head.image_type != TGA_TYPE_RLE_INDEXED &&
head.image_type != TGA_TYPE_RLE_RGB &&
head.image_type != TGA_TYPE_RLE_GREY) {
return false; return false;
} }
if (head.image_type == TGA_TYPE_INDEXED || if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) {
head.image_type == TGA_TYPE_RLE_INDEXED) {
if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) { if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) {
return false; return false;
} }
} }
if (head.image_type == TGA_TYPE_RGB || if (head.image_type == TGA_TYPE_RGB || head.image_type == TGA_TYPE_GREY || head.image_type == TGA_TYPE_RLE_RGB || head.image_type == TGA_TYPE_RLE_GREY) {
head.image_type == TGA_TYPE_GREY ||
head.image_type == TGA_TYPE_RLE_RGB ||
head.image_type == TGA_TYPE_RLE_GREY) {
if (head.colormap_type != 0) { if (head.colormap_type != 0) {
return false; return false;
} }
@ -119,8 +113,7 @@ static bool IsSupported(const TgaHeader &head)
if (head.width == 0 || head.height == 0) { if (head.width == 0 || head.height == 0) {
return false; return false;
} }
if (head.pixel_size != 8 && head.pixel_size != 16 && if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
head.pixel_size != 24 && head.pixel_size != 32) {
return false; return false;
} }
return true; return true;
@ -138,12 +131,16 @@ struct TgaHeaderInfo {
bool rgb; bool rgb;
bool grey; bool grey;
TgaHeaderInfo(const TgaHeader &tga) : rle(false), pal(false), rgb(false), grey(false) TgaHeaderInfo(const TgaHeader &tga)
: rle(false)
, pal(false)
, rgb(false)
, grey(false)
{ {
switch (tga.image_type) { switch (tga.image_type) {
case TGA_TYPE_RLE_INDEXED: case TGA_TYPE_RLE_INDEXED:
rle = true; rle = true;
Q_FALLTHROUGH(); Q_FALLTHROUGH();
// no break is intended! // no break is intended!
case TGA_TYPE_INDEXED: case TGA_TYPE_INDEXED:
pal = true; pal = true;
@ -151,7 +148,7 @@ struct TgaHeaderInfo {
case TGA_TYPE_RLE_RGB: case TGA_TYPE_RLE_RGB:
rle = true; rle = true;
Q_FALLTHROUGH(); Q_FALLTHROUGH();
// no break is intended! // no break is intended!
case TGA_TYPE_RGB: case TGA_TYPE_RGB:
rgb = true; rgb = true;
@ -159,7 +156,7 @@ struct TgaHeaderInfo {
case TGA_TYPE_RLE_GREY: case TGA_TYPE_RLE_GREY:
rle = true; rle = true;
Q_FALLTHROUGH(); Q_FALLTHROUGH();
// no break is intended! // no break is intended!
case TGA_TYPE_GREY: case TGA_TYPE_GREY:
grey = true; grey = true;
@ -202,7 +199,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size; qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
if (size < 1) { if (size < 1) {
// qDebug() << "This TGA file is broken with size " << size; // qDebug() << "This TGA file is broken with size " << size;
return false; return false;
} }
@ -225,7 +222,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
} }
// Allocate image. // Allocate image.
uchar *const image = reinterpret_cast<uchar*>(malloc(size)); uchar *const image = reinterpret_cast<uchar *>(malloc(size));
if (!image) { if (!image) {
return false; return false;
} }
@ -282,11 +279,11 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
return false; return false;
} }
if ((uint)dataRead < count) { if ((uint)dataRead < count) {
const size_t toCopy = count - dataRead; const size_t toCopy = count - dataRead;
if (&dst[dataRead] + toCopy > imgEnd) { if (&dst[dataRead] + toCopy > imgEnd) {
qWarning() << "Trying to write out of bounds!" << ptrdiff_t(image) << ptrdiff_t(&dst[dataRead]);; qWarning() << "Trying to write out of bounds!" << ptrdiff_t(image) << ptrdiff_t(&dst[dataRead]);
;
valid = false; valid = false;
break; break;
} }
@ -314,7 +311,9 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
} }
// Convert image to internal format. // 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) { if (tga.flags & TGA_ORIGIN_UPPER) {
y_start = 0; y_start = 0;
y_step = 1; y_step = 1;
@ -328,7 +327,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
uchar *src = image; uchar *src = image;
for (int y = y_start; y != y_end; y += y_step) { for (int y = y_start; y != y_end; y += y_step) {
QRgb *scanline = (QRgb *) img.scanLine(y); QRgb *scanline = (QRgb *)img.scanLine(y);
if (info.pal) { if (info.pal) {
// Paletted. // Paletted.
@ -389,7 +388,7 @@ bool TGAHandler::canRead() const
bool TGAHandler::read(QImage *outImage) bool TGAHandler::read(QImage *outImage)
{ {
//qDebug() << "Loading TGA file!"; // qDebug() << "Loading TGA file!";
QDataStream s(device()); QDataStream s(device());
s.setByteOrder(QDataStream::LittleEndian); s.setByteOrder(QDataStream::LittleEndian);
@ -401,13 +400,13 @@ bool TGAHandler::read(QImage *outImage)
// Check image file format. // Check image file format.
if (s.atEnd()) { if (s.atEnd()) {
// qDebug() << "This TGA file is not valid."; // qDebug() << "This TGA file is not valid.";
return false; return false;
} }
// Check supported file types. // Check supported file types.
if (!IsSupported(tga)) { if (!IsSupported(tga)) {
// qDebug() << "This TGA file is not supported."; // qDebug() << "This TGA file is not supported.";
return false; return false;
} }
@ -415,7 +414,7 @@ bool TGAHandler::read(QImage *outImage)
bool result = LoadTGA(s, tga, img); bool result = LoadTGA(s, tga, img);
if (result == false) { if (result == false) {
// qDebug() << "Error loading TGA file."; // qDebug() << "Error loading TGA file.";
return false; return false;
} }
@ -435,12 +434,12 @@ bool TGAHandler::write(const QImage &image)
} }
// write header // write header
s << quint16(img.width()); // width s << quint16(img.width()); // width
s << quint16(img.height()); // height s << quint16(img.height()); // height
s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha) 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) 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++) { for (int x = 0; x < img.width(); x++) {
const QRgb color = img.pixel(x, y); const QRgb color = img.pixel(x, y);
s << quint8(qBlue(color)); s << quint8(qBlue(color));
@ -450,6 +449,7 @@ bool TGAHandler::write(const QImage &image)
s << quint8(qAlpha(color)); s << quint8(qAlpha(color));
} }
} }
}
return true; return true;
} }

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ include(ECMMarkAsTest)
macro(kimageformats_executable_tests) macro(kimageformats_executable_tests)
foreach(_testname ${ARGN}) foreach(_testname ${ARGN})
add_executable(${_testname} ${_testname}.cpp) 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}) ecm_mark_as_test(${_testname})
endforeach(_testname) endforeach(_testname)
endmacro() endmacro()

View File

@ -7,8 +7,8 @@
#ifndef FORMAT_ENUM_H #ifndef FORMAT_ENUM_H
#define FORMAT_ENUM_H #define FORMAT_ENUM_H
#include <QMetaEnum>
#include <QImage> #include <QImage>
#include <QMetaEnum>
QImage::Format formatFromString(const QString &str) QImage::Format formatFromString(const QString &str)
{ {

View File

@ -27,21 +27,20 @@ int main(int argc, char **argv)
parser.addVersionOption(); parser.addVersionOption();
parser.addPositionalArgument(QStringLiteral("in"), QStringLiteral("input image file")); parser.addPositionalArgument(QStringLiteral("in"), QStringLiteral("input image file"));
parser.addPositionalArgument(QStringLiteral("out"), QStringLiteral("output image file")); parser.addPositionalArgument(QStringLiteral("out"), QStringLiteral("output image file"));
QCommandLineOption informat( QCommandLineOption informat(QStringList() << QStringLiteral("i") << QStringLiteral("informat"),
QStringList() << QStringLiteral("i") << QStringLiteral("informat"), QStringLiteral("Image format for input file"),
QStringLiteral("Image format for input file"), QStringLiteral("format"));
QStringLiteral("format"));
parser.addOption(informat); parser.addOption(informat);
QCommandLineOption outformat( QCommandLineOption outformat(QStringList() << QStringLiteral("o") << QStringLiteral("outformat"),
QStringList() << QStringLiteral("o") << QStringLiteral("outformat"), QStringLiteral("Image format for output file"),
QStringLiteral("Image format for output file"), QStringLiteral("format"));
QStringLiteral("format"));
parser.addOption(outformat); parser.addOption(outformat);
QCommandLineOption listformats( QCommandLineOption listformats(QStringList() << QStringLiteral("l") << QStringLiteral("list"), QStringLiteral("List supported image formats"));
QStringList() << QStringLiteral("l") << QStringLiteral("list"),
QStringLiteral("List supported image formats"));
parser.addOption(listformats); parser.addOption(listformats);
QCommandLineOption listmimes(QStringList() << QStringLiteral("m") << QStringLiteral("listmime"), QStringLiteral("List supported image mime formats"));
parser.addOption(listmimes);
parser.process(app); parser.process(app);
const QStringList files = parser.positionalArguments(); const QStringList files = parser.positionalArguments();
@ -61,6 +60,21 @@ int main(int argc, char **argv)
return 0; 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) { if (files.count() != 2) {
QTextStream(stdout) << "Must provide exactly two files\n"; QTextStream(stdout) << "Must provide exactly two files\n";
parser.showHelp(1); parser.showHelp(1);

View File

@ -10,10 +10,10 @@
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QImageReader>
#include <QFile> #include <QFile>
#include <QMetaObject> #include <QImageReader>
#include <QMetaEnum> #include <QMetaEnum>
#include <QMetaObject>
#include <QTextStream> #include <QTextStream>
#include "format-enum.h" #include "format-enum.h"
@ -31,27 +31,22 @@ int main(int argc, char **argv)
parser.addVersionOption(); parser.addVersionOption();
parser.addPositionalArgument(QStringLiteral("image"), QStringLiteral("image file")); parser.addPositionalArgument(QStringLiteral("image"), QStringLiteral("image file"));
parser.addPositionalArgument(QStringLiteral("datafile"), QStringLiteral("file QImage data should be written to")); parser.addPositionalArgument(QStringLiteral("datafile"), QStringLiteral("file QImage data should be written to"));
QCommandLineOption informat( QCommandLineOption informat(QStringList() << QStringLiteral("f") << QStringLiteral("file-format"),
QStringList() << QStringLiteral("f") << QStringLiteral("file-format"), QStringLiteral("Image file format"),
QStringLiteral("Image file format"), QStringLiteral("format"));
QStringLiteral("format"));
parser.addOption(informat); parser.addOption(informat);
QCommandLineOption qimgformat( QCommandLineOption qimgformat(QStringList() << QStringLiteral("q") << QStringLiteral("qimage-format"),
QStringList() << QStringLiteral("q") << QStringLiteral("qimage-format"), QStringLiteral("QImage data format"),
QStringLiteral("QImage data format"), QStringLiteral("format"));
QStringLiteral("format"));
parser.addOption(qimgformat); parser.addOption(qimgformat);
QCommandLineOption listformats( QCommandLineOption listformats(QStringList() << QStringLiteral("l") << QStringLiteral("list-file-formats"),
QStringList() << QStringLiteral("l") << QStringLiteral("list-file-formats"), QStringLiteral("List supported image file formats"));
QStringLiteral("List supported image file formats"));
parser.addOption(listformats); parser.addOption(listformats);
QCommandLineOption listmimetypes( QCommandLineOption listmimetypes(QStringList() << QStringLiteral("m") << QStringLiteral("list-mime-types"),
QStringList() << QStringLiteral("m") << QStringLiteral("list-mime-types"), QStringLiteral("List supported image mime types"));
QStringLiteral("List supported image mime types"));
parser.addOption(listmimetypes); parser.addOption(listmimetypes);
QCommandLineOption listqformats( QCommandLineOption listqformats(QStringList() << QStringLiteral("p") << QStringLiteral("list-qimage-formats"),
QStringList() << QStringLiteral("p") << QStringLiteral("list-qimage-formats"), QStringLiteral("List supported QImage data formats"));
QStringLiteral("List supported QImage data formats"));
parser.addOption(listqformats); parser.addOption(listqformats);
parser.process(app); parser.process(app);
@ -93,35 +88,29 @@ int main(int argc, char **argv)
QImageReader reader(files.at(0), parser.value(informat).toLatin1()); QImageReader reader(files.at(0), parser.value(informat).toLatin1());
QImage img = reader.read(); QImage img = reader.read();
if (img.isNull()) { if (img.isNull()) {
QTextStream(stderr) << "Could not read image: " QTextStream(stderr) << "Could not read image: " << reader.errorString() << '\n';
<< reader.errorString() << '\n';
return 2; return 2;
} }
QFile output(files.at(1)); QFile output(files.at(1));
if (!output.open(QIODevice::WriteOnly)) { if (!output.open(QIODevice::WriteOnly)) {
QTextStream(stderr) << "Could not open " << files.at(1) QTextStream(stderr) << "Could not open " << files.at(1) << " for writing: " << output.errorString() << '\n';
<< " for writing: "
<< output.errorString() << '\n';
return 3; return 3;
} }
if (parser.isSet(qimgformat)) { if (parser.isSet(qimgformat)) {
QImage::Format qformat = formatFromString(parser.value(qimgformat)); QImage::Format qformat = formatFromString(parser.value(qimgformat));
if (qformat == QImage::Format_Invalid) { if (qformat == QImage::Format_Invalid) {
QTextStream(stderr) << "Unknown QImage data format " QTextStream(stderr) << "Unknown QImage data format " << parser.value(qimgformat) << '\n';
<< parser.value(qimgformat) << '\n';
return 4; return 4;
} }
img = img.convertToFormat(qformat); img = img.convertToFormat(qformat);
} }
qint64 written = output.write(reinterpret_cast<const char *>(img.bits()), img.sizeInBytes()); qint64 written = output.write(reinterpret_cast<const char *>(img.bits()), img.sizeInBytes());
if (written != img.sizeInBytes()) { if (written != img.sizeInBytes()) {
QTextStream(stderr) << "Could not write image data to " << files.at(1) QTextStream(stderr) << "Could not write image data to " << files.at(1) << ":" << output.errorString() << "\n";
<< ":" << output.errorString() << "\n";
return 5; return 5;
} }
QTextStream(stdout) << "Created " << files.at(1) << " with data format " QTextStream(stdout) << "Created " << files.at(1) << " with data format " << formatToString(img.format()) << "\n";
<< formatToString(img.format()) << "\n";
return 0; return 0;
} }