From da55b84ed813b90200e25e5c44ba2c6a5d74f399 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Tue, 10 Aug 2021 17:43:28 +0900 Subject: [PATCH] tvg_saver: optimize tvg format. So this optimization stragtegy is to merging shapes. If two shapes have the same layer, having save properties except the paths, we can integrate two shapes to one, this helps to build up a simpler scene-tree, reduce the runtime memory, helps for faster processing for rendering. As far as I checked tiger.svg, it removes 142 shape nodes, decreased the binary size: 60537 -> 54568. Overall, avg 4% binary size can be reduced among our example svgs by this patch. --- src/examples/images/test.tvg | Bin 302006 -> 296037 bytes src/savers/tvg/tvgTvgSaver.cpp | 87 ++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/examples/images/test.tvg b/src/examples/images/test.tvg index 873daee9ea975946f322bbe6728473fb91890a64..fe0a79d2d0961434d35ceb0e79d9db405d6aeffa 100644 GIT binary patch delta 2413 zcmb`Idr;L?5XbNDoCU=PS1>@tNFH5NLBJxln&}EwnoMvlP;^XuqvD$+3k3~C$wx%8 zIAt-;m@}sG5c`cyV>I>$HZ)c*EZPuO9A*@UsUu<9ePGz`Q#RY(zQCPeo6Y7701WT5Ap83yzy0mW zcM^%+bviDI$Yy&6v#*PW*80K0GY{$vZa=8gc*`MRNHtI`NU8x&39@T}+k)+NK*V94 zX4W6plcJuutZlYr#9(V`YU*&Fg)|yM?mtGuS@k;DP#ktzyLjtqIQ&D9a|SpEm{~vS zXg@VZ@w3f3F4|YwY~gSyY5rY`+;9;|Wc~~f*8~O4Kwt~7w?(@QH($7<;|J8hLvK6} z-Sfncx9V5S6Rn8GSZAsDr(jCb5j8{ekjpxf*I(BA)e*yrCaeg1A@;GB0xIq)x%}|L zE4r8yuIfVb)>WNhsM{W!$gg2D82X7`o4@42rPp*0kNXt?Hwk842Y$P*UBd{rR)Jeu z>N=e5@ZgVUh>eYr zw>+4&iaIo(@ueH)FTJS9H~y-3Rcl+%L6Q~6$f__ADAldkG;j8&j0!ttaiN{k_@X@zgaQU+-@Mm}(8z=h(tQ53{?2hgK@F_t3C$N-A)F$;sqPdjG!@wV0~ zM~CxeNULG`v}I~VZh_sg4AlPQ~FeiBMShG~RRw%+%CI4SewP>NR^9Yu|1 z|7hCVOHDP{eMv{~OEKg>$N}F{-AFu0(esZVzA=)X;w3ROmRn*d&CH9XNrADqpxu?) zB}gH#8|Sg@Yzt4MiqAbuIcBbtzUVb*U|@GcA4uQ8$f4M^n8KwFfs#s)Acm`=>0$mU zogzX}uuiX5me&5E0n9lQsX&?b$y8;S=Tqog!yK4OHNNKEbjns-GL5FOkx7NTFOwR$ zbUN({N$?)q4*WTzAVndBSyqc!m76?+B9&P_lcrLEt4BX2Lz)5Q54?$58A?D_FaBRB ztqMqP%nY7TTE(Gr=m)mXB_}TzoD{^(19k}71gZ0ZYJq(Luv~Cb5VsK6A!rk%W&_pP zG(Nlm;aj`6%Yj2u2MG<{R&K=s8!2v74X&-npqYjT#p!lh?kDnyWmxd|G8~?a<-ip|{tCdp64(QniK}R; z;ab)so-9aXvBJE2t9aV&fp&vyR+F1+b5WUq*MOCR?*s{JfHJ{Nfh&)kW^EpQX{b%T ztt1Q5aiG^>>EHiVlJ`seKfNOoKva0HRMJP$?L>0XQabZ3LPHd2a!Iih$jM_+sF=AY&77MUY=YuF>^9Zh?;>$+s%r zQ;+)|25v2(=Q(9FDtbWBZwrtkXxc*Kr@Y$Nd+o^=H4RcFoMpgqXBWvPTT+if=ey3S zdrESV3{3I7?Q+sJ@_Os0VOe&QyOia#qj+Q~N?BD(HJtl4@Zwgwz$x2s#oXLRjeK@H zWt%H@;5y=jGJFY+mZ4h1-vxFF`jt~9e^riA;(M4qCU|ZqF4qrtV&tg6M1{bYfi+AC zs@BHgpX(9BQHTy_A&9oKZfZjrxStK(Jhw4viZOUXdfoY?>3arG;3q3-C|3YxdnGlf FKLN7T^ql|z delta 4416 zcma)9eM}Tr5Z~Q53aChdg505Ud>ey7YEz9#d&@^L#weUxv?`)f^t2#W@G~l0)z~z) zm^5{2ZMCTysMU&^x{3XxUi^o)q*`tLqX_|23K%O*+SF8Cud{Dixbq1t$s=!m^P73| zX5YM<*Y#e^B`xNVLpgHPm$*Ms-k+#^o5(uPWInhrNl#bMKRsTOBsH4;$<|owy^$Aj z`rmy0LkUyQvTSs2fN1>mEGuTA&^-~F{f)qSUxa=ap{eHt!<{1ZqX;>h1;Y&@bXkD3 zG3VLbRBV34lJNMitQgNdVlM0(n7Mzmu!ezIJIyBPZbg#vfwI-w+8R)ENOF*nXQWR- z|9KWKOIr3VmKLE<0w4Urvd5N^ZWN6%l7vm|tc2&$&_oAmB$+WL;nVj1{S2XB&@sIJ z*a-RuBj{i2WFGkgJk-gik6S@88yls#CA{9r7Av}`c_CP1#I2>>rhUuHO}nYYr?hZe z+;NAcD7vW`AFPoX9=*d(V{RKOz{7W0J*sW&b&T&~^-OOxQx~(9+>E9XNyAu(N!sBq zRw66?gz=#Sba%7L;nMqc_wea^Jc52+&+zs~dziVzjK(-)3kq#P z!Ps85I#T~Uh=!ZmSe#bd%MQjQ%%+@kbJ7RYERwQlRwv=6beN_sd&2%>Q}kG>*Fn-K zl8h8hroq!>tV1hFA1{KxBK1oITV!0xAYRLtVc%%;^y^_zoT3lJ%ujVl%;ZO*lQ};e zR+&|DHXMra-9DC#dN_DZtRezD30vrtTk8bWbtJiI<&zTr909pn&KPiwa_A}xCcQ*b z3XxXx?WI!fc%mSJrOdGQ*Sa|)pr-fl&tUTX(dM+$PP zE3yFnsk2Bj%=7s!9NH5PIFv|TwlYE-tSkYC7PlCd0In*5bnILT9WH%&NGP;3{u2^z zhN4L5e-qS^S@JSFtXx|xpDmW(7E4}kD=u3spDmW(7E4}XD=u3spDmW(7E3O*6_+iR zuM~0}Z(GOfNCw>qL462zltPJ1A5zb63<+OZ371E>By!5=lEAVO$iS~wL#NRW9b5zb z{bb)7F#E|t$XzZ#4I-}$$lwU2l`bdQCh|r8pg5h2fx+y}b?+Au9B6L-RVmAwhDR2glJ~}bk|Jot)3CZH z3D0eXW_!%CZP1L>6?7{`ZHJC=gU^80R;+Gg@z}Q=>Lc}@phZ!>#}cr52eg=N_spH( z4eGvlSYr2ed?$5Zrh$8x)u%V^qTZdqbsrAQ=Ikb{>IO?i+zp?Y+Uz}6k6*WkYfill() || to->fill()) return false; + + r = g = b = a = r2 = g2 = b2 = a2 = 0; + + from->fillColor(&r, &g, &b, &a); + to->fillColor(&r2, &g2, &b2, &a2); + + if (r != r2 || g != g2 || b != b2 || a != a2) return false; + + //composition + if (from->composite(nullptr) != CompositeMethod::None) return false; + if (to->composite(nullptr) != CompositeMethod::None) return false; + + //opacity + if (from->opacity() != to->opacity()) return false; + + //transform + auto t1 = from->transform(); + auto t2 = to->transform(); + + if (fabs(t1.e11 - t2.e11) > FLT_EPSILON || fabs(t1.e12 - t2.e12) > FLT_EPSILON || fabs(t1.e13 - t2.e13) > FLT_EPSILON || + fabs(t1.e21 - t2.e21) > FLT_EPSILON || fabs(t1.e22 - t2.e22) > FLT_EPSILON || fabs(t1.e23 - t2.e23) > FLT_EPSILON || + fabs(t1.e31 - t2.e31) > FLT_EPSILON || fabs(t1.e32 - t2.e32) > FLT_EPSILON || fabs(t1.e33 - t2.e33) > FLT_EPSILON) { + return false; + } + + //stroke + r = g = b = a = r2 = g2 = b2 = a2 = 0; + + from->strokeColor(&r, &g, &b, &a); + to->strokeColor(&r2, &g2, &b2, &a2); + + if (r != r2 || g != g2 || b != b2 || a != a2) return false; + + if (fabs(from->strokeWidth() - to->strokeWidth()) > FLT_EPSILON) return false; + if (from->strokeCap() != to->strokeCap()) return false; + if (from->strokeJoin() != to->strokeJoin()) return false; + if (from->strokeDash(nullptr) > 0 || to->strokeDash(nullptr) > 0) return false; + if (from->strokeFill() || to->strokeFill()) return false; + + //fill rule + if (from->fillRule() != to->fillRule()) return false; + + //Good, identical shapes, we can merge them. + const PathCommand* cmds = nullptr; + auto cmdCnt = from->pathCommands(&cmds); + + const Point* pts = nullptr; + auto ptsCnt = from->pathCoords(&pts); + + to->appendPath(cmds, cmdCnt, pts, ptsCnt); + + return true; +} + + bool TvgSaver::flushTo(const std::string& path) { FILE* fp = fopen(path.c_str(), "w+"); @@ -477,8 +540,28 @@ TvgBinCounter TvgSaver::serializeChildren(Iterator* it, const Matrix* transform) { TvgBinCounter cnt = 0; - while (auto p = it->next()) { - cnt += serialize(p, transform); + //Merging shapes. the result is written in the children. + Array children; + children.reserve(it->count()); + children.push(it->next()); + + while (auto child = it->next()) { + if (child->id() == TVG_CLASS_ID_SHAPE) { + //only dosable if the previous child is a shape. + auto target = children.ptr() - 1; + if ((*target)->id() == TVG_CLASS_ID_SHAPE) { + if (_merge((Shape*)child, (Shape*)*target)) { + continue; + } + } + } + children.push(child); + } + + //Serialize merged children. + auto child = children.data; + for (uint32_t i = 0; i < children.count; ++i, ++child) { + cnt += serialize(*child, transform); } return cnt;