From 34a9ec1b06844a4caca1362ee15fc8a4143931e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Novomesk=C3=BD?= Date: Sun, 13 Dec 2020 22:23:27 +0000 Subject: [PATCH] Add plugin for AV1 Image File Format (AVIF) --- CMakeLists.txt | 7 + autotests/CMakeLists.txt | 6 + autotests/read/avif/bw.avif | Bin 0 -> 629 bytes autotests/read/avif/bw.png | Bin 0 -> 743 bytes autotests/read/avif/bwa.avif | Bin 0 -> 823 bytes autotests/read/avif/bwa.png | Bin 0 -> 574 bytes autotests/read/avif/rgb.avif | Bin 0 -> 1579 bytes autotests/read/avif/rgb.png | Bin 0 -> 1053 bytes autotests/read/avif/rgba.avif | Bin 0 -> 2260 bytes autotests/read/avif/rgba.png | Bin 0 -> 1234 bytes src/imageformats/CMakeLists.txt | 8 + src/imageformats/avif.cpp | 981 ++++++++++++++++++++++++++++++++ src/imageformats/avif.desktop | 7 + src/imageformats/avif.json | 4 + src/imageformats/avif_p.h | 80 +++ 15 files changed, 1093 insertions(+) create mode 100644 autotests/read/avif/bw.avif create mode 100644 autotests/read/avif/bw.png create mode 100644 autotests/read/avif/bwa.avif create mode 100644 autotests/read/avif/bwa.png create mode 100644 autotests/read/avif/rgb.avif create mode 100644 autotests/read/avif/rgb.png create mode 100644 autotests/read/avif/rgba.avif create mode 100644 autotests/read/avif/rgba.png create mode 100644 src/imageformats/avif.cpp create mode 100644 src/imageformats/avif.desktop create mode 100644 src/imageformats/avif.json create mode 100644 src/imageformats/avif_p.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e916398..a974f48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,13 @@ set_package_properties(OpenEXR PROPERTIES TYPE OPTIONAL PURPOSE "Required for the QImage plugin for OpenEXR images" ) + +find_package(libavif 0.8.2 CONFIG) +set_package_properties(libavif PROPERTIES + TYPE OPTIONAL + PURPOSE "Required for the QImage plugin for AVIF images" +) + add_definitions(-DQT_NO_FOREACH) # 050d00 (5.13) triggers a BIC in qimageiohandler.h, in Qt 5.13, so do not enable that until we can require 5.14 # https://codereview.qt-project.org/c/qt/qtbase/+/279215 diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 4d90f99..1eebcfc 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -70,6 +70,12 @@ if (KF5Archive_FOUND) ) endif() +if (TARGET avif) + kimageformats_read_tests( + avif + ) +endif() + # Allow some fuzziness when reading this formats, to allow for # rounding errors (eg: in alpha blending). kimageformats_read_tests(FUZZ 1 diff --git a/autotests/read/avif/bw.avif b/autotests/read/avif/bw.avif new file mode 100644 index 0000000000000000000000000000000000000000..940043b6c14f708560f2a5c6bd5c1ac378ff7fc9 GIT binary patch literal 629 zcmZQzV30{GsVqn=%S>Yc0uY^>nP!-qnF!*4$W1Lt1c?KIMn+0b5roOWP>`8i0%L>d zoXjMc5qt%iCAnZZMj$DdnUkLk;<+#|FajZnWMC8lVvxBSnVETDd%-eNKu#(XNHI`N zVwr)VbAC=f*vgE|f}#STxMyZTaz2n2$t*5N1t|mq1thTGdCr%M2LZlLqgF0-wloo zQ=>~v0u1LlO}7+Jlh(PxADeV7v$=cr2CEZ_tnZKg3uD${k@d>|^=`j|s;c&noom;1 zv5IQ3=sBtI{!O?avTR1VQ<)d*g1hROQ9)Y_Cfv@~%{%VTTm0DD@%6sP6|$Z`yMH`< zc;ea)FU1P?6GHnA`&PE`zYk7cTp}64^zuP&LE$5n%8ZSd#Kj#~UZ1+cI@mQ7oLAM)>CO42uh%Ti3!GH4h(j>Q za%GBm^wviz%+HtaeV~7>_0IQ?)%y*CcgV8r%U{s&C~Lx+@)(uF;^E6S_N_U-`PrUx zcWWQq{xtjbzV|onwpc9ddwZeya`Za8u=W=$b5DkvkU+ev6|Fmg3?{k7aPXPc3 CZ{4*3 literal 0 HcmV?d00001 diff --git a/autotests/read/avif/bw.png b/autotests/read/avif/bw.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d506bb3da276c09b8f9388dc8ad6b96f987f5e GIT binary patch literal 743 zcmV?P)E1)jxbCtrE}4UMN{WWBtG@56F7V!ybw)(i9BT()zyEYy z*ECJnb>X{hTkpM;5&(=b&N=6twKj?(N+|#kLcI6RIRJ3Z5kf*95z{n9#4i9{*Ue_L ztE;P`D6F;f`Fu8;5kkh}G3Wg0>8WX&s;b`J-Ufp~nx>2~LWnV@u4_cB>ly&!IF?dw zZEc;MolU1xMEnG3n&$HI^8EaKKA$`1wAPEof)Fwo3@D|G#lm}Ut?jxF02pIY^wC~p z45idsyUa@|hr{8)!NC`R-y<9qLJ&e2W1RCW%LpNlkB?-YwM_g-rqjE0Co zBz*z^0Ove#Ym8ZO&VP@CIF32zKbeE3X~MYXocGUxbFM5)tu+7$A^NYt81wM(@c#a; zl*;q`?>W&!1$A9pYuCj=pkXQ5pSe{mFTVOV^*y77_t{#z7Na5J-rnA5G^*?R`T5xx z)Bk&r=lRagju2ur8m;)?!0O%f-oL)SUSD6|-`}5{oP@QfPk?~YwyoA$DYc@};Ky&* ZzW_2gTD;a-w0-~p002ovPDHLkV1n%MZ8iV^ literal 0 HcmV?d00001 diff --git a/autotests/read/avif/bwa.avif b/autotests/read/avif/bwa.avif new file mode 100644 index 0000000000000000000000000000000000000000..f9cf4c957addb1d648fa128d88c7227752ff9a1f GIT binary patch literal 823 zcmZQzV30{GsVqn=%S>Yc0uY^>nP!-qnF!=F&d*IPNd$=lfksA3P7#F3z)+BxTmoZ* z=$y}_!3Jia=>;Oa`+rxBE;0!)TyUzL5_mtW zaMP>}+7~}<-Tr0szlU!n&T^mF8&p?dc~Ys*cCFO{qi1}}(w5hkCvVhhGjpAFZ^7}y z&mL+r?&Eu^dE`V|{eqrVe|Q{^FVS}qxb$aH^8XLs`=_junYZ9$(D751Az>RnR+}`f zeEo8-=&NtDSC!l?V90M1`1$PG)G_+}!z^ z<(daSeWl}8f2z!W#F?d4H1&aOLd4PT-#cSpe0ftb|INIS5CJ~Ye*=p6TZjSwtm7o-PPACuAF{!{_ApYhp>cWE`Jhv|FP8k>*VZy$IXx* zC$rtOSv%U{Yt7GPbFcW`U%P7=dzIX;+eyEx@+a?n5uWUm(yD)X!50ygb^!1y4qp2c$S6kB>o&B3VfPu>a3fvnU8Ky>@0t^g} KFOEbsIRF4X0uOBf literal 0 HcmV?d00001 diff --git a/autotests/read/avif/bwa.png b/autotests/read/avif/bwa.png new file mode 100644 index 0000000000000000000000000000000000000000..a75415012846b5eead134ac502aa740c0cf1cfa7 GIT binary patch literal 574 zcmV-E0>S->P)mofHc2PM z0S82JTDTEZbSp0XGyWxa{s(bsKoEDj(2WBK28=;*igwaTRl7L_Md*&EIw=uyp{O42 zJMYxF_uP6M#6OPdeZcJO2DdZjw^__#m}^{2C3mWpH;+YpM;`??^WRMJzF{zmKnEvq z8#hs6eFyS~aTdJ*4nyE5zT#)ruO$CWFz@3D2HkC-U0lIk+z8;-7!@j|=JUB2kyk0$ zuc`% z)=Dsoi?QD-p6&47*cSq7b6^1{+it?5_Qa=gD@0m#!AkDaINoFqOwxp9Y}gOZa&^vY z(*@P*x5yc=Xp=yJPdVX=6Ivy(j-?vLQWG0?_TW%DS0hw7i2p5q113|2rrJF}9RL6T M07*qoM6N<$f@8P)Z~y=R literal 0 HcmV?d00001 diff --git a/autotests/read/avif/rgb.avif b/autotests/read/avif/rgb.avif new file mode 100644 index 0000000000000000000000000000000000000000..6560e06695358d55f5237d4e08e0e018887f16a8 GIT binary patch literal 1579 zcmXv|3p|s182*N}wJequp~$H%QktmFszvUXO-@Fgu49&u-Heu7xy)s(7Lv# z&{IVKrVjvc45ZhBAp7U;pA~&5@mEtdLp?P9MuHuO$@#f55ajZ>knaX^{WuU~!4R&$ zsu6|%`}1pqpJ943$L#|(oeqX8fWvUEW%i>9hUfDY4u zsR=ZotCq9qw9q903b_@tQ`xTFu4WmB(guJI8->^3&jn8n&eGWHJ7W)%lCN)A)xtI3 zv=!CDz!0G{YLpL`kjxy?jWfoV!#Jn(5S(r3hB zeLMTX8q&%yQlD$>;sR+*L?%+pV8}t+WgXCUdZAiCiovMB1z%e7QFwRBwso^(?d~Sl zqleer6*W#Wt+XFERvAm-Hfl0&FEy@&fiMJ$kDeV)sQI38;_QqMA!bW2v4@_n4D6H2 z|G8N>b83P&UGXZl!rZ^ALuct6F0{|AtZ=xnbNxf|kq2zy$fC~0NxEP&a`raNJ{-yK zL~zRSng~KQT^(`KI->Wyg0u!{w=Ktwx=)_=uu;yERHx~U6g2Xv_gS?U)JLxyWd_Du zXS>$z|2#FW@L8^?xlPPVBnD2>!VmaGsy%EO*;J);5PwM)TgVbWyu2?Oi8}CVwNr#q zUBboq7GYBXWo;;7XBN@JT&?VB z)}H9rYGjkiV@LcqmAx-6I$qX+7hwDLT3_z}ZE`~XNbB14lLq@U86Bm`%CGQrBBNx5 z>BAvK+(CiVqQ=@Dcb4M&_}~EqZ#u zoJ-W>DkMjXKico!0K7}b#6NYW${)Rmt2RA2Ae{Q6Xcgo+$_TU-YP*F4Yv3?oSf;u^PwJvI!c;!(LQ3KfmL-z*4aM+ukmn z6qe^xewec?Y3S>eq}+}cEP=lZNdNSWBK3^RnFxm8tAw4Czk6e=`}Qvl_ggu8^xujU zbi?qA1v^|nqjyE=;MJldyZpDz@((iCSv3?Akv)fkyWq)B<}XH)yqyS}z>nS$hH=dy zrVW_#>R9yl=~7A4%EKKp(hKhm^scgV%GC-->2OV5hO4udF51ATYe8*RAU!Z6&OSU- zn(MKUpqcoBluxbvDzZ1@e17(+bfCViwR)=p;d14*To`-~Go54B8|1+~UfBBVVE2M8 z?x^zD>JeA?HqzQ5togk+JC{hyf$R0pBh}@1{js)te*D=%dUk9wGD*nBBy^1LT+!b9 zZqX}}Pk99~GTVy`NM=8rWDL5<-h0!lH*UuiP!Ia?gX<%BVnbT&vf{x}xiC$*VjM+5>1Q91T@l28w@Af4QrM7fKjx<9@(a=~jVbTU zzwGxkoF(JNU)yF-ud`iD$9Bx4(ILZCH9ZSuQ4~BNAy*uPjiaL zT066MIRa7gA2?Bp!CXl+^>o@95~47v5=nKAMxXb7Sw_L!;+qWKhzGoy;T9uOe9)9$ zo)T+L&6E?pTsgaXbM<}j-<^tli{}J88kJ_a`han8<*HTtjXb9hU6SP|!fGAfn;MuN F_!nu{v3LLg literal 0 HcmV?d00001 diff --git a/autotests/read/avif/rgb.png b/autotests/read/avif/rgb.png new file mode 100644 index 0000000000000000000000000000000000000000..267d67841ad5f80e0f36cbf514c33d6e9cac146b GIT binary patch literal 1053 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 literal 0 HcmV?d00001 diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index 8a53bba..c2b442e 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -24,6 +24,14 @@ endfunction() ################################## +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/) ################################## diff --git a/src/imageformats/avif.cpp b/src/imageformats/avif.cpp new file mode 100644 index 0000000..5909607 --- /dev/null +++ b/src/imageformats/avif.cpp @@ -0,0 +1,981 @@ +/* + AV1 Image File Format (AVIF) support for QImage. + + SPDX-FileCopyrightText: 2020 Daniel Novomesky + + SPDX-License-Identifier: BSD-2-Clause +*/ + +#include +#include + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) +#include +#endif + +#include "avif_p.h" + + +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(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\n", 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\n", 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\n", 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; + } + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) { + result.setColorSpace(QColorSpace::fromIccProfile(QByteArray::fromRawData((const char *) m_decoder->image->icc.data, (int) m_decoder->image->icc.size))); + if (! result.colorSpace().isValid()) { + qWarning("Invalid QColorSpace created from ICC!\n"); + } + } else { + + avifColorPrimaries primaries_to_load; + avifTransferCharacteristics trc_to_load; + + if ((m_decoder->image->colorPrimaries == 2 /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */) || + (m_decoder->image->colorPrimaries == 0 /* AVIF_COLOR_PRIMARIES_UNKNOWN */)) { + primaries_to_load = (avifColorPrimaries) 1; // AVIF_COLOR_PRIMARIES_BT709 + } else { + primaries_to_load = m_decoder->image->colorPrimaries; + } + if ((m_decoder->image->transferCharacteristics == 2 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */) || + (m_decoder->image->transferCharacteristics == 0 /* AVIF_TRANSFER_CHARACTERISTICS_UNKNOWN */)) { + trc_to_load = (avifTransferCharacteristics) 13; // AVIF_TRANSFER_CHARACTERISTICS_SRGB + } else { + trc_to_load = m_decoder->image->transferCharacteristics; + } + + float prim[8]; // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY + avifColorPrimariesGetValues(primaries_to_load, prim); + + QPointF redPoint(prim[0], prim[1]); + QPointF greenPoint(prim[2], prim[3]); + QPointF bluePoint(prim[4], prim[5]); + QPointF whitePoint(prim[6], prim[7]); + + + QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom; + float q_trc_gamma = 0.0f; + + switch (trc_to_load) { + /* 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 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 (primaries_to_load) { + /* AVIF_COLOR_PRIMARIES_BT709 */ + case 1: + result.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma)); + break; + /* AVIF_COLOR_PRIMARIES_SMPTE432 */ + case 12: + result.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma)); + break; + default: + result.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma)); + break; + } + } + + if (! result.colorSpace().isValid()) { + qWarning("Invalid QColorSpace created from NCLX/CICP!\n"); + } + } +#endif + + 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 (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) + if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) { + resultformat = QImage::Format_Grayscale16; + } +#endif + } + } 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\n", 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\n"); + } + } + + 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) { + switch (m_decoder->image->imir.axis) { + case 0: //vertical + result = result.mirrored(false, true); + break; + case 1: //horizontal + 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: +#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) + case QImage::Format_Grayscale16: +#endif + 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: +#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) + case QImage::Format_Grayscale16: +#endif + 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 (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) + if (save_depth > 8) { + tmpformat = QImage::Format_Grayscale16; + } else { + tmpformat = QImage::Format_Grayscale8; + } +#else + tmpformat = QImage::Format_Grayscale8; + save_depth = 8; +#endif + QImage tmpgrayimage = image.convertToFormat(tmpformat); + + avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400); + avifImageAllocatePlanes(avif, AVIF_PLANES_YUV); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + 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; + } + + } +#endif + + if (save_depth > 8) { // QImage::Format_Grayscale16 + for (int y = 0; y < tmpgrayimage.height(); y++) { + const uint16_t *src16bit = reinterpret_cast(tmpgrayimage.constScanLine(y)); + uint16_t *dest16bit = reinterpret_cast(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; + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + + avifColorPrimaries primaries_to_save = (avifColorPrimaries)2; + avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2; + + 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)); + } + } + } +#endif + avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format); + avif->matrixCoefficients = matrix_to_save; + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + avif->colorPrimaries = primaries_to_save; + avif->transferCharacteristics = transfer_to_save; +#endif + + avifRGBImage rgb; + avifRGBImageSetDefaults(&rgb, avif); + rgb.rowBytes = tmpcolorimage.bytesPerLine(); + rgb.pixels = const_cast(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\n", 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\n", qUtf8Printable(device()->errorString())); + return false; + } + } else { + qWarning("ERROR: Failed to encode: %s\n", avifResultToString(res)); + } + + return false; +} + + +QVariant QAVIFHandler::option(ImageOption option) const +{ + if (!supportsOption(option) || !ensureParsed()) { + return QVariant(); + } + + switch (option) { + case Quality: + return m_quality; + 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\n", 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)!\n", + 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\n", 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)!\n", + 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; +} + +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; +} diff --git a/src/imageformats/avif.desktop b/src/imageformats/avif.desktop new file mode 100644 index 0000000..fcda177 --- /dev/null +++ b/src/imageformats/avif.desktop @@ -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 diff --git a/src/imageformats/avif.json b/src/imageformats/avif.json new file mode 100644 index 0000000..47d06b4 --- /dev/null +++ b/src/imageformats/avif.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "avif", "avifs" ], + "MimeTypes": [ "image/avif", "image/avif-sequence" ] +} diff --git a/src/imageformats/avif_p.h b/src/imageformats/avif_p.h new file mode 100644 index 0000000..6876645 --- /dev/null +++ b/src/imageformats/avif_p.h @@ -0,0 +1,80 @@ +/* + AV1 Image File Format (AVIF) support for QImage. + + SPDX-FileCopyrightText: 2020 Daniel Novomesky + + SPDX-License-Identifier: BSD-2-Clause +*/ + +#ifndef KIMG_AVIF_P_H +#define KIMG_AVIF_P_H + +#include +#include +#include +#include +#include +#include + +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: + 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