├── qtbuild_6.5.3 ├── .qmake.conf └── qt-heic-image-plugin_win64.pro ├── qtbuild_6.8.3 ├── .qmake.conf └── qt-heic-image-plugin_win64.pro ├── qtbuild_6.9.3 ├── .qmake.conf └── qt-heic-image-plugin_win64.pro ├── .qmake.conf ├── src ├── heif.json ├── heif.desktop ├── CMakeLists.txt ├── util_p.h ├── heif_p.h ├── .clang-format └── heif.cpp ├── metainfo.yaml ├── README.md ├── qt-heic-image-plugin6.pro ├── qt-heic-image-plugin.pro ├── CMakeLists.txt ├── appveyor.yml ├── 3rdparty └── CMakeLists.txt └── LICENSE /qtbuild_6.5.3/.qmake.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qtbuild_6.8.3/.qmake.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qtbuild_6.9.3/.qmake.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.qmake.conf: -------------------------------------------------------------------------------- 1 | load(qt_build_config) 2 | 3 | -------------------------------------------------------------------------------- /src/heif.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys": [ "heif", "heic", "hej2", "avci" ], 3 | "MimeTypes": [ "image/heif", "image/heif", "image/hej2k", "image/avci" ] 4 | } 5 | -------------------------------------------------------------------------------- /src/heif.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Service 3 | X-KDE-ServiceTypes=QImageIOPlugins 4 | X-KDE-ImageFormat=heif 5 | X-KDE-MimeType=image/heif 6 | X-KDE-Read=true 7 | X-KDE-Write=true 8 | -------------------------------------------------------------------------------- /metainfo.yaml: -------------------------------------------------------------------------------- 1 | description: Qt plug-in to allow Qt and KDE based applications to read/write AVIF images. 2 | platforms: 3 | - name: Linux 4 | - name: FreeBSD 5 | - name: macOS 6 | - name: Windows 7 | - name: Android 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qt-heic-image-plugin 2 | Qt plug-in to allow Qt and KDE based applications to read/write HEIF/HEIC images. 3 | 4 | Requirements: 5 | * Qt 5.14 6 | * [libheif](https://github.com/strukturag/libheif) 1.10.0 with 7 | * libde265 decoder 8 | * x265 encoder (built with 8bit and 10bit support) 9 | 10 | Code for download and compilation: 11 | ``` 12 | git clone --depth 1 https://github.com/novomesk/qt-heic-image-plugin.git 13 | cd qt-heic-image-plugin 14 | mkdir build 15 | cd build 16 | cmake -DCMAKE_BUILD_TYPE=Release .. 17 | make 18 | ``` 19 | Code for installation (run as root): 20 | 21 | `make install` 22 | -------------------------------------------------------------------------------- /qt-heic-image-plugin6.pro: -------------------------------------------------------------------------------- 1 | TARGET = kimg_heif6 2 | 3 | HEADERS = src/heif_p.h src/util_p.h 4 | SOURCES = src/heif.cpp 5 | OTHER_FILES = src/heif.json 6 | 7 | LIBS += -lheif 8 | 9 | TEMPLATE = lib 10 | 11 | CONFIG += release skip_target_version_ext c++17 warn_on plugin 12 | CONFIG -= separate_debug_info debug debug_and_release force_debug_info 13 | 14 | QMAKE_TARGET_COMPANY = "Daniel Novomesky" 15 | QMAKE_TARGET_PRODUCT = "qt-heic-image-plugin" 16 | QMAKE_TARGET_DESCRIPTION = "Qt plug-in to allow Qt and KDE based applications to read/write HEIF/HEIC images." 17 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2020-2025 Daniel Novomesky" 18 | -------------------------------------------------------------------------------- /qt-heic-image-plugin.pro: -------------------------------------------------------------------------------- 1 | TARGET = kimg_heif 2 | 3 | HEADERS = src/heif_p.h src/util_p.h 4 | SOURCES = src/heif.cpp 5 | OTHER_FILES = src/heif.json 6 | 7 | LIBS += -lheif 8 | 9 | PLUGIN_TYPE = imageformats 10 | PLUGIN_CLASS_NAME = HEIFPlugin 11 | load(qt_plugin) 12 | 13 | CONFIG += release skip_target_version_ext c++17 warn_on 14 | CONFIG -= separate_debug_info debug debug_and_release force_debug_info 15 | 16 | QMAKE_TARGET_COMPANY = "Daniel Novomesky" 17 | QMAKE_TARGET_PRODUCT = "qt-heic-image-plugin" 18 | QMAKE_TARGET_DESCRIPTION = "Qt plug-in to allow Qt and KDE based applications to read/write HEIF/HEIC images." 19 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2020-2025 Daniel Novomesky" 20 | -------------------------------------------------------------------------------- /qtbuild_6.5.3/qt-heic-image-plugin_win64.pro: -------------------------------------------------------------------------------- 1 | TARGET = kimg_heif6 2 | 3 | INCLUDEPATH += ../3rdparty/install/include 4 | 5 | HEADERS = ../src/heif_p.h ../src/util_p.h 6 | SOURCES = ../src/heif.cpp 7 | OTHER_FILES = ../src/heif.json 8 | 9 | LIBS += ../3rdparty/install/lib/heif.lib 10 | 11 | TEMPLATE = lib 12 | 13 | CONFIG += release skip_target_version_ext c++17 warn_on plugin 14 | CONFIG -= separate_debug_info debug debug_and_release force_debug_info 15 | 16 | win32:VERSION = 0.6.2 17 | QMAKE_TARGET_COMPANY = "Daniel Novomesky" 18 | QMAKE_TARGET_PRODUCT = "qt-heic-image-plugin" 19 | QMAKE_TARGET_DESCRIPTION = "Qt plug-in to allow Qt and KDE based applications to read HEIF/HEIC/HEJ2 images." 20 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2020-2025 Daniel Novomesky" 21 | QMAKE_TARGET_COMMENTS = "Build using Qt 6.5.3" 22 | -------------------------------------------------------------------------------- /qtbuild_6.8.3/qt-heic-image-plugin_win64.pro: -------------------------------------------------------------------------------- 1 | TARGET = kimg_heif6 2 | 3 | INCLUDEPATH += ../3rdparty/install/include 4 | 5 | HEADERS = ../src/heif_p.h ../src/util_p.h 6 | SOURCES = ../src/heif.cpp 7 | OTHER_FILES = ../src/heif.json 8 | 9 | LIBS += ../3rdparty/install/lib/heif.lib 10 | 11 | TEMPLATE = lib 12 | 13 | CONFIG += release skip_target_version_ext c++17 warn_on plugin 14 | CONFIG -= separate_debug_info debug debug_and_release force_debug_info 15 | 16 | win32:VERSION = 0.6.2 17 | QMAKE_TARGET_COMPANY = "Daniel Novomesky" 18 | QMAKE_TARGET_PRODUCT = "qt-heic-image-plugin" 19 | QMAKE_TARGET_DESCRIPTION = "Qt plug-in to allow Qt and KDE based applications to read HEIF/HEIC/HEJ2 images." 20 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2020-2025 Daniel Novomesky" 21 | QMAKE_TARGET_COMMENTS = "Build using Qt 6.8.3" 22 | -------------------------------------------------------------------------------- /qtbuild_6.9.3/qt-heic-image-plugin_win64.pro: -------------------------------------------------------------------------------- 1 | TARGET = kimg_heif6 2 | 3 | INCLUDEPATH += ../3rdparty/install/include 4 | 5 | HEADERS = ../src/heif_p.h ../src/util_p.h 6 | SOURCES = ../src/heif.cpp 7 | OTHER_FILES = ../src/heif.json 8 | 9 | LIBS += ../3rdparty/install/lib/heif.lib 10 | 11 | TEMPLATE = lib 12 | 13 | CONFIG += release skip_target_version_ext c++17 warn_on plugin 14 | CONFIG -= separate_debug_info debug debug_and_release force_debug_info 15 | 16 | win32:VERSION = 0.6.2 17 | QMAKE_TARGET_COMPANY = "Daniel Novomesky" 18 | QMAKE_TARGET_PRODUCT = "qt-heic-image-plugin" 19 | QMAKE_TARGET_DESCRIPTION = "Qt plug-in to allow Qt and KDE based applications to read HEIF/HEIC/HEJ2 images." 20 | QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2020-2025 Daniel Novomesky" 21 | QMAKE_TARGET_COMMENTS = "Build using Qt 6.9.3" 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | project(qt-heic-image-plugin) 4 | 5 | set (CMAKE_CXX_STANDARD 17) 6 | 7 | include(FeatureSummary) 8 | find_package(ECM 5.89.0 NO_MODULE) 9 | set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") 10 | feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) 11 | 12 | 13 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 14 | 15 | include(KDEInstallDirs) 16 | include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) 17 | include(KDECMakeSettings) 18 | 19 | 20 | include(CheckIncludeFiles) 21 | 22 | set(REQUIRED_QT_VERSION 5.14.0) 23 | find_package(Qt${QT_MAJOR_VERSION}Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) 24 | 25 | include(FindPkgConfig) 26 | 27 | pkg_check_modules(LibHeif REQUIRED IMPORTED_TARGET libheif>=1.10.0) 28 | 29 | add_definitions(-DQT_NO_FOREACH) 30 | add_subdirectory(src) 31 | 32 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 33 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # NB: the desktop files are installed for the benefit of KImageIO in KDELibs4Support. 2 | 3 | ################################## 4 | 5 | function(kimageformats_add_plugin plugin) 6 | set(options) 7 | set(oneValueArgs) 8 | set(multiValueArgs SOURCES) 9 | cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 10 | if(NOT KIF_ADD_PLUGIN_SOURCES) 11 | message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter") 12 | endif() 13 | 14 | add_library(${plugin} MODULE ${KIF_ADD_PLUGIN_SOURCES}) 15 | set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats") 16 | target_link_libraries(${plugin} Qt${QT_MAJOR_VERSION}::Gui) 17 | install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats) 18 | endfunction() 19 | 20 | ################################## 21 | 22 | if (LibHeif_FOUND) 23 | kimageformats_add_plugin("kimg_heif${QT_MAJOR_VERSION}" SOURCES "heif.cpp") 24 | target_link_libraries("kimg_heif${QT_MAJOR_VERSION}" PkgConfig::LibHeif) 25 | 26 | if (QT_MAJOR_VERSION STREQUAL "5") 27 | install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) 28 | endif() 29 | endif() 30 | 31 | ################################## 32 | -------------------------------------------------------------------------------- /src/util_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2022 Albert Astals Cid 3 | SPDX-FileCopyrightText: 2022 Mirco Miranda 4 | 5 | SPDX-License-Identifier: LGPL-2.0-or-later 6 | */ 7 | 8 | #ifndef UTIL_P_H 9 | #define UTIL_P_H 10 | 11 | #include 12 | 13 | #include 14 | 15 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 16 | #include 17 | #endif 18 | 19 | // QVector uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira 20 | static constexpr int kMaxQVectorSize = std::numeric_limits::max() - 32; 21 | 22 | // On Qt 6 to make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit() 23 | // it is necessary to allocate the image with QImageIOHandler::allocateImage(). 24 | inline QImage imageAlloc(const QSize &size, const QImage::Format &format) 25 | { 26 | QImage img; 27 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 28 | img = QImage(size, format); 29 | #else 30 | if (!QImageIOHandler::allocateImage(size, format, &img)) { 31 | img = QImage(); // paranoia 32 | } 33 | #endif 34 | return img; 35 | } 36 | 37 | inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format) 38 | { 39 | return imageAlloc(QSize(width, height), format); 40 | } 41 | 42 | #endif // UTIL_P_H 43 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.6.2.{build} 2 | image: Visual Studio 2022 3 | environment: 4 | CC: cl.exe 5 | CXX: cl.exe 6 | install: 7 | - cmd: >- 8 | call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" 9 | 10 | appveyor DownloadFile "https://github.com/novomesk/downloads/raw/main/nasm-2.16.03-win64.zip" -FileName "nasm.zip" 11 | 12 | 7z x "nasm.zip" 13 | 14 | move nasm-* NASM 15 | 16 | set PATH=%PATH%;%CD%\NASM; 17 | 18 | nasm -v 19 | 20 | cd 3rdparty 21 | 22 | mkdir build 23 | 24 | mkdir download 25 | 26 | mkdir install 27 | 28 | git clone --depth 1 -b v2.6.0 https://github.com/cisco/openh264.git 29 | 30 | cd openh264 31 | 32 | meson setup builddir --prefix C:\projects\qt-heic-image-plugin\3rdparty\install --buildtype release -Dtests=disabled 33 | 34 | meson compile -C builddir 35 | 36 | meson install -C builddir 37 | 38 | cd ../build 39 | 40 | cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Release -DEXTERNALS_DOWNLOAD_DIR=C:/projects/qt-heic-image-plugin/3rdparty/download -DINSTALL_ROOT=C:/projects/qt-heic-image-plugin/3rdparty/install 41 | 42 | ninja 43 | 44 | cd ..\.. 45 | build_script: 46 | - cmd: >- 47 | cd qtbuild_6.5.3 48 | 49 | C:\Qt\6.5.3\msvc2019_64\bin\qmake.exe qt-heic-image-plugin_win64.pro 50 | 51 | nmake.exe 52 | 53 | cd ../qtbuild_6.8.3 54 | 55 | C:\Qt\6.8.3\msvc2022_64\bin\qmake.exe qt-heic-image-plugin_win64.pro 56 | 57 | nmake.exe 58 | 59 | cd ../qtbuild_6.9.3 60 | 61 | C:\Qt\6.9.3\msvc2022_64\bin\qmake.exe qt-heic-image-plugin_win64.pro 62 | 63 | nmake.exe 64 | 65 | artifacts: 66 | - path: qtbuild_6.5.3\*.dll 67 | - path: qtbuild_6.8.3\*.dll 68 | - path: qtbuild_6.9.3\*.dll 69 | - path: 3rdparty\install\bin\*.dll 70 | -------------------------------------------------------------------------------- /3rdparty/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project (libheif-dependency) 2 | 3 | #Example: 4 | #cmake .. -DEXTERNALS_DOWNLOAD_DIR=/dev2/d -DINSTALL_ROOT=/dev2/i 5 | 6 | cmake_minimum_required(VERSION 3.10) 7 | include(ExternalProject) 8 | 9 | if(NOT IS_DIRECTORY ${EXTERNALS_DOWNLOAD_DIR}) 10 | message(FATAL_ERROR "No externals download dir set. Use -DEXTERNALS_DOWNLOAD_DIR") 11 | endif() 12 | 13 | if(NOT IS_DIRECTORY ${INSTALL_ROOT}) 14 | message(FATAL_ERROR "No install dir set. Use -DINSTALL_ROOT") 15 | endif() 16 | 17 | set(TOP_INST_DIR ${INSTALL_ROOT}) 18 | set(EXTPREFIX "${TOP_INST_DIR}") 19 | set(CMAKE_PREFIX_PATH "${EXTPREFIX}") 20 | 21 | SET(EXTPREFIX_heif "${EXTPREFIX}" ) 22 | 23 | ExternalProject_Add( 24 | ext_openjpeg 25 | DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} 26 | 27 | URL https://github.com/uclouvain/openjpeg/archive/refs/tags/v2.5.4.tar.gz 28 | URL_HASH SHA256=a695fbe19c0165f295a8531b1e4e855cd94d0875d2f88ec4b61080677e27188a 29 | DOWNLOAD_EXTRACT_TIMESTAMP FALSE 30 | 31 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_heif} -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIBS=OFF -DBUILD_CODEC=OFF 32 | 33 | UPDATE_COMMAND "" 34 | ) 35 | 36 | ExternalProject_Add( 37 | ext_libde265 38 | GIT_REPOSITORY https://github.com/strukturag/libde265.git 39 | GIT_SHALLOW TRUE 40 | 41 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_heif} -DCMAKE_BUILD_TYPE=Release -DENABLE_DECODER=OFF -DENABLE_ENCODER=OFF -DENABLE_SDL=OFF 42 | 43 | UPDATE_COMMAND "" 44 | ) 45 | 46 | ExternalProject_Add( 47 | ext_libheif 48 | DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} 49 | 50 | URL https://github.com/strukturag/libheif/releases/download/v1.20.2/libheif-1.20.2.tar.gz 51 | URL_HASH SHA256=68ac9084243004e0ef3633f184eeae85d615fe7e4444373a0a21cebccae9d12a 52 | DOWNLOAD_EXTRACT_TIMESTAMP FALSE 53 | # GIT_REPOSITORY https://github.com/strukturag/libheif.git 54 | # GIT_SHALLOW TRUE 55 | 56 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_heif} -DCMAKE_BUILD_TYPE=Release -DENABLE_PLUGIN_LOADING=OFF -DWITH_AOM_DECODER=OFF -DWITH_AOM_ENCODER=OFF -DWITH_DAV1D=OFF -DWITH_LIBDE265=ON -DWITH_RAV1E=OFF -DWITH_X265=OFF -DWITH_SvtEnc=OFF -DWITH_OpenJPEG_DECODER=ON -DWITH_OpenJPEG_ENCODER=ON -DWITH_OpenH264_DECODER=ON -DWITH_EXAMPLES=OFF -DWITH_EXAMPLE_HEIF_VIEW=OFF -DWITH_GDK_PIXBUF=OFF -DWITH_LIBSHARPYUV=OFF 57 | 58 | UPDATE_COMMAND "" 59 | DEPENDS ext_openjpeg ext_libde265 60 | ) 61 | -------------------------------------------------------------------------------- /src/heif_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | High Efficiency Image File Format (HEIF) support for QImage. 3 | 4 | SPDX-FileCopyrightText: 2020 Sirius Bakke 5 | SPDX-FileCopyrightText: 2021 Daniel Novomesky 6 | 7 | SPDX-License-Identifier: LGPL-2.0-or-later 8 | */ 9 | 10 | #ifndef KIMG_HEIF_P_H 11 | #define KIMG_HEIF_P_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | class HEIFHandler : public QImageIOHandler 19 | { 20 | public: 21 | HEIFHandler(); 22 | 23 | bool canRead() const override; 24 | bool read(QImage *image) override; 25 | bool write(const QImage &image) override; 26 | 27 | QVariant option(ImageOption option) const override; 28 | void setOption(ImageOption option, const QVariant &value) override; 29 | bool supportsOption(ImageOption option) const override; 30 | 31 | static bool isHeifDecoderAvailable(); 32 | static bool isHeifEncoderAvailable(); 33 | static bool isHej2DecoderAvailable(); 34 | static bool isHej2EncoderAvailable(); 35 | static bool isAVCIDecoderAvailable(); 36 | 37 | static bool isSupportedBMFFType(const QByteArray &header); 38 | static bool isSupportedHEJ2(const QByteArray &header); 39 | static bool isSupportedAVCI(const QByteArray &header); 40 | 41 | private: 42 | bool ensureParsed() const; 43 | bool ensureDecoder(); 44 | 45 | enum ParseHeicState { 46 | ParseHeicError = -1, 47 | ParseHeicNotParsed = 0, 48 | ParseHeicSuccess = 1, 49 | }; 50 | 51 | ParseHeicState m_parseState; 52 | int m_quality; 53 | QImage m_current_image; 54 | 55 | bool write_helper(const QImage &image); 56 | 57 | static void startHeifLib(); 58 | static void finishHeifLib(); 59 | static void queryHeifLib(); 60 | static size_t m_initialized_count; 61 | 62 | static bool m_plugins_queried; 63 | static bool m_heif_decoder_available; 64 | static bool m_heif_encoder_available; 65 | static bool m_hej2_decoder_available; 66 | static bool m_hej2_encoder_available; 67 | static bool m_avci_decoder_available; 68 | 69 | static QMutex &getHEIFHandlerMutex(); 70 | }; 71 | 72 | class HEIFPlugin : public QImageIOPlugin 73 | { 74 | Q_OBJECT 75 | Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "heif.json") 76 | 77 | public: 78 | Capabilities capabilities(QIODevice *device, const QByteArray &format) const override; 79 | QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override; 80 | }; 81 | 82 | #endif // KIMG_HEIF_P_H 83 | -------------------------------------------------------------------------------- /src/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # SPDX-FileCopyrightText: 2019 Christoph Cullmann 3 | # SPDX-FileCopyrightText: 2019 Gernot Gebhard 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | # This file got automatically created by ECM, do not edit 8 | # See https://clang.llvm.org/docs/ClangFormatStyleOptions.html for the config options 9 | # and https://community.kde.org/Policies/Frameworks_Coding_Style#Clang-format_automatic_code_formatting 10 | # for clang-format tips & tricks 11 | --- 12 | Language: JavaScript 13 | DisableFormat: true 14 | --- 15 | 16 | # Style for C++ 17 | Language: Cpp 18 | 19 | # base is WebKit coding style: https://webkit.org/code-style-guidelines/ 20 | # below are only things set that diverge from this style! 21 | BasedOnStyle: WebKit 22 | 23 | # enforce C++11 (e.g. for std::vector> 24 | Standard: Cpp11 25 | 26 | # 4 spaces indent 27 | TabWidth: 4 28 | 29 | # 2 * 80 wide lines 30 | ColumnLimit: 160 31 | 32 | # sort includes inside line separated groups 33 | SortIncludes: true 34 | 35 | # break before braces on function, namespace and class definitions. 36 | BreakBeforeBraces: Linux 37 | 38 | # CrlInstruction *a; 39 | PointerAlignment: Right 40 | 41 | # horizontally aligns arguments after an open bracket. 42 | AlignAfterOpenBracket: Align 43 | 44 | # don't move all parameters to new line 45 | AllowAllParametersOfDeclarationOnNextLine: false 46 | 47 | # no single line functions 48 | AllowShortFunctionsOnASingleLine: None 49 | 50 | # no single line enums 51 | AllowShortEnumsOnASingleLine: false 52 | 53 | # always break before you encounter multi line strings 54 | AlwaysBreakBeforeMultilineStrings: true 55 | 56 | # don't move arguments to own lines if they are not all on the same 57 | BinPackArguments: false 58 | 59 | # don't move parameters to own lines if they are not all on the same 60 | BinPackParameters: false 61 | 62 | # In case we have an if statement with multiple lines the operator should be at the beginning of the line 63 | # but we do not want to break assignments 64 | BreakBeforeBinaryOperators: NonAssignment 65 | 66 | # format C++11 braced lists like function calls 67 | Cpp11BracedListStyle: true 68 | 69 | # do not put a space before C++11 braced lists 70 | SpaceBeforeCpp11BracedList: false 71 | 72 | # remove empty lines 73 | KeepEmptyLinesAtTheStartOfBlocks: false 74 | 75 | # no namespace indentation to keep indent level low 76 | NamespaceIndentation: None 77 | 78 | # we use template< without space. 79 | SpaceAfterTemplateKeyword: false 80 | 81 | # Always break after template declaration 82 | AlwaysBreakTemplateDeclarations: true 83 | 84 | # macros for which the opening brace stays attached. 85 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE , wl_resource_for_each, wl_resource_for_each_safe ] 86 | 87 | # keep lambda formatting multi-line if not empty 88 | AllowShortLambdasOnASingleLine: Empty 89 | 90 | # We do not want clang-format to put all arguments on a new line 91 | AllowAllArgumentsOnNextLine: false 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /src/heif.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | High Efficiency Image File Format (HEIF) support for QImage. 3 | 4 | SPDX-FileCopyrightText: 2020 Sirius Bakke 5 | SPDX-FileCopyrightText: 2021 Daniel Novomesky 6 | 7 | SPDX-License-Identifier: LGPL-2.0-or-later 8 | */ 9 | 10 | #include "heif_p.h" 11 | #include "util_p.h" 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | size_t HEIFHandler::m_initialized_count = 0; 22 | bool HEIFHandler::m_plugins_queried = false; 23 | bool HEIFHandler::m_heif_decoder_available = false; 24 | bool HEIFHandler::m_heif_encoder_available = false; 25 | bool HEIFHandler::m_hej2_decoder_available = false; 26 | bool HEIFHandler::m_hej2_encoder_available = false; 27 | bool HEIFHandler::m_avci_decoder_available = false; 28 | 29 | extern "C" { 30 | static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata) 31 | { 32 | heif_error error; 33 | error.code = heif_error_Ok; 34 | error.subcode = heif_suberror_Unspecified; 35 | error.message = "Success"; 36 | 37 | if (!userdata || !data || size == 0) { 38 | error.code = heif_error_Usage_error; 39 | error.subcode = heif_suberror_Null_pointer_argument; 40 | error.message = "Wrong parameters!"; 41 | return error; 42 | } 43 | 44 | QIODevice *ioDevice = static_cast(userdata); 45 | qint64 bytesWritten = ioDevice->write(static_cast(data), size); 46 | 47 | if (bytesWritten < static_cast(size)) { 48 | error.code = heif_error_Encoding_error; 49 | error.message = "Bytes written to QIODevice are smaller than input data size"; 50 | error.subcode = heif_suberror_Cannot_write_output_data; 51 | } 52 | 53 | return error; 54 | } 55 | } 56 | 57 | HEIFHandler::HEIFHandler() 58 | : m_parseState(ParseHeicNotParsed) 59 | , m_quality(100) 60 | { 61 | } 62 | 63 | bool HEIFHandler::canRead() const 64 | { 65 | if (m_parseState == ParseHeicNotParsed) { 66 | QIODevice *dev = device(); 67 | if (dev) { 68 | const QByteArray header = dev->peek(28); 69 | 70 | if (HEIFHandler::isSupportedBMFFType(header)) { 71 | setFormat("heif"); 72 | return true; 73 | } 74 | 75 | if (HEIFHandler::isSupportedHEJ2(header)) { 76 | setFormat("hej2"); 77 | return true; 78 | } 79 | 80 | if (HEIFHandler::isSupportedAVCI(header)) { 81 | setFormat("avci"); 82 | return true; 83 | } 84 | } 85 | return false; 86 | } 87 | 88 | if (m_parseState != ParseHeicError) { 89 | return true; 90 | } 91 | return false; 92 | } 93 | 94 | bool HEIFHandler::read(QImage *outImage) 95 | { 96 | if (!ensureParsed()) { 97 | return false; 98 | } 99 | 100 | *outImage = m_current_image; 101 | return true; 102 | } 103 | 104 | bool HEIFHandler::write(const QImage &image) 105 | { 106 | if (image.format() == QImage::Format_Invalid || image.isNull()) { 107 | qWarning("No image data to save"); 108 | return false; 109 | } 110 | 111 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 112 | startHeifLib(); 113 | #endif 114 | 115 | bool success = write_helper(image); 116 | 117 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 118 | finishHeifLib(); 119 | #endif 120 | 121 | return success; 122 | } 123 | 124 | bool HEIFHandler::write_helper(const QImage &image) 125 | { 126 | int save_depth; // 8 or 10bit per channel 127 | QImage::Format tmpformat; // format for temporary image 128 | const bool save_alpha = image.hasAlphaChannel(); 129 | 130 | switch (image.format()) { 131 | case QImage::Format_BGR30: 132 | case QImage::Format_A2BGR30_Premultiplied: 133 | case QImage::Format_RGB30: 134 | case QImage::Format_A2RGB30_Premultiplied: 135 | case QImage::Format_Grayscale16: 136 | case QImage::Format_RGBX64: 137 | case QImage::Format_RGBA64: 138 | case QImage::Format_RGBA64_Premultiplied: 139 | save_depth = 10; 140 | break; 141 | default: 142 | if (image.depth() > 32) { 143 | save_depth = 10; 144 | } else { 145 | save_depth = 8; 146 | } 147 | break; 148 | } 149 | 150 | heif_compression_format encoder_codec = heif_compression_HEVC; 151 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 152 | if (format() == "hej2") { 153 | encoder_codec = heif_compression_JPEG2000; 154 | save_depth = 8; // for compatibility reasons 155 | } 156 | #endif 157 | 158 | heif_chroma chroma; 159 | if (save_depth > 8) { 160 | if (save_alpha) { 161 | tmpformat = QImage::Format_RGBA64; 162 | chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE; 163 | } else { 164 | tmpformat = QImage::Format_RGBX64; 165 | chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE; 166 | } 167 | } else { 168 | if (save_alpha) { 169 | tmpformat = QImage::Format_RGBA8888; 170 | chroma = heif_chroma_interleaved_RGBA; 171 | } else { 172 | tmpformat = QImage::Format_RGB888; 173 | chroma = heif_chroma_interleaved_RGB; 174 | } 175 | } 176 | 177 | #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) 178 | QImage tmpimage; 179 | auto cs = image.colorSpace(); 180 | if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.format() == QImage::Format_CMYK8888) { 181 | tmpimage = image.convertedToColorSpace(QColorSpace(QColorSpace::SRgb), tmpformat); 182 | } else if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Gray 183 | && (image.format() == QImage::Format_Grayscale8 || image.format() == QImage::Format_Grayscale16)) { 184 | QColorSpace::TransferFunction trc_new = cs.transferFunction(); 185 | float gamma_new = cs.gamma(); 186 | if (trc_new == QColorSpace::TransferFunction::Custom) { 187 | trc_new = QColorSpace::TransferFunction::SRgb; 188 | } 189 | tmpimage = image.convertedToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, trc_new, gamma_new), tmpformat); 190 | } else { 191 | tmpimage = image.convertToFormat(tmpformat); 192 | } 193 | #else 194 | QImage tmpimage = image.convertToFormat(tmpformat); 195 | #endif 196 | 197 | struct heif_context *context = heif_context_alloc(); 198 | struct heif_error err; 199 | struct heif_image *h_image = nullptr; 200 | 201 | err = heif_image_create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma, &h_image); 202 | if (err.code) { 203 | qWarning() << "heif_image_create error:" << err.message; 204 | heif_context_free(context); 205 | return false; 206 | } 207 | 208 | QByteArray iccprofile = tmpimage.colorSpace().iccProfile(); 209 | if (iccprofile.size() > 0) { 210 | heif_image_set_raw_color_profile(h_image, "prof", iccprofile.constData(), iccprofile.size()); 211 | } 212 | 213 | heif_image_add_plane(h_image, heif_channel_interleaved, image.width(), image.height(), save_depth); 214 | int stride = 0; 215 | uint8_t *const dst = heif_image_get_plane(h_image, heif_channel_interleaved, &stride); 216 | size_t rowbytes; 217 | 218 | switch (save_depth) { 219 | case 10: 220 | if (save_alpha) { 221 | for (int y = 0; y < tmpimage.height(); y++) { 222 | const uint16_t *src_word = reinterpret_cast(tmpimage.constScanLine(y)); 223 | uint16_t *dest_word = reinterpret_cast(dst + (y * stride)); 224 | for (int x = 0; x < tmpimage.width(); x++) { 225 | int tmp_pixelval; 226 | // R 227 | tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 228 | *dest_word = qBound(0, tmp_pixelval, 1023); 229 | src_word++; 230 | dest_word++; 231 | // G 232 | tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 233 | *dest_word = qBound(0, tmp_pixelval, 1023); 234 | src_word++; 235 | dest_word++; 236 | // B 237 | tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 238 | *dest_word = qBound(0, tmp_pixelval, 1023); 239 | src_word++; 240 | dest_word++; 241 | // A 242 | tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 243 | *dest_word = qBound(0, tmp_pixelval, 1023); 244 | src_word++; 245 | dest_word++; 246 | } 247 | } 248 | } else { // no alpha channel 249 | for (int y = 0; y < tmpimage.height(); y++) { 250 | const uint16_t *src_word = reinterpret_cast(tmpimage.constScanLine(y)); 251 | uint16_t *dest_word = reinterpret_cast(dst + (y * stride)); 252 | for (int x = 0; x < tmpimage.width(); x++) { 253 | int tmp_pixelval; 254 | // R 255 | tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 256 | *dest_word = qBound(0, tmp_pixelval, 1023); 257 | src_word++; 258 | dest_word++; 259 | // G 260 | tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 261 | *dest_word = qBound(0, tmp_pixelval, 1023); 262 | src_word++; 263 | dest_word++; 264 | // B 265 | tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 266 | *dest_word = qBound(0, tmp_pixelval, 1023); 267 | src_word++; 268 | dest_word++; 269 | // X 270 | src_word++; 271 | } 272 | } 273 | } 274 | break; 275 | case 8: 276 | rowbytes = save_alpha ? (tmpimage.width() * 4) : (tmpimage.width() * 3); 277 | for (int y = 0; y < tmpimage.height(); y++) { 278 | memcpy(dst + (y * stride), tmpimage.constScanLine(y), rowbytes); 279 | } 280 | break; 281 | default: 282 | qWarning() << "Unsupported depth:" << save_depth; 283 | heif_image_release(h_image); 284 | heif_context_free(context); 285 | return false; 286 | break; 287 | } 288 | 289 | struct heif_encoder *encoder = nullptr; 290 | err = heif_context_get_encoder_for_format(context, encoder_codec, &encoder); 291 | if (err.code) { 292 | qWarning() << "Unable to get an encoder instance:" << err.message; 293 | heif_image_release(h_image); 294 | heif_context_free(context); 295 | return false; 296 | } 297 | 298 | heif_encoder_set_lossy_quality(encoder, m_quality); 299 | if (m_quality > 90) { 300 | if (m_quality == 100) { 301 | heif_encoder_set_lossless(encoder, true); 302 | } 303 | heif_encoder_set_parameter_string(encoder, "chroma", "444"); 304 | } 305 | 306 | struct heif_encoding_options *encoder_options = heif_encoding_options_alloc(); 307 | encoder_options->save_alpha_channel = save_alpha; 308 | 309 | if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) { 310 | qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations."; 311 | if (save_alpha) { 312 | // This helps to save alpha channel when image has odd dimension 313 | encoder_options->macOS_compatibility_workaround = 0; 314 | } 315 | } 316 | 317 | err = heif_context_encode_image(context, h_image, encoder, encoder_options, nullptr); 318 | 319 | if (encoder_options) { 320 | heif_encoding_options_free(encoder_options); 321 | } 322 | 323 | if (err.code) { 324 | qWarning() << "heif_context_encode_image failed:" << err.message; 325 | heif_encoder_release(encoder); 326 | heif_image_release(h_image); 327 | heif_context_free(context); 328 | return false; 329 | } 330 | 331 | struct heif_writer writer; 332 | writer.writer_api_version = 1; 333 | writer.write = heifhandler_write_callback; 334 | 335 | err = heif_context_write(context, &writer, device()); 336 | 337 | heif_encoder_release(encoder); 338 | heif_image_release(h_image); 339 | 340 | if (err.code) { 341 | qWarning() << "Writing HEIF image failed:" << err.message; 342 | heif_context_free(context); 343 | return false; 344 | } 345 | 346 | heif_context_free(context); 347 | return true; 348 | } 349 | 350 | bool HEIFHandler::isSupportedBMFFType(const QByteArray &header) 351 | { 352 | if (header.size() < 28) { 353 | return false; 354 | } 355 | 356 | const char *buffer = header.constData(); 357 | if (memcmp(buffer + 4, "ftyp", 4) == 0) { 358 | if (memcmp(buffer + 8, "heic", 4) == 0) { 359 | return true; 360 | } 361 | if (memcmp(buffer + 8, "heis", 4) == 0) { 362 | return true; 363 | } 364 | if (memcmp(buffer + 8, "heix", 4) == 0) { 365 | return true; 366 | } 367 | 368 | /* we want to avoid loading AVIF files via this plugin */ 369 | if (memcmp(buffer + 8, "mif1", 4) == 0) { 370 | for (int offset = 16; offset <= 24; offset += 4) { 371 | if (memcmp(buffer + offset, "avif", 4) == 0) { 372 | return false; 373 | } 374 | } 375 | return true; 376 | } 377 | 378 | if (memcmp(buffer + 8, "mif2", 4) == 0) { 379 | return true; 380 | } 381 | if (memcmp(buffer + 8, "msf1", 4) == 0) { 382 | return true; 383 | } 384 | } 385 | 386 | return false; 387 | } 388 | 389 | bool HEIFHandler::isSupportedHEJ2(const QByteArray &header) 390 | { 391 | if (header.size() < 28) { 392 | return false; 393 | } 394 | 395 | const char *buffer = header.constData(); 396 | if (memcmp(buffer + 4, "ftypj2ki", 8) == 0) { 397 | return true; 398 | } 399 | 400 | return false; 401 | } 402 | 403 | bool HEIFHandler::isSupportedAVCI(const QByteArray &header) 404 | { 405 | if (header.size() < 28) { 406 | return false; 407 | } 408 | 409 | const char *buffer = header.constData(); 410 | if (memcmp(buffer + 4, "ftypavci", 8) == 0) { 411 | return true; 412 | } 413 | 414 | return false; 415 | } 416 | 417 | QVariant HEIFHandler::option(ImageOption option) const 418 | { 419 | if (option == Quality) { 420 | return m_quality; 421 | } 422 | 423 | if (!supportsOption(option) || !ensureParsed()) { 424 | return QVariant(); 425 | } 426 | 427 | switch (option) { 428 | case Size: 429 | return m_current_image.size(); 430 | break; 431 | default: 432 | return QVariant(); 433 | break; 434 | } 435 | } 436 | 437 | void HEIFHandler::setOption(ImageOption option, const QVariant &value) 438 | { 439 | switch (option) { 440 | case Quality: 441 | m_quality = value.toInt(); 442 | if (m_quality > 100) { 443 | m_quality = 100; 444 | } else if (m_quality < 0) { 445 | m_quality = 100; 446 | } 447 | break; 448 | default: 449 | QImageIOHandler::setOption(option, value); 450 | break; 451 | } 452 | } 453 | 454 | bool HEIFHandler::supportsOption(ImageOption option) const 455 | { 456 | return option == Quality || option == Size; 457 | } 458 | 459 | bool HEIFHandler::ensureParsed() const 460 | { 461 | if (m_parseState == ParseHeicSuccess) { 462 | return true; 463 | } 464 | if (m_parseState == ParseHeicError) { 465 | return false; 466 | } 467 | 468 | HEIFHandler *that = const_cast(this); 469 | 470 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 471 | startHeifLib(); 472 | #endif 473 | 474 | bool success = that->ensureDecoder(); 475 | 476 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 477 | finishHeifLib(); 478 | #endif 479 | return success; 480 | } 481 | 482 | bool HEIFHandler::ensureDecoder() 483 | { 484 | if (m_parseState != ParseHeicNotParsed) { 485 | if (m_parseState == ParseHeicSuccess) { 486 | return true; 487 | } 488 | return false; 489 | } 490 | 491 | const QByteArray buffer = device()->readAll(); 492 | if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer) && !HEIFHandler::isSupportedAVCI(buffer)) { 493 | m_parseState = ParseHeicError; 494 | return false; 495 | } 496 | 497 | struct heif_context *ctx = heif_context_alloc(); 498 | struct heif_error err = heif_context_read_from_memory(ctx, static_cast(buffer.constData()), buffer.size(), nullptr); 499 | 500 | if (err.code) { 501 | qWarning() << "heif_context_read_from_memory error:" << err.message; 502 | heif_context_free(ctx); 503 | m_parseState = ParseHeicError; 504 | return false; 505 | } 506 | 507 | struct heif_image_handle *handle = nullptr; 508 | err = heif_context_get_primary_image_handle(ctx, &handle); 509 | if (err.code) { 510 | qWarning() << "heif_context_get_primary_image_handle error:" << err.message; 511 | heif_context_free(ctx); 512 | m_parseState = ParseHeicError; 513 | return false; 514 | } 515 | 516 | if ((heif_image_handle_get_width(handle) == 0) || (heif_image_handle_get_height(handle) == 0)) { 517 | m_parseState = ParseHeicError; 518 | heif_image_handle_release(handle); 519 | heif_context_free(ctx); 520 | qWarning() << "HEIC image has zero dimension"; 521 | return false; 522 | } 523 | 524 | const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle); 525 | 526 | if (bit_depth < 8) { 527 | m_parseState = ParseHeicError; 528 | heif_image_handle_release(handle); 529 | heif_context_free(ctx); 530 | qWarning() << "HEIF image with undefined or unsupported bit depth."; 531 | return false; 532 | } 533 | 534 | const bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle); 535 | heif_chroma chroma; 536 | 537 | QImage::Format target_image_format; 538 | 539 | if (bit_depth == 10 || bit_depth == 12 || bit_depth == 16) { 540 | if (hasAlphaChannel) { 541 | chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE; 542 | target_image_format = QImage::Format_RGBA64; 543 | } else { 544 | chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE; 545 | target_image_format = QImage::Format_RGBX64; 546 | } 547 | } else if (bit_depth == 8) { 548 | if (hasAlphaChannel) { 549 | chroma = heif_chroma_interleaved_RGBA; 550 | target_image_format = QImage::Format_ARGB32; 551 | } else { 552 | chroma = heif_chroma_interleaved_RGB; 553 | target_image_format = QImage::Format_RGB32; 554 | } 555 | } else { 556 | m_parseState = ParseHeicError; 557 | heif_image_handle_release(handle); 558 | heif_context_free(ctx); 559 | qWarning() << "Unsupported bit depth:" << bit_depth; 560 | return false; 561 | } 562 | 563 | struct heif_decoding_options *decoder_option = heif_decoding_options_alloc(); 564 | 565 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 566 | decoder_option->strict_decoding = 1; 567 | #endif 568 | 569 | struct heif_image *img = nullptr; 570 | err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option); 571 | 572 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 573 | if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr && buffer.contains("Xiaomi")) { 574 | qWarning() << "Non-standard HEIF image with invalid matrix_coefficients, probably made by a Xiaomi device!"; 575 | 576 | // second try to decode with strict decoding disabled 577 | decoder_option->strict_decoding = 0; 578 | err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option); 579 | } 580 | #endif 581 | 582 | if (decoder_option) { 583 | heif_decoding_options_free(decoder_option); 584 | } 585 | 586 | if (err.code) { 587 | qWarning() << "heif_decode_image error:" << err.message; 588 | heif_image_handle_release(handle); 589 | heif_context_free(ctx); 590 | m_parseState = ParseHeicError; 591 | return false; 592 | } 593 | 594 | const int imageWidth = heif_image_get_width(img, heif_channel_interleaved); 595 | const int imageHeight = heif_image_get_height(img, heif_channel_interleaved); 596 | 597 | QSize imageSize(imageWidth, imageHeight); 598 | 599 | if (!imageSize.isValid()) { 600 | heif_image_release(img); 601 | heif_image_handle_release(handle); 602 | heif_context_free(ctx); 603 | m_parseState = ParseHeicError; 604 | qWarning() << "HEIC image size invalid:" << imageSize; 605 | return false; 606 | } 607 | 608 | int stride = 0; 609 | const uint8_t *const src = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride); 610 | 611 | if (!src || stride <= 0) { 612 | heif_image_release(img); 613 | heif_image_handle_release(handle); 614 | heif_context_free(ctx); 615 | m_parseState = ParseHeicError; 616 | qWarning() << "HEIC data pixels information not valid!"; 617 | return false; 618 | } 619 | 620 | m_current_image = imageAlloc(imageSize, target_image_format); 621 | if (m_current_image.isNull()) { 622 | heif_image_release(img); 623 | heif_image_handle_release(handle); 624 | heif_context_free(ctx); 625 | m_parseState = ParseHeicError; 626 | qWarning() << "Unable to allocate memory!"; 627 | return false; 628 | } 629 | 630 | switch (bit_depth) { 631 | case 16: 632 | if (hasAlphaChannel) { 633 | for (int y = 0; y < imageHeight; y++) { 634 | memcpy(m_current_image.scanLine(y), src + (y * stride), 8 * size_t(imageWidth)); 635 | } 636 | } else { // no alpha channel 637 | for (int y = 0; y < imageHeight; y++) { 638 | const uint16_t *src_word = reinterpret_cast(src + (y * stride)); 639 | uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); 640 | for (int x = 0; x < imageWidth; x++) { 641 | // R 642 | *dest_data = *src_word; 643 | src_word++; 644 | dest_data++; 645 | // G 646 | *dest_data = *src_word; 647 | src_word++; 648 | dest_data++; 649 | // B 650 | *dest_data = *src_word; 651 | src_word++; 652 | dest_data++; 653 | // X = 0xffff 654 | *dest_data = 0xffff; 655 | dest_data++; 656 | } 657 | } 658 | } 659 | break; 660 | case 12: 661 | if (hasAlphaChannel) { 662 | for (int y = 0; y < imageHeight; y++) { 663 | const uint16_t *src_word = reinterpret_cast(src + (y * stride)); 664 | uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); 665 | for (int x = 0; x < imageWidth; x++) { 666 | int tmpvalue; 667 | // R 668 | tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 669 | tmpvalue = qBound(0, tmpvalue, 65535); 670 | *dest_data = (uint16_t)tmpvalue; 671 | src_word++; 672 | dest_data++; 673 | // G 674 | tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 675 | tmpvalue = qBound(0, tmpvalue, 65535); 676 | *dest_data = (uint16_t)tmpvalue; 677 | src_word++; 678 | dest_data++; 679 | // B 680 | tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 681 | tmpvalue = qBound(0, tmpvalue, 65535); 682 | *dest_data = (uint16_t)tmpvalue; 683 | src_word++; 684 | dest_data++; 685 | // A 686 | tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 687 | tmpvalue = qBound(0, tmpvalue, 65535); 688 | *dest_data = (uint16_t)tmpvalue; 689 | src_word++; 690 | dest_data++; 691 | } 692 | } 693 | } else { // no alpha channel 694 | for (int y = 0; y < imageHeight; y++) { 695 | const uint16_t *src_word = reinterpret_cast(src + (y * stride)); 696 | uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); 697 | for (int x = 0; x < imageWidth; x++) { 698 | int tmpvalue; 699 | // R 700 | tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 701 | tmpvalue = qBound(0, tmpvalue, 65535); 702 | *dest_data = (uint16_t)tmpvalue; 703 | src_word++; 704 | dest_data++; 705 | // G 706 | tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 707 | tmpvalue = qBound(0, tmpvalue, 65535); 708 | *dest_data = (uint16_t)tmpvalue; 709 | src_word++; 710 | dest_data++; 711 | // B 712 | tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 713 | tmpvalue = qBound(0, tmpvalue, 65535); 714 | *dest_data = (uint16_t)tmpvalue; 715 | src_word++; 716 | dest_data++; 717 | // X = 0xffff 718 | *dest_data = 0xffff; 719 | dest_data++; 720 | } 721 | } 722 | } 723 | break; 724 | case 10: 725 | if (hasAlphaChannel) { 726 | for (int y = 0; y < imageHeight; y++) { 727 | const uint16_t *src_word = reinterpret_cast(src + (y * stride)); 728 | uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); 729 | for (int x = 0; x < imageWidth; x++) { 730 | int tmpvalue; 731 | // R 732 | tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 733 | tmpvalue = qBound(0, tmpvalue, 65535); 734 | *dest_data = (uint16_t)tmpvalue; 735 | src_word++; 736 | dest_data++; 737 | // G 738 | tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 739 | tmpvalue = qBound(0, tmpvalue, 65535); 740 | *dest_data = (uint16_t)tmpvalue; 741 | src_word++; 742 | dest_data++; 743 | // B 744 | tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 745 | tmpvalue = qBound(0, tmpvalue, 65535); 746 | *dest_data = (uint16_t)tmpvalue; 747 | src_word++; 748 | dest_data++; 749 | // A 750 | tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 751 | tmpvalue = qBound(0, tmpvalue, 65535); 752 | *dest_data = (uint16_t)tmpvalue; 753 | src_word++; 754 | dest_data++; 755 | } 756 | } 757 | } else { // no alpha channel 758 | for (int y = 0; y < imageHeight; y++) { 759 | const uint16_t *src_word = reinterpret_cast(src + (y * stride)); 760 | uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); 761 | for (int x = 0; x < imageWidth; x++) { 762 | int tmpvalue; 763 | // R 764 | tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 765 | tmpvalue = qBound(0, tmpvalue, 65535); 766 | *dest_data = (uint16_t)tmpvalue; 767 | src_word++; 768 | dest_data++; 769 | // G 770 | tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 771 | tmpvalue = qBound(0, tmpvalue, 65535); 772 | *dest_data = (uint16_t)tmpvalue; 773 | src_word++; 774 | dest_data++; 775 | // B 776 | tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 777 | tmpvalue = qBound(0, tmpvalue, 65535); 778 | *dest_data = (uint16_t)tmpvalue; 779 | src_word++; 780 | dest_data++; 781 | // X = 0xffff 782 | *dest_data = 0xffff; 783 | dest_data++; 784 | } 785 | } 786 | } 787 | break; 788 | case 8: 789 | if (hasAlphaChannel) { 790 | for (int y = 0; y < imageHeight; y++) { 791 | const uint8_t *src_byte = src + (y * stride); 792 | uint32_t *dest_pixel = reinterpret_cast(m_current_image.scanLine(y)); 793 | for (int x = 0; x < imageWidth; x++) { 794 | int red = *src_byte++; 795 | int green = *src_byte++; 796 | int blue = *src_byte++; 797 | int alpha = *src_byte++; 798 | *dest_pixel = qRgba(red, green, blue, alpha); 799 | dest_pixel++; 800 | } 801 | } 802 | } else { // no alpha channel 803 | for (int y = 0; y < imageHeight; y++) { 804 | const uint8_t *src_byte = src + (y * stride); 805 | uint32_t *dest_pixel = reinterpret_cast(m_current_image.scanLine(y)); 806 | for (int x = 0; x < imageWidth; x++) { 807 | int red = *src_byte++; 808 | int green = *src_byte++; 809 | int blue = *src_byte++; 810 | *dest_pixel = qRgb(red, green, blue); 811 | dest_pixel++; 812 | } 813 | } 814 | } 815 | break; 816 | default: 817 | heif_image_release(img); 818 | heif_image_handle_release(handle); 819 | heif_context_free(ctx); 820 | m_parseState = ParseHeicError; 821 | qWarning() << "Unsupported bit depth:" << bit_depth; 822 | return false; 823 | break; 824 | } 825 | 826 | heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle); 827 | if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) { 828 | size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle); 829 | if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits::max()) { 830 | QByteArray ba(rawProfileSize, 0); 831 | err = heif_image_handle_get_raw_color_profile(handle, ba.data()); 832 | if (err.code) { 833 | qWarning() << "icc profile loading failed"; 834 | } else { 835 | QColorSpace colorspace = QColorSpace::fromIccProfile(ba); 836 | if (!colorspace.isValid()) { 837 | qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!"; 838 | } 839 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)) 840 | else if (colorspace.colorModel() == QColorSpace::ColorModel::Cmyk) { 841 | qWarning("CMYK ICC profile is not expected for HEIF, discarding the ICCprofile!"); 842 | colorspace = QColorSpace(); 843 | } else if (colorspace.colorModel() == QColorSpace::ColorModel::Gray) { 844 | if (hasAlphaChannel) { 845 | QPointF gray_whitePoint = colorspace.whitePoint(); 846 | if (gray_whitePoint.isNull()) { 847 | gray_whitePoint = QPointF(0.3127f, 0.329f); 848 | } 849 | 850 | const QPointF redP(0.64f, 0.33f); 851 | const QPointF greenP(0.3f, 0.6f); 852 | const QPointF blueP(0.15f, 0.06f); 853 | 854 | QColorSpace::TransferFunction trc_new = colorspace.transferFunction(); 855 | float gamma_new = colorspace.gamma(); 856 | if (trc_new == QColorSpace::TransferFunction::Custom) { 857 | trc_new = QColorSpace::TransferFunction::SRgb; 858 | } 859 | colorspace = QColorSpace(gray_whitePoint, redP, greenP, blueP, trc_new, gamma_new); 860 | if (!colorspace.isValid()) { 861 | qWarning("HEIF plugin created invalid QColorSpace!"); 862 | } 863 | } else { // no alpha channel 864 | m_current_image.convertTo(bit_depth > 8 ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8); 865 | } 866 | } 867 | #endif 868 | m_current_image.setColorSpace(colorspace); 869 | } 870 | } else { 871 | qWarning() << "icc profile is empty or above limits"; 872 | } 873 | 874 | } else if (profileType == heif_color_profile_type_nclx) { 875 | struct heif_color_profile_nclx *nclx = nullptr; 876 | err = heif_image_handle_get_nclx_color_profile(handle, &nclx); 877 | if (err.code || !nclx) { 878 | qWarning() << "nclx profile loading failed"; 879 | } else { 880 | const QPointF redPoint(nclx->color_primary_red_x, nclx->color_primary_red_y); 881 | const QPointF greenPoint(nclx->color_primary_green_x, nclx->color_primary_green_y); 882 | const QPointF bluePoint(nclx->color_primary_blue_x, nclx->color_primary_blue_y); 883 | const QPointF whitePoint(nclx->color_primary_white_x, nclx->color_primary_white_y); 884 | 885 | QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom; 886 | float q_trc_gamma = 0.0f; 887 | 888 | switch (nclx->transfer_characteristics) { 889 | case 4: 890 | q_trc = QColorSpace::TransferFunction::Gamma; 891 | q_trc_gamma = 2.2f; 892 | break; 893 | case 5: 894 | q_trc = QColorSpace::TransferFunction::Gamma; 895 | q_trc_gamma = 2.8f; 896 | break; 897 | case 8: 898 | q_trc = QColorSpace::TransferFunction::Linear; 899 | break; 900 | case 2: 901 | case 13: 902 | q_trc = QColorSpace::TransferFunction::SRgb; 903 | break; 904 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)) 905 | case 16: 906 | q_trc = QColorSpace::TransferFunction::St2084; 907 | break; 908 | case 18: 909 | q_trc = QColorSpace::TransferFunction::Hlg; 910 | break; 911 | #endif 912 | default: 913 | qWarning("CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet.", 914 | nclx->color_primaries, 915 | nclx->transfer_characteristics); 916 | q_trc = QColorSpace::TransferFunction::SRgb; 917 | break; 918 | } 919 | 920 | if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt 921 | switch (nclx->color_primaries) { 922 | case 1: 923 | case 2: 924 | m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma)); 925 | break; 926 | case 12: 927 | m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma)); 928 | break; 929 | default: 930 | m_current_image.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma)); 931 | break; 932 | } 933 | } 934 | heif_nclx_color_profile_free(nclx); 935 | 936 | if (!m_current_image.colorSpace().isValid()) { 937 | qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!"; 938 | } 939 | } 940 | 941 | } else { 942 | m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb)); 943 | } 944 | 945 | heif_image_release(img); 946 | heif_image_handle_release(handle); 947 | heif_context_free(ctx); 948 | m_parseState = ParseHeicSuccess; 949 | return true; 950 | } 951 | 952 | bool HEIFHandler::isHeifDecoderAvailable() 953 | { 954 | HEIFHandler::queryHeifLib(); 955 | 956 | return m_heif_decoder_available; 957 | } 958 | 959 | bool HEIFHandler::isHeifEncoderAvailable() 960 | { 961 | HEIFHandler::queryHeifLib(); 962 | 963 | return m_heif_encoder_available; 964 | } 965 | 966 | bool HEIFHandler::isHej2DecoderAvailable() 967 | { 968 | HEIFHandler::queryHeifLib(); 969 | 970 | return m_hej2_decoder_available; 971 | } 972 | 973 | bool HEIFHandler::isHej2EncoderAvailable() 974 | { 975 | HEIFHandler::queryHeifLib(); 976 | 977 | return m_hej2_encoder_available; 978 | } 979 | 980 | bool HEIFHandler::isAVCIDecoderAvailable() 981 | { 982 | HEIFHandler::queryHeifLib(); 983 | 984 | return m_avci_decoder_available; 985 | } 986 | 987 | void HEIFHandler::queryHeifLib() 988 | { 989 | QMutexLocker locker(&getHEIFHandlerMutex()); 990 | 991 | if (!m_plugins_queried) { 992 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 993 | if (m_initialized_count == 0) { 994 | heif_init(nullptr); 995 | } 996 | #endif 997 | 998 | m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); 999 | m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); 1000 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 1001 | m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000); 1002 | m_hej2_encoder_available = heif_have_encoder_for_format(heif_compression_JPEG2000); 1003 | #endif 1004 | #if LIBHEIF_HAVE_VERSION(1, 19, 6) 1005 | m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC); 1006 | #endif 1007 | m_plugins_queried = true; 1008 | 1009 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 1010 | if (m_initialized_count == 0) { 1011 | heif_deinit(); 1012 | } 1013 | #endif 1014 | } 1015 | } 1016 | 1017 | void HEIFHandler::startHeifLib() 1018 | { 1019 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 1020 | QMutexLocker locker(&getHEIFHandlerMutex()); 1021 | 1022 | if (m_initialized_count == 0) { 1023 | heif_init(nullptr); 1024 | } 1025 | 1026 | m_initialized_count++; 1027 | #endif 1028 | } 1029 | 1030 | void HEIFHandler::finishHeifLib() 1031 | { 1032 | #if LIBHEIF_HAVE_VERSION(1, 13, 0) 1033 | QMutexLocker locker(&getHEIFHandlerMutex()); 1034 | 1035 | if (m_initialized_count == 0) { 1036 | return; 1037 | } 1038 | 1039 | m_initialized_count--; 1040 | if (m_initialized_count == 0) { 1041 | heif_deinit(); 1042 | } 1043 | 1044 | #endif 1045 | } 1046 | 1047 | QMutex &HEIFHandler::getHEIFHandlerMutex() 1048 | { 1049 | static QMutex heif_handler_mutex; 1050 | return heif_handler_mutex; 1051 | } 1052 | 1053 | QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const 1054 | { 1055 | if (format == "heif" || format == "heic") { 1056 | Capabilities format_cap; 1057 | if (HEIFHandler::isHeifDecoderAvailable()) { 1058 | format_cap |= CanRead; 1059 | } 1060 | if (HEIFHandler::isHeifEncoderAvailable()) { 1061 | format_cap |= CanWrite; 1062 | } 1063 | return format_cap; 1064 | } 1065 | 1066 | if (format == "hej2") { 1067 | Capabilities format_cap; 1068 | if (HEIFHandler::isHej2DecoderAvailable()) { 1069 | format_cap |= CanRead; 1070 | } 1071 | if (HEIFHandler::isHej2EncoderAvailable()) { 1072 | format_cap |= CanWrite; 1073 | } 1074 | return format_cap; 1075 | } 1076 | 1077 | if (format == "avci") { 1078 | Capabilities format_cap; 1079 | if (HEIFHandler::isAVCIDecoderAvailable()) { 1080 | format_cap |= CanRead; 1081 | } 1082 | return format_cap; 1083 | } 1084 | 1085 | if (!format.isEmpty()) { 1086 | return {}; 1087 | } 1088 | if (!device->isOpen()) { 1089 | return {}; 1090 | } 1091 | 1092 | Capabilities cap; 1093 | if (device->isReadable()) { 1094 | const QByteArray header = device->peek(28); 1095 | 1096 | if ((HEIFHandler::isSupportedBMFFType(header) && HEIFHandler::isHeifDecoderAvailable()) 1097 | || (HEIFHandler::isSupportedHEJ2(header) && HEIFHandler::isHej2DecoderAvailable()) 1098 | || (HEIFHandler::isSupportedAVCI(header) && HEIFHandler::isAVCIDecoderAvailable())) { 1099 | cap |= CanRead; 1100 | } 1101 | } 1102 | 1103 | if (device->isWritable() && (HEIFHandler::isHeifEncoderAvailable() || HEIFHandler::isHej2EncoderAvailable())) { 1104 | cap |= CanWrite; 1105 | } 1106 | return cap; 1107 | } 1108 | 1109 | QImageIOHandler *HEIFPlugin::create(QIODevice *device, const QByteArray &format) const 1110 | { 1111 | QImageIOHandler *handler = new HEIFHandler; 1112 | handler->setDevice(device); 1113 | handler->setFormat(format); 1114 | return handler; 1115 | } 1116 | 1117 | #include "moc_heif_p.cpp" 1118 | --------------------------------------------------------------------------------