Compare commits

..

60 Commits

Author SHA1 Message Date
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
8845dd9818 Simplify portion of NCLX color profile code 2021-02-02 09:28:32 +01:00
a4b8295625 [imagedump] Add "list MIME type" (-m) option
Allows listing the supported mime types
2021-01-31 20:49:16 +01:00
134c96fa61 GIT_SILENT Add auto generated .clang-format file to .gitignore 2021-01-30 18:39:11 +01:00
3673874a63 GIT_SILENT Upgrade ECM and KF5 version requirements for 5.79.0 release. 2021-01-29 20:44:46 +00:00
8ad43638ad Fix crash with malformed files
oss-fuzz/29284
2021-01-08 14:02:45 +00:00
c72c9f577b ani: Make sure riffSizeData is of the correct size before doing the quint32_le cast dance
oss-fuzz/29290
2021-01-05 21:52:24 +01:00
bf3f99abf5 Add missing includes 2021-01-03 10:05:53 +01:00
b79d1f222d Add plugin for animated Windows cursors (ANI) 2021-01-03 08:49:10 +00:00
bf1de9f8f0 GIT_SILENT Upgrade ECM and KF5 version requirements for 5.78.0 release. 2021-01-02 12:27:55 +00:00
82d5e0f8a4 Remove ifdefs, we require Qt 5.14 now 2020-12-18 19:34:07 +01:00
bbf945137a GIT_SILENT Upgrade Qt5 version requirement to 5.14.0. 2020-12-18 19:02:54 +01:00
54ed1dda27 Add AVIF to the list of supported formats 2020-12-16 08:36:39 +00:00
34a9ec1b06 Add plugin for AV1 Image File Format (AVIF) 2020-12-13 22:23:27 +00:00
a6ec69d276 GIT_SILENT Upgrade ECM and KF5 version requirements for 5.77.0 release. 2020-12-05 10:09:13 +00:00
02cbf3889f GIT_SILENT Upgrade Qt5 version requirement to 5.13.0. 2020-11-27 00:45:24 +01:00
6cf05cf305 test: don't convert image format if possible 2020-11-10 13:03:37 +08:00
938b8126b5 No longer descease color depth to 8 for 16 bit uncompressed PSD files 2020-11-10 13:03:37 +08:00
d36c191351 tests: Remove qimage_format_enum_names and just use QMetaEnum 2020-11-09 19:15:36 +00:00
1acb5a6177 GIT_SILENT Upgrade ECM and KF5 version requirements for 5.76.0 release. 2020-11-07 11:39:10 +00:00
f2ccbf1724 Add test case for RLE compressed 16 bpc PSD files. 2020-11-06 15:04:04 +08:00
5825c83235 Add support for RLE-compressed, 16 bits per channel PSD files. 2020-11-06 15:02:29 +08:00
b742cb7cc7 Return unsupported when reading 16bit RLE compressed PSD files 2020-11-01 11:50:48 +08:00
2e6eeebdfc feat: add psd color depth == 16 format support 2020-10-30 21:47:12 +08:00
db0b5d571a GIT_SILENT increase KF_DISABLE_DEPRECATED_BEFORE_AND_AT 2020-10-11 11:27:17 +02:00
da2fc84a3b GIT_SILENT Upgrade ECM and KF5 version requirements for 5.75.0 release. 2020-10-04 09:52:48 +00:00
d6b79d16ad GIT_SILENT Upgrade ECM and KF5 version requirements for 5.74.0 release. 2020-09-06 09:21:38 +00:00
fc9a128f47 Remove obsolete COPYING files 2020-08-17 08:00:58 +02:00
d326d5dab9 GIT_SILENT increase KF_DISABLE_DEPRECATED_BEFORE_AND_AT 2020-08-16 01:12:16 +02:00
72 changed files with 3716 additions and 1645 deletions

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

@ -0,0 +1,2 @@
#clang-format
1169859b07f25c865ee0bfc2a7dc97a431651776

6
.gitignore vendored
View File

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

View File

@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.16)
project(KImageFormats)
set (CMAKE_CXX_STANDARD 14)
include(FeatureSummary)
find_package(ECM 5.73.0 NO_MODULE)
find_package(ECM 5.83.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -15,11 +15,12 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings)
include(KDEGitCommitHooks)
include(CheckIncludeFiles)
set(REQUIRED_QT_VERSION 5.12.0)
set(REQUIRED_QT_VERSION 5.15.0)
find_package(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF5Archive)
@ -42,16 +43,32 @@ if (UNIX)
endif()
endif()
find_package(OpenEXR)
find_package(OpenEXR 3.0 CONFIG QUIET)
if(NOT OpenEXR_FOUND)
find_package(OpenEXR)
endif()
set_package_properties(OpenEXR PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for OpenEXR images"
)
add_definitions(-DQT_NO_FOREACH)
find_package(libavif 0.8.2 CONFIG)
set_package_properties(libavif PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for AVIF images"
)
option(KIMAGEFORMATS_HEIF "Enable plugin for HEIF format" OFF)
if(KIMAGEFORMATS_HEIF)
include(FindPkgConfig)
pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.10.0)
endif()
add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/HEIC images")
# 050d00 (5.13) triggers a BIC in qimageiohandler.h, in Qt 5.13, so do not enable that until we can require 5.14
# https://codereview.qt-project.org/c/qt/qtbase/+/279215
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050c00)
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054700)
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f00)
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x055100)
add_subdirectory(src)
if (BUILD_TESTING)
add_subdirectory(autotests)
@ -59,3 +76,5 @@ if (BUILD_TESTING)
endif()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)

View File

@ -1,510 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations
below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it
becomes a de-facto standard. To achieve this, non-free programs must
be allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control
compilation and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at least
three years, to give the same user the materials specified in
Subsection 6a, above, for a charge no more than the cost of
performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply, and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License
may add an explicit geographical distribution limitation excluding those
countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms
of the ordinary General Public License).
To apply these terms, attach the following notices to the library.
It is safest to attach them to the start of each source file to most
effectively convey the exclusion of warranty; and each file should
have at least the "copyright" line and a pointer to where the full
notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or
your school, if any, to sign a "copyright disclaimer" for the library,
if necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James
Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@ -13,6 +13,7 @@ image formats.
The following image formats have read-only support:
- Animated Windows cursors (ani)
- Gimp (xcf)
- OpenEXR (exr)
- Photoshop documents (psd)
@ -20,6 +21,7 @@ The following image formats have read-only support:
The following image formats have read and write support:
- AV1 Image File Format (AVIF)
- Encapsulated PostScript (eps)
- Personal Computer Exchange (pcx)
- SGI images (rgb, rgba, sgi, bw)

View File

@ -70,6 +70,18 @@ if (KF5Archive_FOUND)
)
endif()
if (TARGET avif)
kimageformats_read_tests(
avif
)
endif()
if (LibHeif_FOUND)
kimageformats_read_tests(
heif
)
endif()
# Allow some fuzziness when reading this formats, to allow for
# rounding errors (eg: in alpha blending).
kimageformats_read_tests(FUZZ 1
@ -110,3 +122,8 @@ add_executable(pictest pictest.cpp)
target_link_libraries(pictest Qt5::Gui Qt5::Test)
ecm_mark_as_test(pictest)
add_test(NAME kimageformats-pic COMMAND pictest)
add_executable(anitest anitest.cpp)
target_link_libraries(anitest Qt5::Gui Qt5::Test)
ecm_mark_as_test(anitest)
add_test(NAME kimageformats-ani COMMAND anitest)

BIN
autotests/ani/test.ani Normal file

Binary file not shown.

BIN
autotests/ani/test_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

BIN
autotests/ani/test_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

BIN
autotests/ani/test_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

119
autotests/anitest.cpp Normal file
View File

@ -0,0 +1,119 @@
/*
SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <QImage>
#include <QImageReader>
#include <QTest>
static bool imgEquals(const QImage &im1, const QImage &im2)
{
const int height = im1.height();
const int width = im1.width();
for (int i = 0; i < height; ++i) {
const auto *line1 = reinterpret_cast<const quint8 *>(im1.scanLine(i));
const auto *line2 = reinterpret_cast<const quint8 *>(im2.scanLine(i));
for (int j = 0; j < width; ++j) {
if (line1[j] - line2[j] != 0) {
return false;
}
}
}
return true;
}
class AniTests : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase()
{
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
}
void testReadMetadata()
{
QImageReader reader(QFINDTESTDATA("ani/test.ani"));
QVERIFY(reader.canRead());
QCOMPARE(reader.imageCount(), 4);
QCOMPARE(reader.size(), QSize(32, 32));
QCOMPARE(reader.text(QStringLiteral("Title")), QStringLiteral("ANI Test"));
QCOMPARE(reader.text(QStringLiteral("Author")), QStringLiteral("KDE Community"));
}
void textRead()
{
QImageReader reader(QFINDTESTDATA("ani/test.ani"));
QVERIFY(reader.canRead());
QCOMPARE(reader.currentImageNumber(), 0);
QImage aniFrame;
QVERIFY(reader.read(&aniFrame));
QImage img1(QFINDTESTDATA("ani/test_1.png"));
img1.convertTo(aniFrame.format());
QVERIFY(imgEquals(aniFrame, img1));
QCOMPARE(reader.nextImageDelay(), 166); // 10 "jiffies"
QVERIFY(reader.canRead());
// that read() above should have advanced us to the next frame
QCOMPARE(reader.currentImageNumber(), 1);
QVERIFY(reader.read(&aniFrame));
QImage img2(QFINDTESTDATA("ani/test_2.png"));
img2.convertTo(aniFrame.format());
QVERIFY(imgEquals(aniFrame, img2));
// The "middle" frame has a longer delay than the others
QCOMPARE(reader.nextImageDelay(), 333); // 20 "jiffies"
QVERIFY(reader.canRead());
QCOMPARE(reader.currentImageNumber(), 2);
QVERIFY(reader.read(&aniFrame));
QImage img3(QFINDTESTDATA("ani/test_3.png"));
img3.convertTo(aniFrame.format());
QVERIFY(imgEquals(aniFrame, img3));
QCOMPARE(reader.nextImageDelay(), 166);
QVERIFY(reader.canRead());
QCOMPARE(reader.currentImageNumber(), 3);
QVERIFY(reader.read(&aniFrame));
// custom sequence in the ANI file should get us back to img2
QVERIFY(imgEquals(aniFrame, img2));
QCOMPARE(reader.nextImageDelay(), 166);
// We should have reached the end now
QVERIFY(!reader.canRead());
QVERIFY(!reader.read(&aniFrame));
// Jump back to the start
QVERIFY(reader.jumpToImage(0));
QVERIFY(reader.canRead());
QCOMPARE(reader.currentImageNumber(), 0);
QCOMPARE(reader.nextImageDelay(), 166);
QVERIFY(reader.read(&aniFrame));
QVERIFY(imgEquals(aniFrame, img1));
}
};
QTEST_MAIN(AniTests)
#include "anitest.moc"

View File

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

BIN
autotests/read/avif/bw.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

BIN
autotests/read/avif/bw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

BIN
autotests/read/avif/bwa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

View File

@ -22,28 +22,26 @@ static void writeImageData(const char *name, const QString &filename, const QIma
if (file.open(QIODevice::WriteOnly)) {
qint64 written = file.write(reinterpret_cast<const char *>(image.bits()), image.sizeInBytes());
if (written == image.sizeInBytes()) {
QTextStream(stdout) << " " << name
<< " written to " << filename << "\n";
QTextStream(stdout) << " " << name << " written to " << filename << "\n";
} else {
QTextStream(stdout) << " could not write " << name
<< " to " << filename << ":"
<< file.errorString() << "\n";
QTextStream(stdout) << " could not write " << name << " to " << filename << ":" << file.errorString() << "\n";
}
} else {
QTextStream(stdout) << " could not open "
<< filename << ":"
<< file.errorString() << "\n";
QTextStream(stdout) << " could not open " << filename << ":" << file.errorString() << "\n";
}
}
// allow each byte to be different by up to 1, to allow for rounding errors
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 uchar *line1 = im1.scanLine(i);
const uchar *line2 = im2.scanLine(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)
@ -57,7 +55,30 @@ static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
return true;
}
int main(int argc, char ** argv)
// 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
// format which we preferred to use for `fuzzyeq()`.
// We do only support formats with 8-bits/16-bits pre pixel.
// If that changed, don't forget to update `fuzzyeq()` too
static QImage::Format preferredFormat(QImage::Format fmt)
{
switch (fmt) {
case QImage::Format_RGB32:
case QImage::Format_ARGB32:
case QImage::Format_RGBX64:
case QImage::Format_RGBA64:
return fmt;
default:
return QImage::Format_ARGB32;
}
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
@ -70,10 +91,9 @@ int main(int argc, char ** argv)
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test"));
QCommandLineOption fuzz(
QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"),
QStringLiteral("Allow for some deviation in ARGB data."),
QStringLiteral("max"));
QCommandLineOption fuzz(QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"),
QStringLiteral("Allow for some deviation in ARGB data."),
QStringLiteral("max"));
parser.addOption(fuzz);
parser.process(app);
@ -109,13 +129,14 @@ int main(int argc, char ** argv)
int failed = 0;
QTextStream(stdout) << "********* "
<< "Starting basic read tests for "
<< suffix << " images *********\n";
<< "Starting basic read tests for " << suffix << " images *********\n";
const QList<QByteArray> formats = QImageReader::supportedImageFormats();
QStringList formatStrings;
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";
const QFileInfoList lstImgDir = imgdir.entryInfoList();
@ -132,79 +153,58 @@ int main(int argc, char ** argv)
QImage expImage;
if (!expReader.read(&expImage)) {
QTextStream(stdout) << "ERROR: " << fi.fileName()
<< ": could not load " << expfilename
<< ": " << expReader.errorString()
<< "\n";
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
++failed;
continue;
}
if (!inputReader.canRead()) {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": failed can read: "
<< inputReader.errorString()
<< "\n";
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n";
++failed;
continue;
}
if (!inputReader.read(&inputImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": failed to load: "
<< inputReader.errorString()
<< "\n";
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
++failed;
continue;
}
if (expImage.width() != inputImage.width()) {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": width was " << inputImage.width()
<< " but " << expfilename << " width was "
<< expImage.width() << "\n";
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
<< expImage.width() << "\n";
++failed;
} else if (expImage.height() != inputImage.height()) {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": height was " << inputImage.height()
<< " but " << expfilename << " height was "
<< expImage.height() << "\n";
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": height was " << inputImage.height() << " but " << expfilename << " height was "
<< expImage.height() << "\n";
++failed;
} else {
if (inputImage.format() != QImage::Format_ARGB32) {
QTextStream(stdout) << "INFO : " << fi.fileName()
<< ": converting " << fi.fileName()
<< " from " << formatToString(inputImage.format())
<< " to ARGB32\n";
inputImage = inputImage.convertToFormat(QImage::Format_ARGB32);
QImage::Format inputFormat = preferredFormat(inputImage.format());
QImage::Format expFormat = preferredFormat(expImage.format());
QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32;
if (inputImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format())
<< " to " << formatToString(cmpFormat) << '\n';
inputImage = inputImage.convertToFormat(cmpFormat);
}
if (expImage.format() != QImage::Format_ARGB32) {
QTextStream(stdout) << "INFO : " << fi.fileName()
<< ": converting " << expfilename
<< " from " << formatToString(expImage.format())
<< " to ARGB32\n";
expImage = expImage.convertToFormat(QImage::Format_ARGB32);
if (expImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) << " to "
<< formatToString(cmpFormat) << '\n';
expImage = expImage.convertToFormat(cmpFormat);
}
if (fuzzyeq(inputImage, expImage, fuzziness)) {
QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";
++passed;
} else {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": differs from " << expfilename << "\n";
writeImageData("expected data",
fi.fileName() + QLatin1String("-expected.data"),
expImage);
writeImageData("actual data",
fi.fileName() + QLatin1String("-actual.data"),
inputImage);
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": differs from " << expfilename << "\n";
writeImageData("expected data", fi.fileName() + QLatin1String("-expected.data"), expImage);
writeImageData("actual data", fi.fileName() + QLatin1String("-actual.data"), inputImage);
++failed;
}
}
}
QTextStream(stdout) << "Totals: "
<< passed << " passed, "
<< failed << " failed\n";
QTextStream(stdout) << "Totals: " << passed << " passed, " << failed << " failed\n";
QTextStream(stdout) << "********* "
<< "Finished basic read tests for "
<< suffix << " images *********\n";
<< "Finished basic read tests for " << suffix << " images *********\n";
return failed == 0 ? 0 : 1;
}

View File

@ -16,7 +16,7 @@
#include <QImageWriter>
#include <QTextStream>
int main(int argc, char ** argv)
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
@ -29,9 +29,8 @@ int main(int argc, char ** argv)
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test."));
QCommandLineOption lossless(
QStringList() << QStringLiteral("l") << QStringLiteral("lossless"),
QStringLiteral("Check that reading back the data gives the same image."));
QCommandLineOption lossless(QStringList() << QStringLiteral("l") << QStringLiteral("lossless"),
QStringLiteral("Check that reading back the data gives the same image."));
parser.addOption(lossless);
parser.process(app);
@ -56,8 +55,7 @@ int main(int argc, char ** argv)
int failed = 0;
QTextStream(stdout) << "********* "
<< "Starting basic write tests for "
<< suffix << " images *********\n";
<< "Starting basic write tests for " << suffix << " images *********\n";
const QFileInfoList lstImgDir = imgdir.entryInfoList();
for (const QFileInfo &fi : lstImgDir) {
int suffixPos = fi.filePath().count() - suffix.count();
@ -67,20 +65,14 @@ int main(int argc, char ** argv)
QImageReader pngReader(pngfile, "png");
QImage pngImage;
if (!pngReader.read(&pngImage)) {
QTextStream(stdout) << "ERROR: " << fi.fileName()
<< ": could not load " << pngfilename
<< ": " << pngReader.errorString()
<< "\n";
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << pngfilename << ": " << pngReader.errorString() << "\n";
++failed;
continue;
}
QFile expFile(fi.filePath());
if (!expFile.open(QIODevice::ReadOnly)) {
QTextStream(stdout) << "ERROR: " << fi.fileName()
<< ": could not open " << fi.fileName()
<< ": " << expFile.errorString()
<< "\n";
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not open " << fi.fileName() << ": " << expFile.errorString() << "\n";
++failed;
continue;
}
@ -91,10 +83,7 @@ int main(int argc, char ** argv)
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";
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << fi.fileName() << ": " << expFile.errorString() << "\n";
++failed;
continue;
}
@ -105,16 +94,14 @@ int main(int argc, char ** argv)
QBuffer buffer(&writtenData);
QImageWriter imgWriter(&buffer, format.constData());
if (!imgWriter.write(pngImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": failed to write image data\n";
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to write image data\n";
++failed;
continue;
}
}
if (expData != writtenData) {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": written data differs from " << fi.fileName() << "\n";
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": written data differs from " << fi.fileName() << "\n";
++failed;
continue;
}
@ -124,8 +111,7 @@ int main(int argc, char ** argv)
QBuffer buffer(&writtenData);
QImageReader imgReader(&buffer, format.constData());
if (!imgReader.read(&reReadImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": could not read back the written data\n";
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": could not read back the written data\n";
++failed;
continue;
}
@ -134,8 +120,7 @@ int main(int argc, char ** argv)
if (parser.isSet(lossless)) {
if (pngImage != reReadImage) {
QTextStream(stdout) << "FAIL : " << fi.fileName()
<< ": re-reading the data resulted in a different image\n";
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": re-reading the data resulted in a different image\n";
++failed;
continue;
}
@ -145,12 +130,9 @@ int main(int argc, char ** argv)
++passed;
}
QTextStream(stdout) << "Totals: "
<< passed << " passed, "
<< failed << " failed\n";
QTextStream(stdout) << "Totals: " << passed << " passed, " << failed << " failed\n";
QTextStream(stdout) << "********* "
<< "Finished basic write tests for "
<< suffix << " images *********\n";
<< "Finished basic write tests for " << suffix << " images *********\n";
return failed == 0 ? 0 : 1;
}

View File

@ -24,6 +24,19 @@ endfunction()
##################################
kimageformats_add_plugin(kimg_ani JSON "ani.json" SOURCES ani.cpp)
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
##################################
if (TARGET avif)
kimageformats_add_plugin(kimg_avif JSON "avif.json" SOURCES "avif.cpp")
target_link_libraries(kimg_avif "avif")
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
endif()
##################################
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
##################################
@ -45,7 +58,11 @@ install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugi
if(OpenEXR_FOUND)
kimageformats_add_plugin(kimg_exr JSON "exr.json" SOURCES exr.cpp)
target_link_libraries(kimg_exr OpenEXR::IlmImf)
if(TARGET OpenEXR::OpenEXR)
target_link_libraries(kimg_exr OpenEXR::OpenEXR)
else()
target_link_libraries(kimg_exr OpenEXR::IlmImf)
endif()
kde_target_enable_exceptions(kimg_exr PRIVATE)
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
@ -58,6 +75,15 @@ install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugi
##################################
if (LibHeif_FOUND)
kimageformats_add_plugin(kimg_heif JSON "heif.json" SOURCES heif.cpp)
target_link_libraries(kimg_heif PkgConfig::LibHeif)
kde_target_enable_exceptions(kimg_heif PRIVATE)
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_pcx JSON "pcx.json" SOURCES pcx.cpp)
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)

567
src/imageformats/ani.cpp Normal file
View File

@ -0,0 +1,567 @@
/*
SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ani_p.h"
#include <QDebug>
#include <QImage>
#include <QScopeGuard>
#include <QVariant>
#include <QtEndian>
namespace
{
struct ChunkHeader {
char magic[4];
quint32_le size;
};
struct AniHeader {
quint32_le cbSize;
quint32_le nFrames; // number of actual frames in the file
quint32_le nSteps; // number of logical images
quint32_le iWidth;
quint32_le iHeight;
quint32_le iBitCount;
quint32_le nPlanes;
quint32_le iDispRate;
quint32_le bfAttributes; // attributes (0 = bitmap images, 1 = ico/cur, 3 = "seq" block available)
};
struct CurHeader {
quint16_le wReserved; // always 0
quint16_le wResID; // always 2
quint16_le wNumImages;
};
struct CursorDirEntry {
quint8 bWidth;
quint8 bHeight;
quint8 bColorCount;
quint8 bReserved; // always 0
quint16_le wHotspotX;
quint16_le wHotspotY;
quint32_le dwBytesInImage;
quint32_le dwImageOffset;
};
} // namespace
ANIHandler::ANIHandler() = default;
bool ANIHandler::canRead() const
{
if (canRead(device())) {
setFormat("ani");
return true;
}
// Check if there's another frame coming
const QByteArray nextFrame = device()->peek(sizeof(ChunkHeader));
if (nextFrame.size() == sizeof(ChunkHeader)) {
const auto *header = reinterpret_cast<const ChunkHeader *>(nextFrame.data());
if (qstrncmp(header->magic, "icon", sizeof(header->magic)) == 0 && header->size > 0) {
setFormat("ani");
return true;
}
}
return false;
}
bool ANIHandler::read(QImage *outImage)
{
if (!ensureScanned()) {
return false;
}
if (device()->pos() < m_firstFrameOffset) {
device()->seek(m_firstFrameOffset);
}
const QByteArray frameType = device()->read(4);
if (frameType != "icon") {
return false;
}
const QByteArray frameSizeData = device()->read(sizeof(quint32_le));
if (frameSizeData.count() != sizeof(quint32_le)) {
return false;
}
const auto frameSize = *(reinterpret_cast<const quint32_le *>(frameSizeData.data()));
if (!frameSize) {
return false;
}
const QByteArray frameData = device()->read(frameSize);
const bool ok = outImage->loadFromData(frameData, "cur");
++m_currentImageNumber;
// When we have a custom image sequence, seek to before the frame that would follow
if (!m_imageSequence.isEmpty()) {
if (m_currentImageNumber < m_imageSequence.count()) {
const int nextFrame = m_imageSequence.at(m_currentImageNumber);
if (nextFrame < 0 || nextFrame >= m_frameOffsets.count()) {
return false;
}
const auto nextOffset = m_frameOffsets.at(nextFrame);
device()->seek(nextOffset);
} else if (m_currentImageNumber == m_imageSequence.count()) {
const auto endOffset = m_frameOffsets.last();
if (device()->pos() != endOffset) {
device()->seek(endOffset);
}
}
}
return ok;
}
int ANIHandler::currentImageNumber() const
{
if (!ensureScanned()) {
return 0;
}
return m_currentImageNumber;
}
int ANIHandler::imageCount() const
{
if (!ensureScanned()) {
return 0;
}
return m_imageCount;
}
bool ANIHandler::jumpToImage(int imageNumber)
{
if (!ensureScanned()) {
return false;
}
if (imageNumber < 0) {
return false;
}
if (imageNumber == m_currentImageNumber) {
return true;
}
// If we have a custom image sequence we have a index of frames we can jump to
if (!m_imageSequence.isEmpty()) {
if (imageNumber >= m_imageSequence.count()) {
return false;
}
const int targetFrame = m_imageSequence.at(imageNumber);
const auto targetOffset = m_frameOffsets.value(targetFrame, -1);
if (device()->seek(targetOffset)) {
m_currentImageNumber = imageNumber;
return true;
}
return false;
}
if (imageNumber >= m_frameCount) {
return false;
}
// otherwise we need to jump from frame to frame
const auto oldPos = device()->pos();
if (imageNumber < m_currentImageNumber) {
// start from the beginning
if (!device()->seek(m_firstFrameOffset)) {
return false;
}
}
while (m_currentImageNumber < imageNumber) {
if (!jumpToNextImage()) {
device()->seek(oldPos);
return false;
}
}
m_currentImageNumber = imageNumber;
return true;
}
bool ANIHandler::jumpToNextImage()
{
if (!ensureScanned()) {
return false;
}
// If we have a custom image sequence we have a index of frames we can jump to
// Delegate to jumpToImage
if (!m_imageSequence.isEmpty()) {
return jumpToImage(m_currentImageNumber + 1);
}
if (device()->pos() < m_firstFrameOffset) {
if (!device()->seek(m_firstFrameOffset)) {
return false;
}
}
const QByteArray nextFrame = device()->peek(sizeof(ChunkHeader));
if (nextFrame.size() != sizeof(ChunkHeader)) {
return false;
}
const auto *header = reinterpret_cast<const ChunkHeader *>(nextFrame.data());
if (qstrncmp(header->magic, "icon", sizeof(header->magic)) != 0) {
return false;
}
const qint64 seekBy = sizeof(ChunkHeader) + header->size;
if (!device()->seek(device()->pos() + seekBy)) {
return false;
}
++m_currentImageNumber;
return true;
}
int ANIHandler::loopCount() const
{
if (!ensureScanned()) {
return 0;
}
return -1;
}
int ANIHandler::nextImageDelay() const
{
if (!ensureScanned()) {
return 0;
}
int rate = m_displayRate;
if (!m_displayRates.isEmpty()) {
int previousImage = m_currentImageNumber - 1;
if (previousImage < 0) {
previousImage = m_displayRates.count() - 1;
}
rate = m_displayRates.at(previousImage);
}
return rate * 1000 / 60;
}
bool ANIHandler::supportsOption(ImageOption option) const
{
return option == Size || option == Name || option == Description || option == Animation;
}
QVariant ANIHandler::option(ImageOption option) const
{
if (!supportsOption(option) || !ensureScanned()) {
return QVariant();
}
switch (option) {
case QImageIOHandler::Size:
return m_size;
// TODO QImageIOHandler::Format
// but both iBitCount in AniHeader and bColorCount are just zero most of the time
// so one would probably need to traverse even further down into IcoHeader and IconDirEntry...
// but Qt's ICO/CUR handler always seems to give us a ARB
case QImageIOHandler::Name:
return m_name;
case QImageIOHandler::Description: {
QString description;
if (!m_name.isEmpty()) {
description += QStringLiteral("Title: %1\n\n").arg(m_name);
}
if (!m_artist.isEmpty()) {
description += QStringLiteral("Author: %1\n\n").arg(m_artist);
}
return description;
}
case QImageIOHandler::Animation:
return true;
default:
break;
}
return QVariant();
}
bool ANIHandler::ensureScanned() const
{
if (m_scanned) {
return true;
}
if (device()->isSequential()) {
return false;
}
auto *mutableThis = const_cast<ANIHandler *>(this);
const auto oldPos = device()->pos();
auto cleanup = qScopeGuard([this, oldPos] {
device()->seek(oldPos);
});
device()->seek(0);
const QByteArray riffIntro = device()->read(4);
if (riffIntro != "RIFF") {
return false;
}
const auto riffSizeData = device()->read(sizeof(quint32_le));
if (riffSizeData.size() != sizeof(quint32_le)) {
return false;
}
const auto riffSize = *(reinterpret_cast<const quint32_le *>(riffSizeData.data()));
// TODO do a basic sanity check if the size is enough to hold some metadata and a frame?
if (riffSize == 0) {
return false;
}
mutableThis->m_displayRates.clear();
mutableThis->m_imageSequence.clear();
while (device()->pos() < riffSize) {
const QByteArray chunkId = device()->read(4);
if (chunkId.length() != 4) {
return false;
}
if (chunkId == "ACON") {
continue;
}
const QByteArray chunkSizeData = device()->read(sizeof(quint32_le));
if (chunkSizeData.length() != sizeof(quint32_le)) {
return false;
}
auto chunkSize = *(reinterpret_cast<const quint32_le *>(chunkSizeData.data()));
if (chunkId == "anih") {
if (chunkSize != sizeof(AniHeader)) {
qWarning() << "anih chunk size does not match ANIHEADER size";
return false;
}
const QByteArray anihData = device()->read(sizeof(AniHeader));
if (anihData.size() != sizeof(AniHeader)) {
return false;
}
auto *aniHeader = reinterpret_cast<const AniHeader *>(anihData.data());
// The size in the ani header is usually 0 unfortunately,
// so we'll also check the first frame for its size further below
mutableThis->m_size = QSize(aniHeader->iWidth, aniHeader->iHeight);
mutableThis->m_frameCount = aniHeader->nFrames;
mutableThis->m_imageCount = aniHeader->nSteps;
mutableThis->m_displayRate = aniHeader->iDispRate;
} else if (chunkId == "rate" || chunkId == "seq ") {
const QByteArray data = device()->read(chunkSize);
if (static_cast<quint32_le>(data.size()) != chunkSize || data.size() % sizeof(quint32_le) != 0) {
return false;
}
// TODO should we check that the number of rate entries matches nSteps?
auto *dataPtr = data.data();
QVector<int> list;
for (int i = 0; i < data.count(); i += sizeof(quint32_le)) {
const auto entry = *(reinterpret_cast<const quint32_le *>(dataPtr + i));
list.append(entry);
}
if (chunkId == "rate") {
// should we check that the number of rate entries matches nSteps?
mutableThis->m_displayRates = list;
} else if (chunkId == "seq ") {
// Check if it's just an ascending sequence, don't bother with it then
bool isAscending = true;
for (int i = 0; i < list.count(); ++i) {
if (list.at(i) != i) {
isAscending = false;
break;
}
}
if (!isAscending) {
mutableThis->m_imageSequence = list;
}
}
// IART and INAM are technically inside LIST->INFO but "INFO" is supposedly optional
// so just handle those two attributes wherever we encounter them
} else if (chunkId == "INAM" || chunkId == "IART") {
const QByteArray value = device()->read(chunkSize);
if (static_cast<quint32_le>(value.size()) != chunkSize) {
return false;
}
// DWORDs are aligned to even sizes
if (chunkSize % 2 != 0) {
device()->read(1);
}
// FIXME encoding
const QString stringValue = QString::fromLocal8Bit(value);
if (chunkId == "INAM") {
mutableThis->m_name = stringValue;
} else if (chunkId == "IART") {
mutableThis->m_artist = stringValue;
}
} else if (chunkId == "LIST") {
const QByteArray listType = device()->read(4);
if (listType == "INFO") {
// Technically would contain INAM and IART but we handle them anywhere above
} else if (listType == "fram") {
quint64 read = 0;
while (read < chunkSize) {
const QByteArray chunkType = device()->read(4);
read += 4;
if (chunkType != "icon") {
break;
}
if (!m_firstFrameOffset) {
mutableThis->m_firstFrameOffset = device()->pos() - 4;
mutableThis->m_currentImageNumber = 0;
// If size in header isn't valid, use the first frame's size instead
if (!m_size.isValid() || m_size.isEmpty()) {
const auto oldPos = device()->pos();
device()->read(sizeof(quint32_le));
const QByteArray curHeaderData = device()->read(sizeof(CurHeader));
const QByteArray cursorDirEntryData = device()->read(sizeof(CursorDirEntry));
if (curHeaderData.length() == sizeof(CurHeader) && cursorDirEntryData.length() == sizeof(CursorDirEntry)) {
auto *cursorDirEntry = reinterpret_cast<const CursorDirEntry *>(cursorDirEntryData.data());
mutableThis->m_size = QSize(cursorDirEntry->bWidth, cursorDirEntry->bHeight);
}
device()->seek(oldPos);
}
// If we don't have a custom image sequence we can stop scanning right here
if (m_imageSequence.isEmpty()) {
break;
}
}
mutableThis->m_frameOffsets.append(device()->pos() - 4);
const QByteArray frameSizeData = device()->read(sizeof(quint32_le));
if (frameSizeData.size() != sizeof(quint32_le)) {
return false;
}
const auto frameSize = *(reinterpret_cast<const quint32_le *>(frameSizeData.data()));
device()->seek(device()->pos() + frameSize);
read += frameSize;
if (m_frameOffsets.count() == m_frameCount) {
// Also record the end of frame data
mutableThis->m_frameOffsets.append(device()->pos() - 4);
break;
}
}
break;
}
}
}
if (m_imageCount != m_frameCount && m_imageSequence.isEmpty()) {
qWarning("ANIHandler: 'nSteps' is not equal to 'nFrames' but no 'seq' entries were provided");
return false;
}
if (!m_imageSequence.isEmpty() && m_imageSequence.count() != m_imageCount) {
qWarning("ANIHandler: count of entries in 'seq' does not match 'nSteps' in anih");
return false;
}
if (!m_displayRates.isEmpty() && m_displayRates.count() != m_imageCount) {
qWarning("ANIHandler: count of entries in 'rate' does not match 'nSteps' in anih");
return false;
}
if (!m_frameOffsets.isEmpty() && m_frameOffsets.count() - 1 != m_frameCount) {
qWarning("ANIHandler: number of actual frames does not match 'nFrames' in anih");
return false;
}
mutableThis->m_scanned = true;
return true;
}
bool ANIHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("ANIHandler::canRead() called with no device");
return false;
}
const QByteArray riffIntro = device->peek(12);
if (riffIntro.length() != 12) {
return false;
}
if (!riffIntro.startsWith("RIFF")) {
return false;
}
// TODO sanity check chunk size?
if (riffIntro.mid(4 + 4, 4) != "ACON") {
return false;
}
return true;
}
QImageIOPlugin::Capabilities ANIPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "ani") {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && ANIHandler::canRead(device)) {
cap |= CanRead;
}
return cap;
}
QImageIOHandler *ANIPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new ANIHandler;
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=ani
X-KDE-MimeType=application/x-navi-animation
X-KDE-Read=true
X-KDE-Write=false

View File

@ -0,0 +1,4 @@
{
"Keys": [ "ani" ],
"MimeTypes": [ "application/x-navi-animation" ]
}

68
src/imageformats/ani_p.h Normal file
View File

@ -0,0 +1,68 @@
/*
SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIMG_ANI_P_H
#define KIMG_ANI_P_H
#include <QImageIOPlugin>
#include <QSize>
class ANIHandler : public QImageIOHandler
{
public:
ANIHandler();
bool canRead() const override;
bool read(QImage *image) override;
int currentImageNumber() const override;
int imageCount() const override;
bool jumpToImage(int imageNumber) override;
bool jumpToNextImage() override;
int loopCount() const override;
int nextImageDelay() const override;
bool supportsOption(ImageOption option) const override;
QVariant option(ImageOption option) const override;
static bool canRead(QIODevice *device);
private:
bool ensureScanned() const;
bool m_scanned = false;
int m_currentImageNumber = 0;
int m_frameCount = 0; // "physical" frames
int m_imageCount = 0; // logical images
// Stores a custom sequence of images
QVector<int> m_imageSequence;
// and the corresponding offsets where they are
// since we can't read the image data sequentally in this case then
QVector<qint64> m_frameOffsets;
qint64 m_firstFrameOffset = 0;
int m_displayRate = 0;
QVector<int> m_displayRates;
QString m_name;
QString m_artist;
QSize m_size;
};
class ANIPlugin : public QImageIOPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "ani.json")
public:
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
};
#endif // KIMG_ANI_P_H

968
src/imageformats/avif.cpp Normal file
View File

@ -0,0 +1,968 @@
/*
AV1 Image File Format (AVIF) support for QImage.
SPDX-FileCopyrightText: 2020 Daniel Novomesky <dnovomesky@gmail.com>
SPDX-License-Identifier: BSD-2-Clause
*/
#include <QThread>
#include <QtGlobal>
#include <QColorSpace>
#include "avif_p.h"
#include <cfloat>
QAVIFHandler::QAVIFHandler()
: m_parseState(ParseAvifNotParsed)
, m_quality(52)
, m_container_width(0)
, m_container_height(0)
, m_rawAvifData(AVIF_DATA_EMPTY)
, m_decoder(nullptr)
, m_must_jump_to_next_image(false)
{
}
QAVIFHandler::~QAVIFHandler()
{
if (m_decoder) {
avifDecoderDestroy(m_decoder);
}
}
bool QAVIFHandler::canRead() const
{
if (m_parseState == ParseAvifNotParsed && !canRead(device())) {
return false;
}
if (m_parseState != ParseAvifError) {
setFormat("avif");
return true;
}
return false;
}
bool QAVIFHandler::canRead(QIODevice *device)
{
if (!device) {
return false;
}
QByteArray header = device->peek(144);
if (header.size() < 12) {
return false;
}
avifROData input;
input.data = (const uint8_t *)header.constData();
input.size = header.size();
if (avifPeekCompatibleFileType(&input)) {
return true;
}
return false;
}
bool QAVIFHandler::ensureParsed() const
{
if (m_parseState == ParseAvifSuccess) {
return true;
}
if (m_parseState == ParseAvifError) {
return false;
}
QAVIFHandler *that = const_cast<QAVIFHandler *>(this);
return that->ensureDecoder();
}
bool QAVIFHandler::ensureDecoder()
{
if (m_decoder) {
return true;
}
m_rawData = device()->readAll();
m_rawAvifData.data = (const uint8_t *)m_rawData.constData();
m_rawAvifData.size = m_rawData.size();
if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
m_parseState = ParseAvifError;
return false;
}
m_decoder = avifDecoderCreate();
avifResult decodeResult;
decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: avifDecoderSetIOMemory failed: %s", avifResultToString(decodeResult));
avifDecoderDestroy(m_decoder);
m_decoder = nullptr;
m_parseState = ParseAvifError;
return false;
}
decodeResult = avifDecoderParse(m_decoder);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to parse input: %s", avifResultToString(decodeResult));
avifDecoderDestroy(m_decoder);
m_decoder = nullptr;
m_parseState = ParseAvifError;
return false;
}
decodeResult = avifDecoderNextImage(m_decoder);
if (decodeResult == AVIF_RESULT_OK) {
m_container_width = m_decoder->image->width;
m_container_height = m_decoder->image->height;
if ((m_container_width > 32768) || (m_container_height > 32768)) {
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
m_parseState = ParseAvifError;
return false;
}
if ((m_container_width == 0) || (m_container_height == 0)) {
qWarning("Empty image, nothing to decode");
m_parseState = ParseAvifError;
return false;
}
m_parseState = ParseAvifSuccess;
if (decode_one_frame()) {
return true;
} else {
m_parseState = ParseAvifError;
return false;
}
} else {
qWarning("ERROR: Failed to decode image: %s", avifResultToString(decodeResult));
}
avifDecoderDestroy(m_decoder);
m_decoder = nullptr;
m_parseState = ParseAvifError;
return false;
}
bool QAVIFHandler::decode_one_frame()
{
if (!ensureParsed()) {
return false;
}
bool loadalpha;
if (m_decoder->image->alphaPlane) {
loadalpha = true;
} else {
loadalpha = false;
}
QImage::Format resultformat;
if (m_decoder->image->depth > 8) {
if (loadalpha) {
resultformat = QImage::Format_RGBA64;
} else {
resultformat = QImage::Format_RGBX64;
}
} else {
if (loadalpha) {
resultformat = QImage::Format_RGBA8888;
} else {
resultformat = QImage::Format_RGB888;
}
}
QImage result(m_decoder->image->width, m_decoder->image->height, resultformat);
if (result.isNull()) {
qWarning("Memory cannot be allocated");
return false;
}
QColorSpace colorspace;
if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) {
const QByteArray icc_data((const char *)m_decoder->image->icc.data, (int)m_decoder->image->icc.size);
colorspace = QColorSpace::fromIccProfile(icc_data);
if (!colorspace.isValid()) {
qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
}
} else {
float prim[8] = {0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f};
// outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY
avifColorPrimariesGetValues(m_decoder->image->colorPrimaries, prim);
const QPointF redPoint(QAVIFHandler::CompatibleChromacity(prim[0], prim[1]));
const QPointF greenPoint(QAVIFHandler::CompatibleChromacity(prim[2], prim[3]));
const QPointF bluePoint(QAVIFHandler::CompatibleChromacity(prim[4], prim[5]));
const QPointF whitePoint(QAVIFHandler::CompatibleChromacity(prim[6], prim[7]));
QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom;
float q_trc_gamma = 0.0f;
switch (m_decoder->image->transferCharacteristics) {
/* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
case 4:
q_trc = QColorSpace::TransferFunction::Gamma;
q_trc_gamma = 2.2f;
break;
/* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
case 5:
q_trc = QColorSpace::TransferFunction::Gamma;
q_trc_gamma = 2.8f;
break;
/* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
case 8:
q_trc = QColorSpace::TransferFunction::Linear;
break;
/* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
case 0:
case 2: /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
case 13:
q_trc = QColorSpace::TransferFunction::SRgb;
break;
default:
qWarning("CICP colorPrimaries: %d, transferCharacteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
m_decoder->image->colorPrimaries,
m_decoder->image->transferCharacteristics);
q_trc = QColorSpace::TransferFunction::SRgb;
break;
}
if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt
switch (m_decoder->image->colorPrimaries) {
/* AVIF_COLOR_PRIMARIES_BT709 */
case 0:
case 1:
case 2: /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */
colorspace = QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma);
break;
/* AVIF_COLOR_PRIMARIES_SMPTE432 */
case 12:
colorspace = QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma);
break;
default:
colorspace = QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma);
break;
}
}
if (!colorspace.isValid()) {
qWarning("AVIF plugin created invalid QColorSpace from NCLX/CICP!");
}
}
result.setColorSpace(colorspace);
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, m_decoder->image);
if (m_decoder->image->depth > 8) {
rgb.depth = 16;
rgb.format = AVIF_RGB_FORMAT_RGBA;
if (!loadalpha) {
rgb.ignoreAlpha = AVIF_TRUE;
result.fill(Qt::black);
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
resultformat = QImage::Format_Grayscale16;
}
}
} else {
rgb.depth = 8;
if (loadalpha) {
rgb.format = AVIF_RGB_FORMAT_RGBA;
resultformat = QImage::Format_ARGB32;
} else {
rgb.format = AVIF_RGB_FORMAT_RGB;
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
resultformat = QImage::Format_Grayscale8;
} else {
resultformat = QImage::Format_RGB32;
}
}
}
rgb.rowBytes = result.bytesPerLine();
rgb.pixels = result.bits();
avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb);
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageYUVToRGB: %s", avifResultToString(res));
return false;
}
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
&& (m_decoder->image->clap.vertOffD > 0)) {
int new_width, new_height, offx, offy;
new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
if (new_width > result.width()) {
new_width = result.width();
}
new_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
if (new_height > result.height()) {
new_height = result.height();
}
if (new_width > 0 && new_height > 0) {
offx = ((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5;
if (offx < 0) {
offx = 0;
} else if (offx > (result.width() - new_width)) {
offx = result.width() - new_width;
}
offy = ((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5;
if (offy < 0) {
offy = 0;
} else if (offy > (result.height() - new_height)) {
offy = result.height() - new_height;
}
result = result.copy(offx, offy, new_width, new_height);
}
}
else { // Zero values, we need to avoid 0 divide.
qWarning("ERROR: Wrong values in avifCleanApertureBox");
}
}
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
QTransform transform;
switch (m_decoder->image->irot.angle) {
case 1:
transform.rotate(-90);
result = result.transformed(transform);
break;
case 2:
transform.rotate(180);
result = result.transformed(transform);
break;
case 3:
transform.rotate(90);
result = result.transformed(transform);
break;
}
}
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) {
#endif
case 0: // top-to-bottom
result = result.mirrored(false, true);
break;
case 1: // left-to-right
result = result.mirrored(true, false);
break;
}
}
if (resultformat == result.format()) {
m_current_image = result;
} else {
m_current_image = result.convertToFormat(resultformat);
}
m_must_jump_to_next_image = false;
return true;
}
bool QAVIFHandler::read(QImage *image)
{
if (!ensureParsed()) {
return false;
}
if (m_must_jump_to_next_image) {
jumpToNextImage();
}
*image = m_current_image;
if (imageCount() >= 2) {
m_must_jump_to_next_image = true;
}
return true;
}
bool QAVIFHandler::write(const QImage &image)
{
if (image.format() == QImage::Format_Invalid) {
qWarning("No image data to save");
return false;
}
if ((image.width() > 32768) || (image.height() > 32768)) {
qWarning("Image is too large");
return false;
}
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
int minQuantizer = 0;
int maxQuantizerAlpha = 0;
avifResult res;
bool save_grayscale; // true - monochrome, false - colors
int save_depth; // 8 or 10bit per channel
QImage::Format tmpformat; // format for temporary image
avifImage *avif = nullptr;
// grayscale detection
switch (image.format()) {
case QImage::Format_Mono:
case QImage::Format_MonoLSB:
case QImage::Format_Grayscale8:
case QImage::Format_Grayscale16:
save_grayscale = true;
break;
case QImage::Format_Indexed8:
save_grayscale = image.isGrayscale();
break;
default:
save_grayscale = false;
break;
}
// 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 = 10;
break;
default:
if (image.depth() > 32) {
save_depth = 10;
} else {
save_depth = 8;
}
break;
}
// quality settings
if (maxQuantizer > 20) {
minQuantizer = maxQuantizer - 20;
if (maxQuantizer > 40) { // we decrease quality of alpha channel here
maxQuantizerAlpha = maxQuantizer - 40;
}
}
if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
if (save_depth > 8) {
tmpformat = QImage::Format_Grayscale16;
} else {
tmpformat = QImage::Format_Grayscale8;
}
QImage tmpgrayimage = image.convertToFormat(tmpformat);
avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
if (tmpgrayimage.colorSpace().isValid()) {
avif->colorPrimaries = (avifColorPrimaries)1;
avif->matrixCoefficients = (avifMatrixCoefficients)1;
switch (tmpgrayimage.colorSpace().transferFunction()) {
case QColorSpace::TransferFunction::Linear:
/* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
avif->transferCharacteristics = (avifTransferCharacteristics)8;
break;
case QColorSpace::TransferFunction::SRgb:
/* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
avif->transferCharacteristics = (avifTransferCharacteristics)13;
break;
default:
/* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
break;
}
}
if (save_depth > 8) { // QImage::Format_Grayscale16
for (int y = 0; y < tmpgrayimage.height(); 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]);
for (int x = 0; x < tmpgrayimage.width(); x++) {
int tmp_pixelval = (int)(((float)(*src16bit) / 65535.0f) * 1023.0f + 0.5f); // downgrade to 10 bits
*dest16bit = qBound(0, tmp_pixelval, 1023);
dest16bit++;
src16bit++;
}
}
} else { // QImage::Format_Grayscale8
for (int y = 0; y < tmpgrayimage.height(); y++) {
const uchar *src8bit = tmpgrayimage.constScanLine(y);
uint8_t *dest8bit = avif->yuvPlanes[0] + y * avif->yuvRowBytes[0];
for (int x = 0; x < tmpgrayimage.width(); x++) {
*dest8bit = *src8bit;
dest8bit++;
src8bit++;
}
}
}
} else { // we are going to save color image
if (save_depth > 8) {
if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA64;
} else {
tmpformat = QImage::Format_RGBX64;
}
} else { // 8bit depth
if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA8888;
} else {
tmpformat = QImage::Format_RGB888;
}
}
QImage tmpcolorimage = image.convertToFormat(tmpformat);
avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
if (maxQuantizer < 20) {
if (maxQuantizer < 10) {
pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
} else {
pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
}
}
avifMatrixCoefficients matrix_to_save = (avifMatrixCoefficients)1; // default for Qt 5.12 and 5.13;
avifColorPrimaries primaries_to_save = (avifColorPrimaries)2;
avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2;
QByteArray iccprofile;
if (tmpcolorimage.colorSpace().isValid()) {
switch (tmpcolorimage.colorSpace().primaries()) {
case QColorSpace::Primaries::SRgb:
/* AVIF_COLOR_PRIMARIES_BT709 */
primaries_to_save = (avifColorPrimaries)1;
/* AVIF_MATRIX_COEFFICIENTS_BT709 */
matrix_to_save = (avifMatrixCoefficients)1;
break;
case QColorSpace::Primaries::DciP3D65:
/* AVIF_NCLX_COLOUR_PRIMARIES_P3, AVIF_NCLX_COLOUR_PRIMARIES_SMPTE432 */
primaries_to_save = (avifColorPrimaries)12;
/* AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL */
matrix_to_save = (avifMatrixCoefficients)12;
break;
default:
/* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
primaries_to_save = (avifColorPrimaries)2;
/* AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED */
matrix_to_save = (avifMatrixCoefficients)2;
break;
}
switch (tmpcolorimage.colorSpace().transferFunction()) {
case QColorSpace::TransferFunction::Linear:
/* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
transfer_to_save = (avifTransferCharacteristics)8;
break;
case QColorSpace::TransferFunction::Gamma:
if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.2f) < 0.1f) {
/* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
transfer_to_save = (avifTransferCharacteristics)4;
} else if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.8f) < 0.1f) {
/* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
transfer_to_save = (avifTransferCharacteristics)5;
} else {
/* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
transfer_to_save = (avifTransferCharacteristics)2;
}
break;
case QColorSpace::TransferFunction::SRgb:
/* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
transfer_to_save = (avifTransferCharacteristics)13;
break;
default:
/* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
transfer_to_save = (avifTransferCharacteristics)2;
break;
}
// in case primaries or trc were not identified
if ((primaries_to_save == 2) || (transfer_to_save == 2)) {
// upgrade image to higher bit depth
if (save_depth == 8) {
save_depth = 10;
if (tmpcolorimage.hasAlphaChannel()) {
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
} else {
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
}
}
if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
switch (transfer_to_save) {
case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
break;
case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
break;
case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
break;
default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
transfer_to_save = (avifTransferCharacteristics)13;
break;
}
} else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
transfer_to_save = (avifTransferCharacteristics)13;
tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
} else { // unrecognized profile
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
transfer_to_save = (avifTransferCharacteristics)13;
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
}
}
} else { // profile is unsupported by Qt
iccprofile = tmpcolorimage.colorSpace().iccProfile();
if (iccprofile.size() > 0) {
matrix_to_save = (avifMatrixCoefficients)6;
}
}
avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
avif->matrixCoefficients = matrix_to_save;
avif->colorPrimaries = primaries_to_save;
avif->transferCharacteristics = transfer_to_save;
if (iccprofile.size() > 0) {
avifImageSetProfileICC(avif, (const uint8_t *)iccprofile.constData(), iccprofile.size());
}
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, avif);
rgb.rowBytes = tmpcolorimage.bytesPerLine();
rgb.pixels = const_cast<uint8_t *>(tmpcolorimage.constBits());
if (save_depth > 8) { // 10bit depth
rgb.depth = 16;
if (tmpcolorimage.hasAlphaChannel()) {
avif->alphaRange = AVIF_RANGE_FULL;
} else {
rgb.ignoreAlpha = AVIF_TRUE;
}
rgb.format = AVIF_RGB_FORMAT_RGBA;
} else { // 8bit depth
rgb.depth = 8;
if (tmpcolorimage.hasAlphaChannel()) {
rgb.format = AVIF_RGB_FORMAT_RGBA;
avif->alphaRange = AVIF_RANGE_FULL;
} else {
rgb.format = AVIF_RGB_FORMAT_RGB;
}
}
res = avifImageRGBToYUV(avif, &rgb);
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
return false;
}
}
avifRWData raw = AVIF_DATA_EMPTY;
avifEncoder *encoder = avifEncoderCreate();
encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
encoder->minQuantizer = minQuantizer;
encoder->maxQuantizer = maxQuantizer;
if (image.hasAlphaChannel()) {
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
encoder->maxQuantizerAlpha = maxQuantizerAlpha;
}
encoder->speed = 8;
res = avifEncoderWrite(encoder, avif, &raw);
avifEncoderDestroy(encoder);
avifImageDestroy(avif);
if (res == AVIF_RESULT_OK) {
qint64 status = device()->write((const char *)raw.data, raw.size);
avifRWDataFree(&raw);
if (status > 0) {
return true;
} else if (status == -1) {
qWarning("Write error: %s", qUtf8Printable(device()->errorString()));
return false;
}
} else {
qWarning("ERROR: Failed to encode: %s", avifResultToString(res));
}
return false;
}
QVariant QAVIFHandler::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();
case Animation:
if (imageCount() >= 2) {
return true;
} else {
return false;
}
default:
return QVariant();
}
}
void QAVIFHandler::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 = 52;
}
return;
default:
break;
}
QImageIOHandler::setOption(option, value);
}
bool QAVIFHandler::supportsOption(ImageOption option) const
{
return option == Quality || option == Size || option == Animation;
}
int QAVIFHandler::imageCount() const
{
if (!ensureParsed()) {
return 0;
}
if (m_decoder->imageCount >= 1) {
return m_decoder->imageCount;
}
return 0;
}
int QAVIFHandler::currentImageNumber() const
{
if (m_parseState == ParseAvifNotParsed) {
return -1;
}
if (m_parseState == ParseAvifError || !m_decoder) {
return 0;
}
return m_decoder->imageIndex;
}
bool QAVIFHandler::jumpToNextImage()
{
if (!ensureParsed()) {
return false;
}
if (m_decoder->imageCount < 2) {
return true;
}
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from begining
avifDecoderReset(m_decoder);
}
avifResult decodeResult = avifDecoderNextImage(m_decoder);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
m_parseState = ParseAvifError;
return false;
}
if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!",
m_decoder->image->width,
m_decoder->image->height,
m_container_width,
m_container_height);
m_parseState = ParseAvifError;
return false;
}
if (decode_one_frame()) {
return true;
} else {
m_parseState = ParseAvifError;
return false;
}
}
bool QAVIFHandler::jumpToImage(int imageNumber)
{
if (!ensureParsed()) {
return false;
}
if (m_decoder->imageCount < 2) { // not an animation
if (imageNumber == 0) {
return true;
} else {
return false;
}
}
if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
return false;
}
if (imageNumber == m_decoder->imageCount) { // we are here already
return true;
}
avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult));
m_parseState = ParseAvifError;
return false;
}
if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!",
m_decoder->image->width,
m_decoder->image->height,
m_container_width,
m_container_height);
m_parseState = ParseAvifError;
return false;
}
if (decode_one_frame()) {
return true;
} else {
m_parseState = ParseAvifError;
return false;
}
}
int QAVIFHandler::nextImageDelay() const
{
if (!ensureParsed()) {
return 0;
}
if (m_decoder->imageCount < 2) {
return 0;
}
int delay_ms = 1000.0 * m_decoder->imageTiming.duration;
if (delay_ms < 1) {
delay_ms = 1;
}
return delay_ms;
}
int QAVIFHandler::loopCount() const
{
if (!ensureParsed()) {
return 0;
}
if (m_decoder->imageCount < 2) {
return 0;
}
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
{
if (format == "avif") {
return Capabilities(CanRead | CanWrite);
}
if (format == "avifs") {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && QAVIFHandler::canRead(device)) {
cap |= CanRead;
}
if (device->isWritable()) {
cap |= CanWrite;
}
return cap;
}
QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new QAVIFHandler;
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=avif
X-KDE-MimeType=image/avif
X-KDE-Read=true
X-KDE-Write=true

View File

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

82
src/imageformats/avif_p.h Normal file
View File

@ -0,0 +1,82 @@
/*
AV1 Image File Format (AVIF) support for QImage.
SPDX-FileCopyrightText: 2020 Daniel Novomesky <dnovomesky@gmail.com>
SPDX-License-Identifier: BSD-2-Clause
*/
#ifndef KIMG_AVIF_P_H
#define KIMG_AVIF_P_H
#include <QByteArray>
#include <QImage>
#include <QImageIOPlugin>
#include <QPointF>
#include <QVariant>
#include <avif/avif.h>
#include <qimageiohandler.h>
class QAVIFHandler : public QImageIOHandler
{
public:
QAVIFHandler();
~QAVIFHandler();
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:
static QPointF CompatibleChromacity(qreal chrX, qreal chrY);
bool ensureParsed() const;
bool ensureDecoder();
bool decode_one_frame();
enum ParseAvifState {
ParseAvifError = -1,
ParseAvifNotParsed = 0,
ParseAvifSuccess = 1,
};
ParseAvifState m_parseState;
int m_quality;
uint32_t m_container_width;
uint32_t m_container_height;
QByteArray m_rawData;
avifROData m_rawAvifData;
avifDecoder *m_decoder;
QImage m_current_image;
bool m_must_jump_to_next_image;
};
class QAVIFPlugin : public QImageIOPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "avif.json")
public:
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
};
#endif // KIMG_AVIF_P_H

View File

@ -9,13 +9,13 @@
*/
#include "eps_p.h"
#include <QCoreApplication>
#include <QImage>
#include <QImageReader>
#include <QPainter>
#include <QPrinter>
#include <QProcess>
#include <QTemporaryFile>
#include <QCoreApplication>
// logging category for this framework, default: log stuff >= warning
Q_LOGGING_CATEGORY(EPSPLUGIN, "kf.imageformats.plugins.eps", QtWarningMsg)
@ -51,19 +51,13 @@ static bool seekToCodeStart(QIODevice *io, qint64 &ps_offset, qint64 &ps_size)
return false;
}
ps_offset // Offset is in little endian
= qint64(((unsigned char)buf[0])
+ ((unsigned char)buf[1] << 8)
+ ((unsigned char)buf[2] << 16)
+ ((unsigned char)buf[3] << 24));
= qint64(((unsigned char)buf[0]) + ((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.
qCDebug(EPSPLUGIN) << "cannot read size of MS-DOS EPS file";
return false;
}
ps_size // Size is in little endian
= qint64(((unsigned char)buf[0])
+ ((unsigned char)buf[1] << 8)
+ ((unsigned char)buf[2] << 16)
+ ((unsigned char)buf[3] << 24));
= qint64(((unsigned char)buf[0]) + ((unsigned char)buf[1] << 8) + ((unsigned char)buf[2] << 16) + ((unsigned char)buf[3] << 24));
qCDebug(EPSPLUGIN) << "Offset: " << ps_offset << " Size: " << ps_size;
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";
@ -101,8 +95,7 @@ static bool bbox(QIODevice *io, int *x1, int *y1, int *x2, int *y2)
// Some EPS files have non-integer values for the bbox
// We don't support that currently, but at least we parse it
float _x1, _y1, _x2, _y2;
if (sscanf(buf, "%*s %f %f %f %f",
&_x1, &_y1, &_x2, &_y2) == 4) {
if (sscanf(buf, "%*s %f %f %f %f", &_x1, &_y1, &_x2, &_y2) == 4) {
qCDebug(EPSPLUGIN) << "BBOX: " << _x1 << " " << _y1 << " " << _x2 << " " << _y2;
*x1 = int(_x1);
*y1 = int(_y1);
@ -177,23 +170,17 @@ bool EPSHandler::read(QImage *image)
// create GS command line
QStringList gsArgs;
gsArgs << QLatin1String("-sOutputFile=") + tmpFile.fileName()
<< QStringLiteral("-q")
<< QStringLiteral("-g%1x%2").arg(wantedWidth).arg(wantedHeight)
<< QStringLiteral("-dSAFER")
<< QStringLiteral("-dPARANOIDSAFER")
<< QStringLiteral("-dNOPAUSE")
<< QStringLiteral("-sDEVICE=ppm")
gsArgs << QLatin1String("-sOutputFile=") + tmpFile.fileName() << QStringLiteral("-q") << QStringLiteral("-g%1x%2").arg(wantedWidth).arg(wantedHeight)
<< QStringLiteral("-dSAFER") << QStringLiteral("-dPARANOIDSAFER") << QStringLiteral("-dNOPAUSE") << QStringLiteral("-sDEVICE=ppm")
<< QStringLiteral("-c")
<< QStringLiteral("0 0 moveto "
"1000 0 lineto "
"1000 1000 lineto "
"0 1000 lineto "
"1 1 254 255 div setrgbcolor fill "
"0 0 0 setrgbcolor")
<< QStringLiteral("-")
<< QStringLiteral("-c")
<< QStringLiteral("showpage quit");
<< QStringLiteral(
"0 0 moveto "
"1000 0 lineto "
"1000 1000 lineto "
"0 1000 lineto "
"1 1 254 255 div setrgbcolor fill "
"0 0 0 setrgbcolor")
<< QStringLiteral("-") << QStringLiteral("-c") << QStringLiteral("showpage quit");
qCDebug(EPSPLUGIN) << "Running gs with args" << gsArgs;
QProcess converter;
@ -264,7 +251,8 @@ bool EPSHandler::write(const QImage &image)
psOut.setOutputFileName(tmpFile.fileName());
psOut.setOutputFormat(QPrinter::PdfFormat);
psOut.setFullPage(true);
psOut.setPaperSize(image.size(), QPrinter::DevicePixel);
const double multiplier = psOut.resolution() <= 0 ? 1.0 : 72.0 / psOut.resolution();
psOut.setPageSize(QPageSize(image.size() * multiplier, QPageSize::Point));
// painting the pixmap to the "printer" which is a file
p.begin(&psOut);
@ -277,24 +265,16 @@ bool EPSHandler::write(const QImage &image)
// pdftops comes with Poppler and produces much smaller EPS files than GhostScript
QStringList pdftopsArgs;
pdftopsArgs << QStringLiteral("-eps")
<< tmpFile.fileName()
<< QStringLiteral("-");
pdftopsArgs << QStringLiteral("-eps") << tmpFile.fileName() << QStringLiteral("-");
qCDebug(EPSPLUGIN) << "Running pdftops with args" << pdftopsArgs;
converter.start(QStringLiteral("pdftops"), pdftopsArgs);
if (!converter.waitForStarted()) {
// GhostScript produces huge files, and takes a long time doing so
QStringList gsArgs;
gsArgs << QStringLiteral("-q") << QStringLiteral("-P-")
<< QStringLiteral("-dNOPAUSE") << QStringLiteral("-dBATCH")
<< QStringLiteral("-dSAFER")
<< QStringLiteral("-sDEVICE=epswrite")
<< QStringLiteral("-sOutputFile=-")
<< QStringLiteral("-c")
<< QStringLiteral("save") << QStringLiteral("pop")
<< QStringLiteral("-f")
<< tmpFile.fileName();
gsArgs << QStringLiteral("-q") << QStringLiteral("-P-") << QStringLiteral("-dNOPAUSE") << QStringLiteral("-dBATCH") << QStringLiteral("-dSAFER")
<< 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;
converter.start(QStringLiteral("gs"), gsArgs);

View File

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

View File

@ -9,39 +9,41 @@
#include "exr_p.h"
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
#include <IexThrowErrnoExc.h>
#include <ImathBox.h>
#include <ImfInputFile.h>
#include <ImfArray.h>
#include <ImfBoxAttribute.h>
#include <ImfChannelListAttribute.h>
#include <ImfCompressionAttribute.h>
#include <ImfConvert.h>
#include <ImfFloatAttribute.h>
#include <ImfInputFile.h>
#include <ImfInt64.h>
#include <ImfIntAttribute.h>
#include <ImfLineOrderAttribute.h>
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
#include <ImfStringAttribute.h>
#include <ImfVecAttribute.h>
#include <ImfArray.h>
#include <ImfConvert.h>
#include <ImfVersion.h>
#include <IexThrowErrnoExc.h>
#include <iostream>
#include <QImage>
#include <QDataStream>
#include <QDebug>
#include <QImage>
#include <QImageIOPlugin>
class K_IStream: public Imf::IStream
class K_IStream : public Imf::IStream
{
public:
K_IStream(QIODevice *dev, const QByteArray &fileName):
IStream(fileName.data()), m_dev(dev)
K_IStream(QIODevice *dev, const QByteArray &fileName)
: IStream(fileName.data())
, m_dev(dev)
{
}
bool read(char c[], int n) override;
bool read(char c[], int n) override;
Imf::Int64 tellg() override;
void seekg(Imf::Int64 pos) override;
void clear() override;
@ -128,19 +130,19 @@ QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
if (a > 1.0) {
a = 1.0 + Imath::Math<float>::log((a - 1.0) * 0.184874 + 1) / 0.184874;
}
//
// 5) Gamma-correct the pixel values, assuming that the
// screen's gamma is 0.4545 (or 1/2.2).
//
// 5) Gamma-correct the pixel values, assuming that the
// screen's gamma is 0.4545 (or 1/2.2).
r = Imath::Math<float>::pow(r, 0.4545);
g = Imath::Math<float>::pow(g, 0.4545);
b = Imath::Math<float>::pow(b, 0.4545);
a = Imath::Math<float>::pow(a, 0.4545);
// 6) Scale the values such that pixels middle gray
// pixels are mapped to 84.66 (or 3.5 f-stops below
// the display's maximum intensity).
//
// 7) Clamp the values to [0, 255].
// 6) Scale the values such that pixels middle gray
// pixels are mapped to 84.66 (or 3.5 f-stops below
// the display's maximum intensity).
//
// 7) Clamp the values to [0, 255].
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(b * 84.66f, 0.f, 255.f)),
@ -169,7 +171,7 @@ bool EXRHandler::read(QImage *outImage)
Imf::RgbaInputFile file(istr);
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;
QImage image(width, height, QImage::Format_RGB32);
@ -196,7 +198,7 @@ bool EXRHandler::read(QImage *outImage)
return true;
} catch (const std::exception &exc) {
// qDebug() << exc.what();
// qDebug() << exc.what();
return false;
}
}

View File

@ -20,13 +20,13 @@ typedef unsigned char uchar;
// From GIMP "tile.h" v1.2
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
const int RANDOM_TABLE_SIZE = 4096; //!< Size of 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
@ -41,10 +41,9 @@ const uchar OPAQUE_OPACITY = 255; //!< Opaque value for 8-bit alpha component.
typedef enum {
RGB,
GRAY,
INDEXED
INDEXED,
} GimpImageBaseType;
// From GIMP "libgimp/gimpenums.h" v2.4
//! Effect to apply when layers are merged together.
@ -74,7 +73,6 @@ typedef enum {
GRAIN_MERGE_MODE
} LayerModeEffects;
// 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;
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)
{
if (saturation == 0) {
hue = value;
hue = value;
saturation = value;
//value = value;
// value = value;
} else {
double h = hue * 6. / 255.;
double s = saturation / 255.;
@ -195,34 +193,34 @@ static void HSVTORGB(uchar &hue, uchar &saturation, uchar &value)
switch ((int)h) {
case 0:
hue = (uchar)(v * 255);
hue = (uchar)(v * 255);
saturation = (uchar)(t * 255);
value = (uchar)(p * 255);
value = (uchar)(p * 255);
break;
case 1:
hue = (uchar)(q * 255);
hue = (uchar)(q * 255);
saturation = (uchar)(v * 255);
value = (uchar)(p * 255);
value = (uchar)(p * 255);
break;
case 2:
hue = (uchar)(p * 255);
hue = (uchar)(p * 255);
saturation = (uchar)(v * 255);
value = (uchar)(t * 255);
value = (uchar)(t * 255);
break;
case 3:
hue = (uchar)(p * 255);
hue = (uchar)(p * 255);
saturation = (uchar)(q * 255);
value = (uchar)(v * 255);
value = (uchar)(v * 255);
break;
case 4:
hue = (uchar)(t * 255);
hue = (uchar)(t * 255);
saturation = (uchar)(p * 255);
value = (uchar)(v * 255);
value = (uchar)(v * 255);
break;
case 5:
hue = (uchar)(v * 255);
hue = (uchar)(v * 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;
blue = (uchar)s;
blue = (uchar)s;
}
/*!
@ -330,8 +328,8 @@ static void HLSTORGB(uchar &hue, uchar &lightness, uchar &saturation)
double s = saturation;
if (s == 0) {
hue = (uchar)l;
lightness = (uchar)l;
hue = (uchar)l;
lightness = (uchar)l;
saturation = (uchar)l;
} else {
double m1, m2;
@ -344,8 +342,8 @@ static void HLSTORGB(uchar &hue, uchar &lightness, uchar &saturation)
m1 = (l / 127.5) - m2;
hue = HLSVALUE(m1, m2, h + 85);
lightness = HLSVALUE(m1, m2, h);
hue = HLSVALUE(m1, m2, h + 85);
lightness = HLSVALUE(m1, m2, h);
saturation = HLSVALUE(m1, m2, h - 85);
}
}

View File

@ -8,8 +8,8 @@
#include "hdr_p.h"
#include <QImage>
#include <QDataStream>
#include <QImage>
#include <QLoggingCategory>
#include <QRegularExpressionMatch>
@ -19,19 +19,18 @@ typedef unsigned char uchar;
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
namespace // Private.
namespace // Private.
{
#define MAXLINE 1024
#define MINELEN 8 // minimum scanline length for encoding
#define MAXELEN 0x7fff // maximum scanline length for encoding
#define MAXLINE 1024
#define MINELEN 8 // minimum scanline length for encoding
#define MAXELEN 0x7fff // maximum scanline length for encoding
static inline uchar ClipToByte(float value)
{
if (value > 255.0f) {
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);
}
@ -39,8 +38,8 @@ static inline uchar ClipToByte(float value)
// if 'first' is true the first byte is already read
static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
{
int rshift = 0;
int i;
int rshift = 0;
int i;
while (width > 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)) {
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];
image += 4;
width--;
@ -81,9 +80,7 @@ static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
v = 1.0f / float(1 << -e);
}
scanline[j] = qRgb(ClipToByte(float(image[0]) * v),
ClipToByte(float(image[1]) * v),
ClipToByte(float(image[2]) * v));
scanline[j] = qRgb(ClipToByte(float(image[0]) * v), ClipToByte(float(image[1]) * v), ClipToByte(float(image[2]) * v));
image += 4;
}
@ -103,10 +100,10 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
QByteArray lineArray;
lineArray.resize(4 * width);
uchar *image = (uchar *) lineArray.data();
uchar *image = (uchar *)lineArray.data();
for (int cline = 0; cline < height; cline++) {
QRgb *scanline = (QRgb *) img.scanLine(cline);
QRgb *scanline = (QRgb *)img.scanLine(cline);
// determine scanline type
if ((width < MINELEN) || (MAXELEN < width)) {
@ -168,7 +165,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
} else {
// non-run
while (code != 0) {
s >> image[i + j * 4];
s >> image[i + j * 4];
j++;
code--;
}
@ -227,8 +224,14 @@ bool HDRHandler::read(QImage *outImage)
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
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());

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

View File

@ -12,9 +12,9 @@
#include <kzip.h>
#include <QImage>
#include <QIODevice>
#include <QFile>
#include <QIODevice>
#include <QImage>
static constexpr char s_magic[] = "application/x-krita";
static constexpr int s_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0
@ -35,12 +35,14 @@ bool KraHandler::canRead() const
bool KraHandler::read(QImage *image)
{
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"));
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");

View File

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

View File

@ -34,12 +34,14 @@ bool OraHandler::canRead() const
bool OraHandler::read(QImage *image)
{
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"));
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");

View File

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

View File

@ -12,8 +12,7 @@
#include <QDebug>
#include <QImage>
#pragma pack(push,1)
#pragma pack(push, 1)
class RGB
{
public:
@ -29,7 +28,6 @@ public:
c.b = qBlue(color);
return c;
}
};
class Palette
@ -37,7 +35,7 @@ class Palette
public:
void setColor(int i, const QRgb color)
{
RGB &c = rgb[ i ];
RGB &c = rgb[i];
c.r = qRed(color);
c.g = qGreen(color);
c.b = qBlue(color);
@ -45,10 +43,10 @@ public:
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
@ -69,8 +67,8 @@ public:
return (Encoding == 1);
}
quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx
quint8 Version; // Version information·
quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx
quint8 Version; // Version information·
// 0 = Version 2.5 of PC Paintbrush·
// 2 = Version 2.8 w/palette information·
// 3 = Version 2.8 w/o palette information·
@ -80,8 +78,8 @@ public:
// and PC Paintbrush +, includes
// Publisher's Paintbrush . Includes
// 24-bit .PCX files·
quint8 Encoding; // 1 = .PCX run length encoding
quint8 Bpp; // Number of bits to represent a pixel
quint8 Encoding; // 1 = .PCX run length encoding
quint8 Bpp; // Number of bits to represent a pixel
// (per Plane) - 1, 2, 4, or 8·
quint16 XMin;
quint16 YMin;
@ -89,17 +87,17 @@ public:
quint16 YMax;
quint16 HDpi;
quint16 YDpi;
Palette ColorMap;
quint8 Reserved; // Should be set to 0.
quint8 NPlanes; // Number of color planes
quint16 BytesPerLine; // Number of bytes to allocate for a scanline
Palette ColorMap;
quint8 Reserved; // Should be set to 0.
quint8 NPlanes; // Number of color planes
quint16 BytesPerLine; // Number of bytes to allocate for a scanline
// plane. MUST be an EVEN number. Do NOT
// 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 + )·
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
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
};
@ -120,7 +118,7 @@ static QDataStream &operator>>(QDataStream &s, RGB &rgb)
static QDataStream &operator>>(QDataStream &s, Palette &pal)
{
for (int i = 0; i < 16; ++i) {
s >> pal.rgb[ i ];
s >> pal.rgb[i];
}
return s;
@ -151,12 +149,16 @@ static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
ph.Reserved = res;
ph.NPlanes = np;
quint16 bytesperline;
s >> bytesperline; ph.BytesPerLine = bytesperline;
s >> bytesperline;
ph.BytesPerLine = bytesperline;
quint16 paletteinfo;
s >> paletteinfo; ph.PaletteInfo = paletteinfo;
s >> paletteinfo;
ph.PaletteInfo = paletteinfo;
quint16 hscreensize, vscreensize;
s >> hscreensize; ph.HScreenSize = hscreensize;
s >> vscreensize; ph.VScreenSize = vscreensize;
s >> hscreensize;
ph.HScreenSize = hscreensize;
s >> vscreensize;
ph.VScreenSize = vscreensize;
// Skip the rest of the header
quint8 byte;
@ -177,7 +179,7 @@ static QDataStream &operator<<(QDataStream &s, const RGB rgb)
static QDataStream &operator<<(QDataStream &s, const Palette &pal)
{
for (int i = 0; i < 16; ++i) {
s << pal.rgb[ i ];
s << pal.rgb[i];
}
return s;
@ -232,14 +234,14 @@ static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
s >> byte;
}
while (count-- && i < size) {
buf[ i++ ] = byte;
buf[i++] = byte;
}
}
} else {
// Image is not compressed (possible?)
while (i < size) {
s >> byte;
buf[ i++ ] = byte;
buf[i++] = byte;
}
}
}
@ -266,7 +268,7 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
uchar *p = img.scanLine(y);
unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
for (unsigned int x = 0; x < bpl; ++x) {
p[ x ] = buf[x];
p[x] = buf[x];
}
}
@ -299,8 +301,8 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
for (int i = 0; i < 4; i++) {
quint32 offset = i * header.BytesPerLine;
for (int x = 0; x < header.width(); ++x)
if (buf[ offset + (x / 8) ] & (128 >> (x % 8))) {
pixbuf[ x ] = (int)(pixbuf[ x ]) + (1 << i);
if (buf[offset + (x / 8)] & (128 >> (x % 8))) {
pixbuf[x] = (int)(pixbuf[x]) + (1 << i);
}
}
@ -309,7 +311,7 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
qWarning() << "Failed to get scanline for" << y << "might be out of bounds";
}
for (int x = 0; x < header.width(); ++x) {
p[ x ] = pixbuf[ x ];
p[x] = pixbuf[x];
}
}
@ -346,13 +348,13 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
for (unsigned int x = 0; x < bpl; ++x) {
p[ x ] = buf[ x ];
p[x] = buf[x];
}
}
quint8 flag;
s >> flag;
// qDebug() << "Palette Flag: " << flag;
// qDebug() << "Palette Flag: " << flag;
if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
// Read the palette
@ -389,7 +391,7 @@ static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
uint *p = (uint *)img.scanLine(y);
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]);
}
}
}
@ -403,9 +405,9 @@ static void writeLine(QDataStream &s, QByteArray &buf)
while (i < size) {
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;
++count;
}
@ -438,7 +440,7 @@ static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
// Invert as QImage uses reverse palette for monochrome images?
for (int i = 0; i < header.BytesPerLine; ++i) {
buf[ i ] = ~p[ i ];
buf[i] = ~p[i];
}
writeLine(s, buf);
@ -457,28 +459,28 @@ static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
s << header;
QByteArray buf[ 4 ];
QByteArray buf[4];
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) {
quint8 *p = img.scanLine(y);
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 i = 0; i < 4; ++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) {
writeLine(s, buf[ i ]);
writeLine(s, buf[i]);
}
}
}
@ -497,7 +499,7 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
quint8 *p = img.scanLine(y);
for (int i = 0; i < header.BytesPerLine; ++i) {
buf[ i ] = p[ i ];
buf[i] = p[i];
}
writeLine(s, buf);
@ -530,9 +532,9 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
for (int x = 0; x < header.width(); ++x) {
QRgb rgb = *p++;
r_buf[ x ] = qRed(rgb);
g_buf[ x ] = qGreen(rgb);
b_buf[ x ] = qBlue(rgb);
r_buf[x] = qRed(rgb);
g_buf[x] = qGreen(rgb);
b_buf[x] = qBlue(rgb);
}
writeLine(s, r_buf);
@ -571,19 +573,19 @@ bool PCXHandler::read(QImage *outImage)
return false;
}
// int w = header.width();
// int h = header.height();
// int w = header.width();
// int h = header.height();
// qDebug() << "Manufacturer: " << header.Manufacturer;
// qDebug() << "Version: " << header.Version;
// qDebug() << "Encoding: " << header.Encoding;
// qDebug() << "Bpp: " << header.Bpp;
// qDebug() << "Width: " << w;
// qDebug() << "Height: " << h;
// qDebug() << "Window: " << header.XMin << "," << header.XMax << ","
// << header.YMin << "," << header.YMax << endl;
// qDebug() << "BytesPerLine: " << header.BytesPerLine;
// qDebug() << "NPlanes: " << header.NPlanes;
// qDebug() << "Manufacturer: " << header.Manufacturer;
// qDebug() << "Version: " << header.Version;
// qDebug() << "Encoding: " << header.Encoding;
// qDebug() << "Bpp: " << header.Bpp;
// qDebug() << "Width: " << w;
// qDebug() << "Height: " << h;
// qDebug() << "Window: " << header.XMin << "," << header.XMax << ","
// << header.YMin << "," << header.YMax << endl;
// qDebug() << "BytesPerLine: " << header.BytesPerLine;
// qDebug() << "NPlanes: " << header.NPlanes;
QImage img;
@ -597,9 +599,9 @@ bool PCXHandler::read(QImage *outImage)
readImage24(img, s, header);
}
// qDebug() << "Image Bytes: " << img.numBytes();
// qDebug() << "Image Bytes Per Line: " << img.bytesPerLine();
// qDebug() << "Image Depth: " << img.depth();
// qDebug() << "Image Bytes: " << img.numBytes();
// qDebug() << "Image Bytes Per Line: " << img.bytesPerLine();
// qDebug() << "Image Depth: " << img.depth();
if (!img.isNull()) {
*outImage = img;
@ -619,11 +621,11 @@ bool PCXHandler::write(const QImage &image)
int w = img.width();
int h = img.height();
// qDebug() << "Width: " << w;
// qDebug() << "Height: " << h;
// 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;

View File

@ -21,9 +21,9 @@
#include <QDebug>
#include <QImage>
#include <QVariant>
#include <qendian.h>
#include <algorithm>
#include <functional>
#include <qendian.h>
/**
* Reads a PIC file header from a data stream.
@ -34,7 +34,7 @@
*
* @relates PicHeader
*/
static QDataStream &operator>> (QDataStream &s, PicHeader &header)
static QDataStream &operator>>(QDataStream &s, PicHeader &header)
{
s.setFloatingPointPrecision(QDataStream::SinglePrecision);
s >> header.magic;
@ -71,7 +71,7 @@ static QDataStream &operator>> (QDataStream &s, PicHeader &header)
*
* @relates PicHeader
*/
static QDataStream &operator<< (QDataStream &s, const PicHeader &header)
static QDataStream &operator<<(QDataStream &s, const PicHeader &header)
{
s.setFloatingPointPrecision(QDataStream::SinglePrecision);
s << header.magic;
@ -107,7 +107,7 @@ static QDataStream &operator<< (QDataStream &s, const PicHeader &header)
*
* @relates PicChannel
*/
static QDataStream &operator>> (QDataStream &s, QList<PicChannel> &channels)
static QDataStream &operator>>(QDataStream &s, QList<PicChannel> &channels)
{
const unsigned maxChannels = 8;
unsigned count = 0;
@ -142,7 +142,7 @@ static QDataStream &operator>> (QDataStream &s, QList<PicChannel> &channels)
*
* @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);
for (int i = 0; i < channels.size() - 1; ++i) {
@ -160,8 +160,8 @@ static QDataStream &operator<< (QDataStream &s, const QList<PicChannel> &channel
static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<PicChannel> &channels)
{
for(const PicChannel &channel : channels) {
auto readPixel = [&] (QDataStream &str) -> QRgb {
for (const PicChannel &channel : channels) {
auto readPixel = [&](QDataStream &str) -> QRgb {
quint8 red = 0;
if (channel.code & RED) {
str >> red;
@ -180,16 +180,14 @@ static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<P
}
return qRgba(red, green, blue, alpha);
};
auto updatePixel = [&] (QRgb oldPixel, QRgb newPixel) -> QRgb {
return qRgba(
qRed((channel.code & RED) ? newPixel : oldPixel),
qGreen((channel.code & GREEN) ? newPixel : oldPixel),
qBlue((channel.code & BLUE) ? newPixel : oldPixel),
qAlpha((channel.code & ALPHA) ? newPixel : oldPixel));
auto updatePixel = [&](QRgb oldPixel, QRgb newPixel) -> QRgb {
return qRgba(qRed((channel.code & RED) ? newPixel : oldPixel),
qGreen((channel.code & GREEN) ? newPixel : oldPixel),
qBlue((channel.code & BLUE) ? newPixel : oldPixel),
qAlpha((channel.code & ALPHA) ? newPixel : oldPixel));
};
if (channel.encoding == MixedRLE) {
bool success = decodeRLEData(RLEVariant::PIC, stream, row, width,
readPixel, updatePixel);
bool success = decodeRLEData(RLEVariant::PIC, stream, row, width, readPixel, updatePixel);
if (!success) {
qDebug() << "decodeRLEData failed";
return false;
@ -245,10 +243,10 @@ bool SoftimagePICHandler::read(QImage *image)
return false;
}
img.fill(qRgb(0,0,0));
img.fill(qRgb(0, 0, 0));
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)) {
qDebug() << "readRow failed";
m_state = Error;
@ -265,9 +263,7 @@ bool SoftimagePICHandler::read(QImage *image)
bool SoftimagePICHandler::write(const QImage &_image)
{
bool alpha = _image.hasAlphaChannel();
const QImage image = _image.convertToFormat(
alpha ? QImage::Format_ARGB32
: QImage::Format_RGB32);
const QImage image = _image.convertToFormat(alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
if (image.width() < 0 || image.height() < 0) {
qDebug() << "Image size invalid:" << image.width() << image.height();
@ -292,22 +288,17 @@ bool SoftimagePICHandler::write(const QImage &_image)
stream << channels;
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 */
auto rgbEqual = [] (QRgb p1, QRgb p2) -> bool {
return qRed(p1) == qRed(p2) &&
qGreen(p1) == qGreen(p2) &&
qBlue(p1) == qBlue(p2);
auto rgbEqual = [](QRgb p1, QRgb p2) -> bool {
return qRed(p1) == qRed(p2) && qGreen(p1) == qGreen(p2) && qBlue(p1) == qBlue(p2);
};
auto writeRgb = [] (QDataStream &str, QRgb pixel) -> void {
str << quint8(qRed(pixel))
<< quint8(qGreen(pixel))
<< quint8(qBlue(pixel));
auto writeRgb = [](QDataStream &str, QRgb pixel) -> void {
str << quint8(qRed(pixel)) << quint8(qGreen(pixel)) << quint8(qBlue(pixel));
};
if (m_compression) {
encodeRLEData(RLEVariant::PIC, stream, row, image.width(),
rgbEqual, writeRgb);
encodeRLEData(RLEVariant::PIC, stream, row, image.width(), rgbEqual, writeRgb);
} else {
for (int i = 0; i < image.width(); ++i) {
writeRgb(stream, row[i]);
@ -316,15 +307,14 @@ bool SoftimagePICHandler::write(const QImage &_image)
/* Write the alpha channel */
if (alpha) {
auto alphaEqual = [] (QRgb p1, QRgb p2) -> bool {
auto alphaEqual = [](QRgb p1, QRgb p2) -> bool {
return qAlpha(p1) == qAlpha(p2);
};
auto writeAlpha = [] (QDataStream &str, QRgb pixel) -> void {
auto writeAlpha = [](QDataStream &str, QRgb pixel) -> void {
str << quint8(qAlpha(pixel));
};
if (m_compression) {
encodeRLEData(RLEVariant::PIC, stream, row, image.width(),
alphaEqual, writeAlpha);
encodeRLEData(RLEVariant::PIC, stream, row, image.width(), alphaEqual, writeAlpha);
} else {
for (int i = 0; i < image.width(); ++i) {
writeAlpha(stream, row[i]);
@ -341,7 +331,7 @@ bool SoftimagePICHandler::canRead(QIODevice *device)
if (device->peek(data, 4) != 4) {
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()
@ -374,67 +364,62 @@ bool SoftimagePICHandler::readChannels()
void SoftimagePICHandler::setOption(ImageOption option, const QVariant &value)
{
switch (option) {
case CompressionRatio:
m_compression = value.toBool();
break;
case Description: {
m_description.clear();
const QStringList entries = value.toString().split(QStringLiteral("\n\n"));
for (const QString &entry : entries) {
if (entry.startsWith(QStringLiteral("Description: "))) {
m_description = entry.mid(13).simplified().toUtf8();
}
case CompressionRatio:
m_compression = value.toBool();
break;
case Description: {
m_description.clear();
const QStringList entries = value.toString().split(QStringLiteral("\n\n"));
for (const QString &entry : entries) {
if (entry.startsWith(QStringLiteral("Description: "))) {
m_description = entry.mid(13).simplified().toUtf8();
}
break;
}
default:
break;
break;
}
default:
break;
}
}
QVariant SoftimagePICHandler::option(ImageOption option) const
{
const_cast<SoftimagePICHandler*>(this)->readHeader();
const_cast<SoftimagePICHandler *>(this)->readHeader();
switch (option) {
case Size:
if (const_cast<SoftimagePICHandler*>(this)->readHeader()) {
return QSize(m_header.width, m_header.height);
} else {
return QVariant();
case Size:
if (const_cast<SoftimagePICHandler *>(this)->readHeader()) {
return QSize(m_header.width, m_header.height);
} else {
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;
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"));
}
return QString();
case ImageFormat:
if (const_cast<SoftimagePICHandler *>(this)->readChannels()) {
for (const PicChannel &channel : qAsConst(m_channels)) {
if (channel.code & ALPHA) {
return QImage::Format_ARGB32;
}
}
return QString();
case ImageFormat:
if (const_cast<SoftimagePICHandler*>(this)->readChannels()) {
for (const PicChannel &channel : qAsConst(m_channels)) {
if (channel.code & ALPHA) {
return QImage::Format_ARGB32;
}
}
return QImage::Format_RGB32;
}
return QVariant();
default:
return QVariant();
return QImage::Format_RGB32;
}
return QVariant();
default:
return QVariant();
}
}
bool SoftimagePICHandler::supportsOption(ImageOption option) const
{
return (option == CompressionRatio ||
option == Description ||
option == ImageFormat ||
option == Size);
return (option == CompressionRatio || option == Description || option == ImageFormat || option == Size);
}
QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const

View File

@ -8,8 +8,8 @@
#ifndef KIMG_PIC_P_H
#define KIMG_PIC_P_H
#include <QImageIOPlugin>
#include <QDataStream>
#include <QImageIOPlugin>
/**
* The magic number at the start of a SoftImage PIC file.
@ -25,7 +25,7 @@ enum PicFields {
NoPicture = 0, /**< No picture */
OddScanlines = 1, /**< Odd scanlines */
EvenScanlines = 2, /**< Even scanlines */
BothScanlines = 3 /**< Every scanline */
BothScanlines = 3, /**< Every scanline */
};
/**
@ -33,7 +33,7 @@ enum PicFields {
*/
enum PicChannelEncoding {
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 */
GREEN = 0x40, /**< Green channel */
BLUE = 0x20, /**< Blue channel */
ALPHA = 0x10 /**< Alpha channel */
ALPHA = 0x10, /**< Alpha channel */
};
/**
@ -70,9 +70,12 @@ struct PicHeader {
, height(_height)
, ratio(1.0f)
, fields(BothScanlines)
{}
{
}
/** Construct an invalid header. */
PicHeader() {}
PicHeader()
{
}
quint32 magic; /**< Should be PIC_MAGIC_NUMBER */
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.
*/
bool isValid() const {
return magic == PIC_MAGIC_NUMBER
&& id == "PICT";
bool isValid() const
{
return magic == PIC_MAGIC_NUMBER && id == "PICT";
}
/**
@ -123,7 +126,8 @@ struct PicChannel {
: size(_size)
, encoding(_encoding)
, code(_code)
{}
{
}
/**
* Constructs a default channel description for a SoftImage PIC file.
*
@ -135,7 +139,8 @@ struct PicChannel {
*/
PicChannel()
: size(8)
{}
{
}
};
class SoftimagePICHandler : public QImageIOHandler
@ -155,13 +160,14 @@ public:
Error,
Ready,
ReadHeader,
ReadChannels
ReadChannels,
};
SoftimagePICHandler()
: m_state(Ready)
, m_compression(true)
{}
{
}
bool readHeader();
bool readChannels();

View File

@ -29,9 +29,8 @@ typedef quint32 uint;
typedef quint16 ushort;
typedef quint8 uchar;
namespace // Private.
namespace // Private.
{
enum ColorMode {
CM_BITMAP = 0,
CM_GRAYSCALE = 1,
@ -40,7 +39,7 @@ enum ColorMode {
CM_CMYK = 4,
CM_MULTICHANNEL = 7,
CM_DUOTONE = 8,
CM_LABCOLOR = 9
CM_LABCOLOR = 9,
};
struct PSDHeader {
@ -54,7 +53,7 @@ struct PSDHeader {
ushort color_mode;
};
static QDataStream &operator>> (QDataStream &s, PSDHeader &header)
static QDataStream &operator>>(QDataStream &s, PSDHeader &header)
{
s >> header.signature;
s >> header.version;
@ -72,7 +71,7 @@ static QDataStream &operator>> (QDataStream &s, PSDHeader &header)
// Check that the header is a valid PSD.
static bool IsValid(const PSDHeader &header)
{
if (header.signature != 0x38425053) { // '8BPS'
if (header.signature != 0x38425053) { // '8BPS'
return false;
}
return true;
@ -87,7 +86,7 @@ static bool IsSupported(const PSDHeader &header)
if (header.channel_count > 16) {
return false;
}
if (header.depth != 8) {
if (header.depth != 8 && header.depth != 16) {
return false;
}
if (header.color_mode != CM_RGB) {
@ -104,24 +103,31 @@ static void skip_section(QDataStream &s)
s.skipRawData(section_length);
}
static quint8 readPixel(QDataStream &stream) {
quint8 pixel;
template<class Trait>
static Trait readPixel(QDataStream &stream)
{
Trait pixel;
stream >> 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));
}
static QRgb updateGreen(QRgb oldPixel, quint8 greenPixel) {
static QRgb updateGreen(QRgb oldPixel, quint8 greenPixel)
{
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));
}
static QRgb updateAlpha(QRgb oldPixel, quint8 alphaPixel) {
static QRgb updateAlpha(QRgb oldPixel, quint8 alphaPixel)
{
return qRgba(qRed(oldPixel), qGreen(oldPixel), qBlue(oldPixel), alphaPixel);
}
typedef QRgb(*channelUpdater)(QRgb,quint8);
typedef QRgb (*channelUpdater)(QRgb, quint8);
// Load the PSD image.
static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
@ -149,42 +155,53 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
quint32 channel_num = header.channel_count;
QImage::Format fmt = QImage::Format_RGB32;
QImage::Format fmt = header.depth == 8 ? QImage::Format_RGB32 : QImage::Format_RGBX64;
// Clear the image.
if (channel_num >= 4) {
// Enable alpha.
fmt = QImage::Format_ARGB32;
fmt = header.depth == 8 ? QImage::Format_ARGB32 : QImage::Format_RGBA64;
// Ignore the other channels.
channel_num = 4;
}
img = QImage(header.width, header.height, fmt);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
return false;
}
img.fill(qRgb(0,0,0));
img.fill(qRgb(0, 0, 0));
const quint32 pixel_count = header.height * header.width;
const quint32 channel_size = pixel_count * header.depth / 8;
// Verify this, as this is used to write into the memory of the QImage
if (pixel_count > img.sizeInBytes() / sizeof(QRgb)) {
if (pixel_count > img.sizeInBytes() / (header.depth == 8 ? sizeof(QRgb) : sizeof(QRgba64))) {
qWarning() << "Invalid pixel count!" << pixel_count << "bytes available:" << img.sizeInBytes();
return false;
}
QRgb *image_data = reinterpret_cast<QRgb*>(img.bits());
QRgb *image_data = reinterpret_cast<QRgb *>(img.bits());
if (!image_data) {
return false;
}
static const channelUpdater updaters[4] = {
updateRed,
updateGreen,
updateBlue,
updateAlpha
};
static const channelUpdater updaters[4] = {updateRed, updateGreen, updateBlue, updateAlpha};
typedef QRgba64 (*channelUpdater16)(QRgba64, quint16);
static const channelUpdater16 updaters64[4] = {[](QRgba64 oldPixel, quint16 redPixel) {
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 alphaPixel) {
return qRgba64((oldPixel & ~(0xFFFFull << 48)) | (quint64(alphaPixel) << 48));
}};
if (compression) {
// Skip row lengths.
@ -194,9 +211,14 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
}
for (unsigned short channel = 0; channel < channel_num; channel++) {
bool success = decodeRLEData(RLEVariant::PackBits, stream,
image_data, pixel_count,
&readPixel, updaters[channel]);
bool success = false;
if (header.depth == 8) {
success = decodeRLEData(RLEVariant::PackBits, stream, image_data, channel_size, &readPixel<quint8>, updaters[channel]);
} else if (header.depth == 16) {
QRgba64 *image_data = reinterpret_cast<QRgba64 *>(img.bits());
success = decodeRLEData(RLEVariant::PackBits16, stream, image_data, channel_size, &readPixel<quint8>, updaters64[channel]);
}
if (!success) {
qDebug() << "decodeRLEData on channel" << channel << "failed";
return false;
@ -204,8 +226,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
}
} else {
for (unsigned short channel = 0; channel < channel_num; channel++) {
for (unsigned i = 0; i < pixel_count; ++i) {
image_data[i] = updaters[channel](image_data[i], readPixel(stream));
if (header.depth == 8) {
for (unsigned i = 0; i < pixel_count; ++i) {
image_data[i] = updaters[channel](image_data[i], readPixel<quint8>(stream));
}
} else if (header.depth == 16) {
QRgba64 *image_data = reinterpret_cast<QRgba64 *>(img.bits());
for (unsigned i = 0; i < pixel_count; ++i) {
image_data[i] = updaters64[channel](image_data[i], readPixel<quint16>(stream));
}
}
// make sure we didn't try to read past the end of the stream
if (stream.status() != QDataStream::Ok) {
@ -243,19 +272,19 @@ bool PSDHandler::read(QImage *image)
// Check image file format.
if (s.atEnd() || !IsValid(header)) {
// qDebug() << "This PSD file is not valid.";
// qDebug() << "This PSD file is not valid.";
return false;
}
// Check if it's a supported format.
if (!IsSupported(header)) {
// qDebug() << "This PSD file is not supported.";
// qDebug() << "This PSD file is not supported.";
return false;
}
QImage img;
if (!LoadPSD(s, header, img)) {
// qDebug() << "Error loading PSD file.";
// qDebug() << "Error loading PSD file.";
return false;
}

View File

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

View File

@ -9,11 +9,11 @@
#include "ras_p.h"
#include <QImage>
#include <QDataStream>
#include <QDebug>
#include <QImage>
namespace // Private.
namespace // Private.
{
// 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
enum RASType {
RAS_TYPE_OLD = 0x0,
RAS_TYPE_STANDARD = 0x1,
RAS_TYPE_BYTE_ENCODED = 0x2,
RAS_TYPE_RGB_FORMAT = 0x3,
RAS_TYPE_TIFF_FORMAT = 0x4,
RAS_TYPE_IFF_FORMAT = 0x5,
RAS_TYPE_EXPERIMENTAL = 0xFFFF
RAS_TYPE_OLD = 0x0,
RAS_TYPE_STANDARD = 0x1,
RAS_TYPE_BYTE_ENCODED = 0x2,
RAS_TYPE_RGB_FORMAT = 0x3,
RAS_TYPE_TIFF_FORMAT = 0x4,
RAS_TYPE_IFF_FORMAT = 0x5,
RAS_TYPE_EXPERIMENTAL = 0xFFFF,
};
enum RASColorMapType {
RAS_COLOR_MAP_TYPE_NONE = 0x0,
RAS_COLOR_MAP_TYPE_RGB = 0x1,
RAS_COLOR_MAP_TYPE_RAW = 0x2
RAS_COLOR_MAP_TYPE_NONE = 0x0,
RAS_COLOR_MAP_TYPE_RGB = 0x1,
RAS_COLOR_MAP_TYPE_RAW = 0x2,
};
struct RasHeader {
@ -46,10 +46,12 @@ struct RasHeader {
quint32 Type;
quint32 ColorMapType;
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.Width;
@ -79,8 +81,7 @@ static bool IsSupported(const RasHeader &head)
// check for an appropriate depth
// we support 8bit+palette, 24bit and 32bit ONLY!
// TODO: add support for 1bit
if (!((head.Depth == 8 && head.ColorMapType == 1)
|| head.Depth == 24 || head.Depth == 32)) {
if (!((head.Depth == 8 && head.ColorMapType == 1) || head.Depth == 24 || head.Depth == 32)) {
return false;
}
// 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);
int i = 0;
while (! s.atEnd() && i < input.size()) {
while (!s.atEnd() && i < input.size()) {
s >> input[i];
// I guess we need to find out if we're at the end of a line
if (paddingrequired && i != 0 && !(i % (ras.Width * bpp))) {
@ -168,7 +169,6 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
img.setPixel(x, y, qRgb(red, green, blue));
}
}
}
if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
@ -282,13 +282,13 @@ bool RASHandler::read(QImage *outImage)
// Check image file format. Type 2 is RLE, which causing seeking to be silly.
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;
}
// Check supported file types.
if (!IsSupported(ras)) {
// qDebug() << "This RAS file is not supported.";
// qDebug() << "This RAS file is not supported.";
return false;
}
@ -296,7 +296,7 @@ bool RASHandler::read(QImage *outImage)
bool result = LoadRAS(s, ras, img);
if (result == false) {
// qDebug() << "Error loading RAS file.";
// qDebug() << "Error loading RAS file.";
return false;
}
@ -306,7 +306,6 @@ bool RASHandler::read(QImage *outImage)
QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "ras") {
return Capabilities(CanRead);
}

View File

@ -24,14 +24,17 @@
#include <QMap>
#include <QVector>
#include <QImage>
#include <QDebug>
#include <QImage>
class RLEData : public QVector<uchar>
{
public:
RLEData() {}
RLEData(const uchar *d, uint l, uint o) : _offset(o)
RLEData()
{
}
RLEData(const uchar *d, uint l, uint o)
: _offset(o)
{
for (uint i = 0; i < l; i++) {
append(d[i]);
@ -51,7 +54,11 @@ private:
class RLEMap : public QMap<RLEData, uint>
{
public:
RLEMap() : _counter(0), _offset(0) {}
RLEMap()
: _counter(0)
, _offset(0)
{
}
uint insert(const uchar *d, uint l);
QVector<const RLEData *> vector();
void setBaseOffset(uint o)
@ -74,7 +81,12 @@ public:
bool writeImage(const QImage &);
private:
enum { NORMAL, DITHERED, SCREEN, COLORMAP }; // colormap
enum {
NORMAL,
DITHERED,
SCREEN,
COLORMAP,
}; // colormap
QIODevice *_dev;
QDataStream _stream;
@ -108,9 +120,9 @@ private:
uchar intensity(uchar);
};
SGIImage::SGIImage(QIODevice *io) :
_starttab(nullptr),
_lengthtab(nullptr)
SGIImage::SGIImage(QIODevice *io)
: _starttab(nullptr)
, _lengthtab(nullptr)
{
_dev = io;
_stream.setDevice(_dev);
@ -249,7 +261,7 @@ bool SGIImage::readImage(QImage &img)
qint16 u16;
qint32 u32;
// qDebug() << "reading rgb ";
// qDebug() << "reading rgb ";
// magic
_stream >> u16;
@ -259,42 +271,42 @@ bool SGIImage::readImage(QImage &img)
// verbatim/rle
_stream >> _rle;
// qDebug() << (_rle ? "RLE" : "verbatim");
// qDebug() << (_rle ? "RLE" : "verbatim");
if (_rle > 1) {
return false;
}
// bytes per channel
_stream >> _bpc;
// qDebug() << "bytes per channel: " << int(_bpc);
// qDebug() << "bytes per channel: " << int(_bpc);
if (_bpc == 1)
;
else if (_bpc == 2) {
// qDebug() << "dropping least significant byte";
// qDebug() << "dropping least significant byte";
} else {
return false;
}
// number of dimensions
_stream >> _dim;
// qDebug() << "dimensions: " << _dim;
// qDebug() << "dimensions: " << _dim;
if (_dim < 1 || _dim > 3) {
return false;
}
_stream >> _xsize >> _ysize >> _zsize >> _pixmin >> _pixmax >> u32;
// qDebug() << "x: " << _xsize;
// qDebug() << "y: " << _ysize;
// qDebug() << "z: " << _zsize;
// qDebug() << "x: " << _xsize;
// qDebug() << "y: " << _ysize;
// qDebug() << "z: " << _zsize;
// name
_stream.readRawData(_imagename, 80);
_imagename[79] = '\0';
_stream >> _colormap;
// qDebug() << "colormap: " << _colormap;
// qDebug() << "colormap: " << _colormap;
if (_colormap != NORMAL) {
return false; // only NORMAL supported
return false; // only NORMAL supported
}
for (int i = 0; i < 404; i++) {
@ -302,7 +314,7 @@ bool SGIImage::readImage(QImage &img)
}
if (_dim == 1) {
// qDebug() << "1-dimensional images aren't supported yet";
// qDebug() << "1-dimensional images aren't supported yet";
return false;
}
@ -316,13 +328,13 @@ bool SGIImage::readImage(QImage &img)
return false;
}
if (_zsize == 0 )
if (_zsize == 0)
return false;
if (_zsize == 2 || _zsize == 4) {
img = img.convertToFormat(QImage::Format_ARGB32);
} 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
// this is most likely a broken file anyway
if (_ysize > std::numeric_limits<int>::max() / _zsize)
@ -355,12 +367,12 @@ bool SGIImage::readImage(QImage &img)
for (uint o = 0; o < _numrows; o++)
// don't change to greater-or-equal!
if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
// qDebug() << "image corrupt (sanity check failed)";
// qDebug() << "image corrupt (sanity check failed)";
return false;
}
if (!readData(img)) {
// qDebug() << "image corrupt (incomplete scanline)";
// qDebug() << "image corrupt (incomplete scanline)";
return false;
}
@ -567,7 +579,7 @@ void SGIImage::writeHeader()
void SGIImage::writeRle()
{
_rle = 1;
// qDebug() << "writing RLE data";
// qDebug() << "writing RLE data";
writeHeader();
uint i;
@ -590,7 +602,7 @@ void SGIImage::writeRle()
void SGIImage::writeVerbatim(const QImage &img)
{
_rle = 0;
// qDebug() << "writing verbatim data";
// qDebug() << "writing verbatim data";
writeHeader();
const QRgb *c;
@ -637,7 +649,7 @@ void SGIImage::writeVerbatim(const QImage &img)
bool SGIImage::writeImage(const QImage &image)
{
// qDebug() << "writing "; // TODO add filename
// qDebug() << "writing "; // TODO add filename
QImage img = image;
if (img.allGray()) {
_dim = 2, _zsize = 1;
@ -651,7 +663,7 @@ bool SGIImage::writeImage(const QImage &image)
img = img.convertToFormat(QImage::Format_RGB32);
if (img.isNull()) {
// qDebug() << "can't convert image to depth 32";
// qDebug() << "can't convert image to depth 32";
return false;
}
@ -666,7 +678,7 @@ bool SGIImage::writeImage(const QImage &image)
_rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32));
if (!scanData(img)) {
// qDebug() << "this can't happen";
// qDebug() << "this can't happen";
return false;
}
@ -678,11 +690,11 @@ bool SGIImage::writeImage(const QImage &image)
rle_size += _rlevector[i]->size();
}
// qDebug() << "minimum intensity: " << _pixmin;
// qDebug() << "maximum intensity: " << _pixmax;
// qDebug() << "saved scanlines: " << _numrows - _rlemap.size();
// qDebug() << "total savings: " << (verbatim_size - rle_size) << " bytes";
// qDebug() << "compression: " << (rle_size * 100.0 / verbatim_size) << '%';
// qDebug() << "minimum intensity: " << _pixmin;
// qDebug() << "maximum intensity: " << _pixmax;
// qDebug() << "saved scanlines: " << _numrows - _rlemap.size();
// qDebug() << "total savings: " << (verbatim_size - rle_size) << " bytes";
// qDebug() << "compression: " << (rle_size * 100.0 / verbatim_size) << '%';
if (verbatim_size <= rle_size) {
writeVerbatim(img);
@ -746,8 +758,7 @@ bool RGBHandler::canRead(QIODevice *device)
QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "rgb" || format == "rgba" ||
format == "bw" || format == "sgi") {
if (format == "rgb" || format == "rgba" || format == "bw" || format == "sgi") {
return Capabilities(CanRead | CanWrite);
}
if (!format.isEmpty()) {

View File

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

View File

@ -8,8 +8,8 @@
#ifndef KIMAGEFORMATS_RLE_P_H
#define KIMAGEFORMATS_RLE_P_H
#include <QDebug>
#include <QDataStream>
#include <QDebug>
/**
* The RLEVariant to use.
@ -24,6 +24,11 @@ enum class RLEVariant {
* of size 2, 130 of size 3, up to 255 of size 128.
*/
PackBits,
/**
* Same as PackBits, but treat unpacked data as
* 16-bit integers.
*/
PackBits16,
/**
* PIC-style RLE
*
@ -32,7 +37,7 @@ enum class RLEVariant {
* of size 128, 130 of size 127, down to 255 of
* size 2.
*/
PIC
PIC,
};
/**
@ -59,14 +64,11 @@ enum class RLEVariant {
* into @p buf, @c false otherwise.
*/
template<typename Item, typename Func1, typename Func2>
static inline bool decodeRLEData(RLEVariant variant,
QDataStream &stream,
Item *dest,
quint32 length,
Func1 readData,
Func2 updateItem)
static inline bool decodeRLEData(RLEVariant variant, QDataStream &stream, Item *dest, quint32 length, Func1 readData, Func2 updateItem)
{
unsigned offset = 0; // in dest
bool is_msb = true; // only used for 16-bit PackBits, data is big-endian
quint16 temp_data = 0;
while (offset < length) {
unsigned remaining = length - offset;
quint8 count1;
@ -85,7 +87,7 @@ static inline bool decodeRLEData(RLEVariant variant,
// 2 to 128 repetitions
length = count1 - 127u;
}
} else if (variant == RLEVariant::PackBits) {
} else if (variant == RLEVariant::PackBits || variant == RLEVariant::PackBits16) {
if (count1 == 128u) {
// Ignore value 128
continue;
@ -102,7 +104,18 @@ static inline bool decodeRLEData(RLEVariant variant,
}
auto datum = readData(stream);
for (unsigned i = offset; i < offset + length; ++i) {
dest[i] = updateItem(dest[i], datum);
if (variant == RLEVariant::PackBits16) {
if (is_msb) {
temp_data = datum << 8;
is_msb = false;
} else {
temp_data |= datum;
dest[i >> 1] = updateItem(dest[i >> 1], temp_data);
is_msb = true;
}
} else {
dest[i] = updateItem(dest[i], datum);
}
}
offset += length;
} else {
@ -114,7 +127,18 @@ static inline bool decodeRLEData(RLEVariant variant,
}
for (unsigned i = offset; i < offset + length; ++i) {
auto datum = readData(stream);
dest[i] = updateItem(dest[i], datum);
if (variant == RLEVariant::PackBits16) {
if (is_msb) {
temp_data = datum << 8;
is_msb = false;
} else {
temp_data |= datum;
dest[i >> 1] = updateItem(dest[i >> 1], temp_data);
is_msb = true;
}
} else {
dest[i] = updateItem(dest[i], datum);
}
}
offset += length;
}
@ -125,7 +149,6 @@ static inline bool decodeRLEData(RLEVariant variant,
return stream.status() == QDataStream::Ok;
}
/**
* Encodes data in run-length encoding format.
*
@ -141,18 +164,10 @@ static inline bool decodeRLEData(RLEVariant variant,
* and writes the item to the data stream.
*/
template<typename Item, typename Func1, typename Func2>
static inline void encodeRLEData(RLEVariant variant,
QDataStream &stream,
const Item *data,
unsigned length,
Func1 itemsEqual,
Func2 writeItem)
static inline void encodeRLEData(RLEVariant variant, QDataStream &stream, const Item *data, unsigned length, Func1 itemsEqual, Func2 writeItem)
{
unsigned offset = 0;
const unsigned maxEncodableChunk =
(variant == RLEVariant::PIC)
? 65535u
: 128;
const unsigned maxEncodableChunk = (variant == RLEVariant::PIC) ? 65535u : 128;
while (offset < length) {
const Item *chunkStart = data + offset;
unsigned maxChunk = qMin(length - offset, maxEncodableChunk);
@ -191,10 +206,7 @@ static inline void encodeRLEData(RLEVariant variant,
}
chunkLength = 1;
chunkEnd = chunkStart + 1;
while (chunkLength < maxChunk &&
(chunkLength + 1u == maxChunk ||
!itemsEqual(*chunkEnd, *(chunkEnd+1))))
{
while (chunkLength < maxChunk && (chunkLength + 1u == maxChunk || !itemsEqual(*chunkEnd, *(chunkEnd + 1)))) {
++chunkEnd;
++chunkLength;
}

View File

@ -20,27 +20,26 @@
#include <assert.h>
#include <QImage>
#include <QDataStream>
#include <QDebug>
#include <QImage>
typedef quint32 uint;
typedef quint16 ushort;
typedef quint8 uchar;
namespace // Private.
namespace // Private.
{
// 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 {
TGA_TYPE_INDEXED = 1,
TGA_TYPE_RGB = 2,
TGA_TYPE_GREY = 3,
TGA_TYPE_RLE_INDEXED = 9,
TGA_TYPE_RLE_RGB = 10,
TGA_TYPE_RLE_GREY = 11
TGA_TYPE_INDEXED = 1,
TGA_TYPE_RGB = 2,
TGA_TYPE_GREY = 3,
TGA_TYPE_RLE_INDEXED = 9,
TGA_TYPE_RLE_RGB = 10,
TGA_TYPE_RLE_GREY = 11,
};
#define TGA_INTERLEAVE_MASK 0xc0
@ -48,11 +47,11 @@ enum TGAType {
#define TGA_INTERLEAVE_2WAY 0x40
#define TGA_INTERLEAVE_4WAY 0x80
#define TGA_ORIGIN_MASK 0x30
#define TGA_ORIGIN_LEFT 0x00
#define TGA_ORIGIN_RIGHT 0x10
#define TGA_ORIGIN_LOWER 0x00
#define TGA_ORIGIN_UPPER 0x20
#define TGA_ORIGIN_MASK 0x30
#define TGA_ORIGIN_LEFT 0x00
#define TGA_ORIGIN_RIGHT 0x10
#define TGA_ORIGIN_LOWER 0x00
#define TGA_ORIGIN_UPPER 0x20
/** Tga Header. */
struct TgaHeader {
@ -69,10 +68,12 @@ struct TgaHeader {
uchar pixel_size;
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.colormap_type;
@ -88,30 +89,23 @@ static QDataStream &operator>> (QDataStream &s, TgaHeader &head)
s >> head.flags;
/*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() << "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;
}
static bool IsSupported(const TgaHeader &head)
{
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_RLE_RGB &&
head.image_type != TGA_TYPE_RLE_GREY) {
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_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) {
return false;
}
if (head.image_type == TGA_TYPE_INDEXED ||
head.image_type == TGA_TYPE_RLE_INDEXED) {
if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) {
if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) {
return false;
}
}
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) {
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) {
if (head.colormap_type != 0) {
return false;
}
@ -119,8 +113,7 @@ static bool IsSupported(const TgaHeader &head)
if (head.width == 0 || head.height == 0) {
return false;
}
if (head.pixel_size != 8 && head.pixel_size != 16 &&
head.pixel_size != 24 && head.pixel_size != 32) {
if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
return false;
}
return true;
@ -138,12 +131,16 @@ struct TgaHeaderInfo {
bool rgb;
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) {
case TGA_TYPE_RLE_INDEXED:
rle = true;
Q_FALLTHROUGH();
Q_FALLTHROUGH();
// no break is intended!
case TGA_TYPE_INDEXED:
pal = true;
@ -151,7 +148,7 @@ struct TgaHeaderInfo {
case TGA_TYPE_RLE_RGB:
rle = true;
Q_FALLTHROUGH();
Q_FALLTHROUGH();
// no break is intended!
case TGA_TYPE_RGB:
rgb = true;
@ -159,7 +156,7 @@ struct TgaHeaderInfo {
case TGA_TYPE_RLE_GREY:
rle = true;
Q_FALLTHROUGH();
Q_FALLTHROUGH();
// no break is intended!
case TGA_TYPE_GREY:
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;
if (size < 1) {
// qDebug() << "This TGA file is broken with size " << size;
// qDebug() << "This TGA file is broken with size " << size;
return false;
}
@ -225,7 +222,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
}
// Allocate image.
uchar *const image = reinterpret_cast<uchar*>(malloc(size));
uchar *const image = reinterpret_cast<uchar *>(malloc(size));
if (!image) {
return false;
}
@ -282,11 +279,11 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
return false;
}
if ((uint)dataRead < count) {
const size_t toCopy = count - dataRead;
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;
break;
}
@ -328,7 +325,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
uchar *src = image;
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) {
// Paletted.
@ -389,7 +386,7 @@ bool TGAHandler::canRead() const
bool TGAHandler::read(QImage *outImage)
{
//qDebug() << "Loading TGA file!";
// qDebug() << "Loading TGA file!";
QDataStream s(device());
s.setByteOrder(QDataStream::LittleEndian);
@ -401,13 +398,13 @@ bool TGAHandler::read(QImage *outImage)
// Check image file format.
if (s.atEnd()) {
// qDebug() << "This TGA file is not valid.";
// qDebug() << "This TGA file is not valid.";
return false;
}
// Check supported file types.
if (!IsSupported(tga)) {
// qDebug() << "This TGA file is not supported.";
// qDebug() << "This TGA file is not supported.";
return false;
}
@ -415,7 +412,7 @@ bool TGAHandler::read(QImage *outImage)
bool result = LoadTGA(s, tga, img);
if (result == false) {
// qDebug() << "Error loading TGA file.";
// qDebug() << "Error loading TGA file.";
return false;
}
@ -435,10 +432,10 @@ bool TGAHandler::write(const QImage &image)
}
// write header
s << quint16(img.width()); // width
s << quint16(img.height()); // height
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 << quint16(img.width()); // width
s << quint16(img.height()); // height
s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4)
for (int y = 0; y < img.height(); y++)
for (int x = 0; x < img.width(); x++) {

File diff suppressed because it is too large Load Diff

View File

@ -8,55 +8,23 @@
#define FORMAT_ENUM_H
#include <QImage>
// Generated from QImage::Format enum
static const char * qimage_format_enum_names[] = {
"Invalid",
"Mono",
"MonoLSB",
"Indexed8",
"RGB32",
"ARGB32",
"ARGB32_Premultiplied",
"RGB16",
"ARGB8565_Premultiplied",
"RGB666",
"ARGB6666_Premultiplied",
"RGB555",
"ARGB8555_Premultiplied",
"RGB888",
"RGB444",
"ARGB4444_Premultiplied",
"RGBX8888",
"RGBA8888",
"RGBA8888_Premultiplied"
};
// Never claim there are more than QImage::NImageFormats supported formats.
// This is future-proofing against the above list being extended.
static const int qimage_format_enum_names_count =
(sizeof(qimage_format_enum_names) / sizeof(*qimage_format_enum_names) > int(QImage::NImageFormats))
? int(QImage::NImageFormats)
: (sizeof(qimage_format_enum_names) / sizeof(*qimage_format_enum_names));
#include <QMetaEnum>
QImage::Format formatFromString(const QString &str)
{
for (int i = 0; i < qimage_format_enum_names_count; ++i) {
if (str.compare(QLatin1String(qimage_format_enum_names[i]), Qt::CaseInsensitive) == 0) {
return (QImage::Format)(i);
}
}
return QImage::Format_Invalid;
const QMetaEnum metaEnum = QMetaEnum::fromType<QImage::Format>();
const QString enumString = QStringLiteral("Format_") + str;
bool ok;
const int res = metaEnum.keyToValue(enumString.toLatin1().constData(), &ok);
return ok ? static_cast<QImage::Format>(res) : QImage::Format_Invalid;
}
QString formatToString(QImage::Format format)
{
int index = int(format);
if (index > 0 && index < qimage_format_enum_names_count) {
return QLatin1String(qimage_format_enum_names[index]);
}
return QLatin1String("<unknown:") +
QString::number(index) +
QLatin1String(">");
const QMetaEnum metaEnum = QMetaEnum::fromType<QImage::Format>();
return QString::fromLatin1(metaEnum.valueToKey(format)).remove(QStringLiteral("Format_"));
}
#endif

View File

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

View File

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