From 9f69817344eb75fbc4613892c1f54ee69cae2e66 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen <allan.jensen@qt.io> Date: Tue, 11 Dec 2018 10:48:15 +0100 Subject: [PATCH] Add Grayscale16 support to TIFF Change-Id: I927d9ab0af78baf90d8fd8d44088218dff0e7082 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io> --- .../imageformats/tiff/qtiffhandler.cpp | 10 +++++++-- tests/auto/tiff/tst_qtiff.cpp | 20 +++++++++++++++++- tests/shared/images/tiff.qrc | 1 + tests/shared/images/tiff/gray16.tiff | Bin 0 -> 8164 bytes 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/shared/images/tiff/gray16.tiff diff --git a/src/plugins/imageformats/tiff/qtiffhandler.cpp b/src/plugins/imageformats/tiff/qtiffhandler.cpp index edb54438..837f9ff5 100644 --- a/src/plugins/imageformats/tiff/qtiffhandler.cpp +++ b/src/plugins/imageformats/tiff/qtiffhandler.cpp @@ -272,6 +272,8 @@ bool QTiffHandlerPrivate::readHeaders(QIODevice *device) format = QImage::Format_Mono; else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 8 && samplesPerPixel == 1) format = QImage::Format_Grayscale8; + else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 16 && samplesPerPixel == 1) + format = QImage::Format_Grayscale16; else if ((grayscale || photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1) format = QImage::Format_Indexed8; else if (samplesPerPixel < 4) @@ -402,9 +404,11 @@ bool QTiffHandler::read(QImage *image) } } bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8); + bool format16bit = (format == QImage::Format_Grayscale16); bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied); - if (format8bit || format64bit) { + // Formats we read directly, instead of over RGBA32: + if (format8bit || format16bit || format64bit) { int bytesPerPixel = image->depth() / 8; if (format == QImage::Format_RGBX64) bytesPerPixel = 6; @@ -513,6 +517,7 @@ static QVector<QRgb> effectiveColorTable(const QImage &image) colors[i] = qRgba(0, 0, 0, i); break; case QImage::Format_Grayscale8: + case QImage::Format_Grayscale16: colors.resize(256); for (int i = 0; i < 256; ++i) colors[i] = qRgb(i, i, i); @@ -622,6 +627,7 @@ bool QTiffHandler::write(const QImage &image) TIFFClose(tiff); } else if (format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8 + || format == QImage::Format_Grayscale16 || format == QImage::Format_Alpha8) { QVector<QRgb> colorTable = effectiveColorTable(image); bool isGrayscale = checkGrayscale(colorTable); @@ -631,7 +637,7 @@ bool QTiffHandler::write(const QImage &image) photometric = PHOTOMETRIC_MINISWHITE; if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric) || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW) - || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8) + || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, image.depth()) || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) { TIFFClose(tiff); return false; diff --git a/tests/auto/tiff/tst_qtiff.cpp b/tests/auto/tiff/tst_qtiff.cpp index 9c815d57..b2c55468 100644 --- a/tests/auto/tiff/tst_qtiff.cpp +++ b/tests/auto/tiff/tst_qtiff.cpp @@ -85,6 +85,7 @@ private slots: void tiled(); void readRgba64(); + void readGray16(); private: QString prefix; @@ -168,6 +169,7 @@ void tst_qtiff::readImage_data() QTest::newRow("tiled_oddsize_grayscale") << QString("tiled_oddsize_grayscale.tiff") << QSize(59, 71); QTest::newRow("tiled_oddsize_mono") << QString("tiled_oddsize_mono.tiff") << QSize(59, 71); QTest::newRow("16bpc") << QString("16bpc.tiff") << QSize(64, 46); + QTest::newRow("gray16") << QString("gray16.tiff") << QSize(64, 46); } void tst_qtiff::readImage() @@ -178,9 +180,13 @@ void tst_qtiff::readImage() QString path = prefix + fileName; QImageReader reader(path); QVERIFY(reader.canRead()); + QImage::Format headerFormat = reader.imageFormat(); + QSize headerSize = reader.size(); QImage image = reader.read(); QVERIFY(!image.isNull()); QCOMPARE(image.size(), size); + QCOMPARE(image.size(), headerSize); + QCOMPARE(image.format(), headerFormat); } void tst_qtiff::readCorruptImage_data() @@ -386,7 +392,8 @@ void tst_qtiff::readWriteNonDestructive_data() QTest::newRow("tiff indexed") << QImage::Format_Indexed8 << QImage::Format_Indexed8 << QImageIOHandler::TransformationMirror; QTest::newRow("tiff argb32pm") << QImage::Format_ARGB32_Premultiplied << QImage::Format_ARGB32_Premultiplied << QImageIOHandler::TransformationRotate90; QTest::newRow("tiff rgb32") << QImage::Format_RGB32 << QImage::Format_RGB32 << QImageIOHandler::TransformationRotate270; - QTest::newRow("tiff grayscale") << QImage::Format_Grayscale8 << QImage::Format_Grayscale8 << QImageIOHandler::TransformationFlip; + QTest::newRow("tiff grayscale8") << QImage::Format_Grayscale8 << QImage::Format_Grayscale8 << QImageIOHandler::TransformationFlip; + QTest::newRow("tiff grayscale16") << QImage::Format_Grayscale16 << QImage::Format_Grayscale16 << QImageIOHandler::TransformationMirror; QTest::newRow("tiff rgb64") << QImage::Format_RGBX64 << QImage::Format_RGBX64 << QImageIOHandler::TransformationNone; QTest::newRow("tiff rgba64") << QImage::Format_RGBA64 << QImage::Format_RGBA64 << QImageIOHandler::TransformationRotate90; QTest::newRow("tiff rgba64pm") << QImage::Format_RGBA64_Premultiplied << QImage::Format_RGBA64_Premultiplied << QImageIOHandler::TransformationNone; @@ -609,5 +616,16 @@ void tst_qtiff::readRgba64() QCOMPARE(image.format(), QImage::Format_RGBX64); } +void tst_qtiff::readGray16() +{ + QString path = prefix + QString("gray16.tiff"); + QImageReader reader(path); + QVERIFY(reader.canRead()); + QCOMPARE(reader.imageFormat(), QImage::Format_Grayscale16); + QImage image = reader.read(); + QVERIFY(!image.isNull()); + QCOMPARE(image.format(), QImage::Format_Grayscale16); +} + QTEST_MAIN(tst_qtiff) #include "tst_qtiff.moc" diff --git a/tests/shared/images/tiff.qrc b/tests/shared/images/tiff.qrc index 91bbf93b..e1ce9daf 100644 --- a/tests/shared/images/tiff.qrc +++ b/tests/shared/images/tiff.qrc @@ -51,5 +51,6 @@ <file>tiff/tiled_oddsize_mono.tiff</file> <file>tiff/oddsize_mono.tiff</file> <file>tiff/tiled_rgb.tiff</file> + <file>tiff/gray16.tiff</file> </qresource> </RCC> diff --git a/tests/shared/images/tiff/gray16.tiff b/tests/shared/images/tiff/gray16.tiff new file mode 100644 index 0000000000000000000000000000000000000000..aeeb0458d54972dd1a686052efddf68d8328866b GIT binary patch literal 8164 zcmYkA4^$LKy6F4QOi#~D_jJ!6hGG8EJs<)>925ybg53im5?6^~vqX*QL1WPM4=Nhg zO|m`1Fbs-hP-BQOz8>Vd=(;X?y)M_wdb<Z=SeG@Umo<dzb9<C^iOca#Sl35<JkOiE zH|L%0bE@mtRo_=tr@FqXufCNla{&|p03+}mPyk?({{DdQ-<e5%fZua|lA-UJOfvjE zE0c_T&;O;5gAnjaGM`B%J^0^k?o3t~{y`??AK3go^GW&C@8$P70l*3>|2~GYq^|9I zu1vE1A9S<7XDF##kn9&r=Btx3i;`?g^139y_7C#OB4A;|t{rbL+~nQ!_Pe{bZ&~=x z)~1EKo0cTg)*aq=-rlln&kk?X-i1wX?@hk<HYCM1FZ_E=vTW;?r2HQ5PYR0i|BC@2 zk!&y7#sAM&to%U@wJ5*9onH(rau*br6f7z!D4+`5C5440g)aiUV(e@Rn!_AgbL5z6 z9`GAx@tUKrwH>=5<&UlW^{vR}x_S1B>Yo3|zdUlFx?4Z*(AmYeUw;0fzLy-4{qD`} z1+F(oj#tw)!5!4iY(q{0+EtqRY33J_qj#11$i|ji;or#fH>yuX3(+Rg<b|t*TLXos z@Os$DT%Te)%sZ4m??+VAWIBYNknT{mJN56(8$0W^R+)}eXZ}H5d;1Of*yi4<zB|vb zQ~Tcf<kRK%FNQwYtxxwg?7m6`g0KFM@)d4+VD<h@&M(`B*l$)ful(%z&$+H~WUu+8 zu~ItQi@v2l_SNvmU8Z1FO;_iG&&k25tPJ!7{a4Elw{gAy4@{t+%0fQ?Z}G>)mD|#G zBX6>|`##FbT+;rDXK~$|r6|!0Xv)kj<4?+*8B=Rnd^o4cqYi*sOHXXdc{2ZKJ@C|d z+!t?}ms)?zv-sokpWH0c`{nA)gMMc90_}sG^M&D|nv+Yk@X8~F2H4%R$l{sx<e%`| zYR=KQs_I;h`(T1P+5_ISIY0&=4slZ@gn=1HG&XMK9_Uk%jzTb>(+eMFj>TSW>8)X2 zYE{<MZ$Q5oKatxOO)PhYhSI_X=@~iq3O|Cko-FRl=(_e|Y70uuZ*!4#R2!7WQ=dTS zm5ig%PsqG3k2Iap3jXV~{ZoIH^@We?<-o#}`foPo8+*PE<$?A6cXO2Yx7KCZ{KbLv zk0K-Wc2F9hbbJ)&x9Jb^v4i+=F}@L3h>`7z&nUVZJPBCrkcMIm>UMcNL8J+ZDW^Sy zKC{%aNeaAp>VBwuUW&hJbw;SZrfh!a{ZA6PsDEJQg%hskMHzsncqGHzLtSzi{l#P^ z>T$2j{LI5D(k;G`BB!#)a(f=^uXE1BAoi<6=$V>83OXM+fVORt_m{QJ>)wVS8QJy4 zK`*ztxwWFd%+#WsU9_gf|MGyw$gxw<hBGn(^DVq-hu_~{<bZt*bw9w^#%HgPu8V++ zSb^SN+_rpRrxCe6yKS{fQP;Ff6$d&FgeGS@52)vLAK$0W<PWmw&n@95HC=Z^tyaoG zvMmTSm9#x*2rde_EY16oX!gz@2II5aUqO0{8+IVAazjo~s@hf`TuyCV+xoeC!^&2k zU%j|RX52gA`6GpUVPG~J_tB!}ePD|P+064{r!K%}Z~s}4>ps7xb9j7ipH7yie$<)8 z+^Gphnj4f-co{+^EQeza^T8<(%1>s%vG9^;l}k-eXA+H_Hh<MeYS*!h7O~?65A}al z4@|a0!v2LG^b?f1>uh0mrh?tw$mvzq5V;-kiKme`X;+Cm%Op{w0yo>#;$|ZlYE;HM zQrr0&L;}8n<T(uC7HI+T3&{JPqMbr1hFSphbQs(Fdx>)1NkWRs&@C6Gpswq{SdpK9 zh``|pzG>FdPS#T_->!VXqy~g7OTqD5D0SJTZ4736$nQ{Ep!ryM8B}LhmuI>Ti6OU# z`U?u!orlBk!5&Lo_1FT!+<v^RubAK_6|nGBC%H?lT*3**>IhFmDIv-Q@T&&wc<8SP zP?O?IcvQ^DGe{1Wg1et7q<(D*kXZmo)7sY9?$ml}0eWj{URz(E({o;}_HLj7GGvWr z7KgmU=_<jk0EXAKfce;pfr4}@_D0LaY@)S+u>x~OSe_!<x>={s{*oe+Yp~b+JL=s| ze;D&yJeC~P+eCwj_mpf5Mx<gJ!8PUi<l`!#BOPme&mqlTLZJ$(n7^ru_7#!9NL34X z6I;RM>E%$Ck7tx$@TnGgv&k1pAPSz=a|3UwnCLuSJf?PGDIp=86=hD5{u+Z?xSZ9R z-Hy2IZ-v-FLQ$(wd5_uKn8pln#HsRKcDB2lHQ-dUN?1h^RGSL!USX0f+Y~j;dSKks z>(6rm@<Ep$RjY<p*%f5NVsIc`#TTX@=0C`UfHoA&qdG|cVh>t`(uK}|yHUXkA0o8H za=5>4zJz`V#e20dS%OPdpD6FYXF1roKOgE_roT_E_H`)=$_Lbhym`4IZZIgStVZt1 zDuS7%;qIF{xGBFqaRGvwooIZV4$8p-zr}@m@1~=%JqSDgM0LN}b(rt4L+($}9*axO zgj9Y@3}r_$w2997e#;lC{;6D;F2u{pQZ+NT15QBX@=7;Kt)d|^m>VT8s-hu#TmQBf zg>1Ku>)X4WECMC;u9C6XG~#;@n7EE+%q>t#SXM>V*Sgc$%ZOW;b-1C{z)roQ@s=zL zaNW4XErywqoT27g9IVUlWP0sU?u?p^Whv$3sAt|!0?{$5gX>gBtGd)Mlc5kp3!~H} zl+C0G;9$DHcpfTlyVJroV=DJzka@Bupyjk4^p7Ze<d4?I0PZKfst2R?PH~$-3Y}Jp z)5cbJnZuXuN6Mc#qTD%RIkl9a=9(1!tF5isdu<*7))d4RL_=2$_eUIu=xw=B-C=Ef z&>Up;Ta<kVH1UxiB77hfsHwI27^Sk{hUtq?m4$nf0?Yk+U}~k6&8|>L+YH^7qi9Pp zt>XN79^lx|y}-LI!e+cYd$9!=+W|{vlC5JkAX*xr>&RfA2V>EyfF2?IQzgJlg%+*+ zx<#V4!u}EChmCs>h?G=4wKhM$-2}>Cp|0*6HGkU}I9sR5DBj)C_$UuX1;Ag`5F$0{ zKFa|t(qF9-PT2kEJKA_7&|0(B^y|<~O?jQGQ{Hd&B~GA`-puwyKMIMLFnVq=Mx9W4 z9G9HTV~2WVOG^3FyBctM9ReuciK6?^O_}ps$P53ba<9R?g^EDU1kr{TTc|N@3vgRg zK~ZVEw^%`OFR-ymM6O9w#Wu%5u2k*AR<_+L%vFXjsOrxzZST60etl$bdSno7b2n0u z*g^DI90-5H0$bil1#7Ze_4&qV)f$C2yRap(O&1)0S;bbkRlacw2{l^i#EHYZ?K&2K zxB;!OUkz{$Agq^2D>tj4CLF?`Q9fuCLaD%c8}}%kdTN$u6wHi{c?1DB(?mfd2N15= zz-@+s8jD<-F6Wy1>tU%u>HR=C71dGe%*iTd8_Jzgfz>LIgTdtTo5h6*oqW^Aj2fsI z#*eFjspJB4$}<Fi#Vquz@lMSy4?vyN^2e22$VTml_~Q!Tse(4>eJJe91L*@exXLL$ z(Fl{N-V$TH1deMo%rq89Q(4F$kEZjZChkTRCF<C5l9?sr2S{%g$&ZuX5XnxGK+>iP zwUH+pKtO+Sf&j`4T%MZGL>|?^kwuC~lR|VU!C58qD-{qWz$%-!ES;OxGO-kX(9B&W z<!g{UYy@tk%QubGh>05n;#Po#QX~xsJjA(iwJ?&x_mxl$xL`1X8`CDU&G8@+Ii~}| zsjNRGav;@5+Qfkr?zY;C*y&PdBu_0*>8UXXmB9GtDL$PU%tPaOR?v*GX658seR5Zb z43oa+gm*(Ff6c;QPzj+ls@BTor}CK^Cd<j^lX9_*_ZWGPld4E#s#1jBRM4DCpjiB& zR=S`CO&XcfQunm%fL5Hwcmb2bT1hs8N$Z_3GP#EMb_1|lPb)~?MY6io)({ps1G9}P zALo#IoxW0xrcxsuCN0nPS}fA)6#kBpuh9!r1Q61B-GtmrRy=pmixA1JrIRLTAiQP^ zOQI60^C`6aV<#U=XQF^O0*k{A?zoexGVpmiAWy*;Ie=niXbk4_Vg4cP+m3jhI8}`a zZjHQCBUftT{Tj)s5py+i2c|peVD5s#ZQOg(7RgkDq&jZa`m%6uCM+p%?@k!(*7^49 zy+w%E;`HXLBWuxrn#E50F>VB=YY;jg;O<(4351zQ_aeFoghg~v+-{WYX0H>ao<qQ6 zQk-$}Gb(1*9s%^+Qvi7AkncdiSUT`1g?R>wlTOi(3uS3Sk&<DQe3^nTP3H^Eay~5Q zrvs}}#6cT3><|PEFIdDgPT+!*zl_LH19w{~PJqHaK%P+1$LtcR01=xHb;j+flFP=H z5K`C>sU@Y%6jo!9pIUzV2=_*{@-wX{YQakyV5e4S#v&UC-k-bylG{R{SEHAb-t!zF zPi<uPck=fvjO<{hHPmb>^^~NhjNF)ky8$s1i1^$nDV*GMEq~s^R5^jtgWx4N@)Y)7 zg5oa7Yh5oE=~_FqV5f$e(y)V?hjJ=&+aSctnGzjXfyZCp$JVArN{y0+lu#9`GqO4x z&6%a9^mw+}s{#3^X~LL=8+LL72H~1Q7*vP?%=G|VA4WaVND76dGEh$maTJ#_F`*Ij z`f;{~0GmnR3`wK9(+AYTw4Itvk?-g!QOjSqQj_*Lgdfc!feV_*phB2|r3tw890ug= zjXwdU3lI>5yq!u0)v#AJ@(Hbwhf@h$+@b?JbzWJ=pVYCGj$Ms!HlysG@2FXWP){-f z5qFyy#Kc|Dd%H=hngB;}ei)}GFunxiLKwA89e=3tPT)WTS-Hyj=ZCrn=YP*eF}W8n zya)Rf>bTNPzemvb@%RiLnZvz5CHWC6AS2u?%A(pkJP|40BY12=p_Or~s9b_^fuh|a znADWn$yorg1`^Muaf2%08pMnn897~?&~TIbw);A1L?_yHbTvk=!>Iwxw?Rj_3I1Ub z^USzsfD}7OFsSQ`LGm3?ybbcB>2lOa^%LB2Sp3rt-a^FB>AW>ME?>u-!Nn6eHHdo~ zh^Jq_m$YR`L2}bN@lJ|3?qKd{_z4>~V~GPM8q)X_sZ0Xk_UpyMG%@$0cM1ZJfkFc0 z2S9oX1WAxic4t6@KPiMOM((;pxTh5FVZx+MnAY<%PT&d3KQ+piF!{Pwew^-wpuc4i z)IpLuN3!-s5~KqXI(gE`+)FxY7Up&uH=HV5)rfsKpGbx&VEGZrOhDp21ry)NF*qH- z1d@1h*a0eRal6S&rVFx`ku}Vu5qOkFYtRTf`1&}`KT<Qh3w_5RW;+Dz2Sp7iWbApT z7o@U5<~kVp00xgE@tB6bp_S_JnJ4MoLzsK47iaa%bDa<J#sOS<q?0^Eq?w>JM4Tk) zagysL>0=}(b^wpHV+%XD2@<&BV6H&iWn6r!7iJy6U5uH)ssDVN+lh-jPH)D%tMQMA zFGMyQ_{aLl>v;SsE?m)ZqlARB5@`aS+2epp93!X=S|;1TP)6B~h*mvg0eG`vBmnYt zpqN}yB})2+iW*nb_q1&ZEC19aJk@fuPG-(bL#aLm4D_b(H}%}KIysZf(-iKzqw*F) z+DL*^q}NGuM@R}Inbm|aN&rnbcS9?eXr)X-x<CMSoLT;{eAmcLsHq979CJ`ny&O{? zzeBPUB<Ix2CQ{z0qo3-SNnQLo$qee*+azB?fcJGwt(q2L-~hy)0!1q*^n<=tpsyG7 zWr0F7%<*bThqDL)W|Op=NFJkLNJl3KHj4y{Nah!-<0L3)V5*CkoD<AFD?e?tYIM{# zT-rl`Bg9wJ+K3`Oxob(tjQ0}meWI1uV)Pk8T%_|cIKbe{J`HzQD~|uq$RdpC!08#X zXk6t(oDqY`i>ZL;1ob>k_&;kHj%2FdlqbK8^eO~1L|p*|4;UfA_~^GlnNncXz?Qpg zjF9$|2glM_EZqkn)QnNQ193OgDVbzuEFhH5q7Hcm<vLZ|DTO>@6DLffY!~i1g=^`+ zHHTt5L4`=Bhh$B9pOuX32(A`q)?&JT8#S|1F>V)bSoxT>q7i3II32~<B203VmU^uZ z(bHrqt3xHk04j_us*9rlKZgQ;`Iq=fbtDKgmqB?P^wxoL^ZQf)5N{&PH1X*}LbybT zb;OAadf=P=9EXcdSfpE<{H@+>Ol~AV7cSq|`tEDEVfC6Dh!|4{<D@+FQ*eVuT#Zp3 zI-j4U1d>f|bWcg9n+WVTFxMbJbPA8O;snIq0h!+|W@@o#yi>es7p|N6Q5!#Jl%Jb@ zm@Ps&;yQhVgncHnmvYM`<o!C85VLVtQ>bBp>QD<Wf60%dzFORSPtR2vxN(x|)q&l( zFpKj=IQ#pJz;GHDRmj&(+^9-?r~oyo5xbF@hN*+8@}M0!ktTG4)VYm<9RmwB!efp2 zx>mTY<Now(R{4Q=on#<F5V80#Bx;c0?^}Rzn7@-o-7xVYDvoIRQL}sl+JoVA5cgpu zbyp7_A-#~E;S3<JmsacRJvIq-a!*lVQjtj+lIG0hVnDT8MzlgEE_q0BEx{!x5WuNv zOx%yLmuJK%w#4?9{N2{15yiLb>0&*=<H9;b+=z3vT3{BH*Qu=|5OrN6UQz((&D`Zw z@!?;td>!L%kW7@|&tdW%3@p~lr0$<b)0i=Y8r2J;ii*O(_1{bXZj&c)?xD_`X9k@X zulg*Nt)~}}-b+MWCgOucTp;<Uq|mKzX*5PG#-+8GPsY667_Gpnj^o@-l)7&ar=3DV zFVCdPPY7mC6-QLbn>{cM@i$Gv4TCu0WG-VoXOm|@K}3L4kkCnr$25GuR=i~A&Kdb% zeJp1D@l6E(@ioGmzy?XH=NbWqb>2hW;;1g^zVcC%7<CBOAz+-+9v%6;`R!MJ)fE^s zg3zvI`Cn!kZ~X?i%3dXZWukUtFI1c+|MXY?svC$q_t2J}i2Y2ckRrBUhN-!?*ehMH zJb^>^-dv<qcY^(PVRxZZ^J9tYLe9T*4U^WE_lCdCqN`6I`RB<quPqqRahLTkD|WD| zFW)w16khwG`|5`|L%-|4{l6Deqm_R}=c2Ov7prgd(vHkuR`@?W568G?+g@IB^Of?S z3{C@OtwiNfuQ>N>)>O0V=;^VWXg;zk`+;j*G`#piXMFLwy1NI;fBqUZ+>klZ=eAYz z_kVo%#*HPv`fb*?X`fK$Dx-b{++shzdY+>iUKER+znx?I-HMFgrYJVD83gtk+I=<b z{^goo<?`?tu=wTC`A2QF(b*-fz3PLNxS|<lKg#$f_A-?54YEx0;8yiZpJnuCE;F6( z&s&_jX?HVov_gMs*@v*$y`=p#SyG_9|F3g7eperrN1o<}D?1c@Pdut2E^DzWL%(tH zXYk1WV#xblOy{U@AMscM<oRU^|E)yEvHCZjFK~FiyOw>}|B0~JziHOQ5T~0H`KbR^ z>7vgb%vLW`4;3GGTRr0)`5;`-x6B!!7p?5bta7m*D=4ZQIXzWYW^MTcH9yR6H60-8 zEq7FS{9Cd@-=ij*!<&6TqpmzVTCGV&lvq>{E;V-1U*{Es1@hibJSyI+!cQk=env{- z)RGVk6m9HMeW%DLD+iurYd6<)&TEG`AqNv0&hKk)9XSu^u9MU24^1{)F6p3|xi?6W zn<Wli@4is1Uf$U8v1QXA%1lQ+-?<^9|15K$D_%1LbPmPtIB^%5_$f*EJ^evvulVS? zH2^;HScXYqCQJ_`fWsO6gX+VdcP9#tc)ram{!mIx?$SxrqYXMShqUR3v+v`f%<eKG z9LUMlhoS8IYdby<kCdXrLdRAui(cEIMncEfL;uu${ilcKxgWnkMz)&X*O&K?l^q1f zJBw5m<Kf*MV%3v^BlYctl_^`k+gX_6N(0=TVBI4ETW;ycF|ck-g$HV`FX^DhN16|- z8e~w{J201#x0x&|JDQ2^@dSIb^UCqzy4#iTyWvL_ot2@PHAkphc2$Q5Z2<j4>Rj*f z6>k(SJ%&iul+O7~^gt*ec9PoRzJmu)usFD{9pDG|>cN-tG7UfPGI$R~LdPuV7AE$R zo@bsIx@bDH_K5#%4R?_1prYhPlO&d^=nhMH)p|^7+|zooBxOjfMJ3)9{K_y`m2_O# zOcrFJVg-Pbo9?dk`L-!PA77V3-Cc9Az*^Wc{(GEhT#Ce9aG)ybl;+`~gsZT8YFbb8 z8{lv2;6NgziQmmrO1eT%|5-JNrnl6*w~URgQpr>vdJ%FASB;aN;i8b-Yw$BA>P@3d zaXpX=4<LGYgdx7wxC5b(?(~Kil+;OZs2IWJxn$UA#0^_M!#15SXcul|4K)nw>W7yi z;_M3*mI~5a#ei{zIY2%neU?`d`dG>J{<kdk<8QPU9B?kL>L#c}F@MqIf=A9H55_cY zHLjeYr0aZtc0Kg%drJfUKbyUq*&yh*E@$4iviz=qtTC*Rs|}IH<*l*mlofqzVc%w> zl1vx^HPuRA)fQ#H#a5pEuF==Oq%8r_p3G#3wwqQk7cvhGY)O;CRFFSn=cy)jd~msn zvN{lSpE@$NKy`7g6KVLF8p6Dx0!v1#*n`#2RUhVO3!$k|GhDk|>!s{$n1dptD?bj` z<^bV8L$2u}q^lGOW*@hl#;SZ>8lt6dJ>GMf0d_N*;p}e+xvsR;2N(m5B{-09sbVMR z4KYy+CUZI)yL0_qw#Fso1LCL_F8*I6)Xez8{Q%_tr7Aj`d6>FdSWX47`kF;JAX7sl z|DuC(HX^t3Z7t!whG0!e4>0Zm_znmPyrYr&l0iM$`Jkz~<JPEKls`c{RC+rY%dBkn zrAIp8K=LRqx9kU8Trt{JX{!*otHD7f5}vaLEQgUEhD2*HiXYjR<9qT1p^M2Nx+=vx zP>%EmQ<>t|(pa}s(f@&|yqQzRdtGgjr;c*V4?)_#KA@LLao|TLDe!(!ypmZ-_B&nS zZK0YzCttN5-CI`}?7xf;h27+)?DF<5T~Vv$teFXHYW0dm!Tzz-NZ~qN#Pc;dU^aRh zD-Z8h7~p~V+9lzNAX93Mj_gPS&(|Xtdu)~DE{AWpI>ZJkKFp`H$1tS;q(r$YEUq9F z0&0b`w~-)kYZc5UPiSSTRJtCar)>VlW{4duQ?h;o6fRfM<om7Ed51C_MINmB0DE;} z9x!sy;YSy=H9{1U*lF=*8<4ILRWaZ<-3qLRcQ@rCp=CCD?rja|r<CH~45u5}R`*nC z3-zT|Bo{(4G21_@$2X0?2*zYJ+_<?79Q;8WIc+ZwHA0a`s{#4(3!cz_s>(S#?+%(E z_seQ9FTb4|hNJQywd`naCx5CG>i!OUbaJ`Meb8Fb_j($b_hw)>Vr1&JsxnN(VenCY zCHfn%C$`|=?ri#taM00Ht5w&89V;4MgC}cBLNDFUX2*Z0Y}vjBlg}+^srsd=XH{O% zU14IolYux(X@E8B+uW}~zOx?%!gq7a8^6MbL(5cPw>{XsM+FY=YvV4Zm-oL*NGCT2 z*a5PI>oV5cD-gk%y1ajrn&#e8z8X#O4b%bj_R6<K*$r1!Bn>1_>pt}&6n>2WHoT(# z_89Pa-@4RIrq!)dUSWkWkM!1+;d(S}xGtz~DJ;`A&RK`1{$|LCt;f9m3xlyzvnz8s z@;h^?gsw2rK!$?N(u=)2v_Nl>hIzJdsCf(CQ&Eh?CN)FiK2nqI2}Mq3GTogH4^STb zwsG5V@x_$L-5+5!&)q}KFXK>cQESa*Fxu$QY_jAfZwhu8E!OsQ=V^oig+F#f(^a)m z<vW$_X%qq1aMi&Flf=+SCLv#5nsk|q;O=gt*!L<dukevC`-_*hWwSu@dZc($ps${u mT>*zRaA+kIE(NDbz{KnRz7JXeIvMM1>2|^LOMzmZ2mT)fv@N9o literal 0 HcmV?d00001 -- GitLab