From 81d46600fbfd505bff2f73a584b9c59b7c5759b2 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Mon, 6 Oct 2025 13:29:27 -0400 Subject: [PATCH 1/3] Optimize PDF path algorithm by using RLE --- QRCoder/PdfByteQRCode.cs | 26 ++++++++++++++---- ...der_pdfbyte_qrcode_blackwhite.approved.pdf | Bin 4537 -> 2478 bytes ...n_render_pdfbyte_qrcode_color.approved.pdf | Bin 4537 -> 2478 bytes ...der_pdfbyte_qrcode_custom_dpi.approved.pdf | Bin 4537 -> 2478 bytes ...er_pdfbyte_qrcode_from_helper.approved.pdf | Bin 4540 -> 2481 bytes ..._pdfbyte_qrcode_from_helper_2.approved.pdf | Bin 4537 -> 2478 bytes 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/QRCoder/PdfByteQRCode.cs b/QRCoder/PdfByteQRCode.cs index d534051e..62f9b5a9 100644 --- a/QRCoder/PdfByteQRCode.cs +++ b/QRCoder/PdfByteQRCode.cs @@ -174,7 +174,7 @@ public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string li /// /// Creates a PDF path with rectangles for all dark modules in the QR code. - /// Each dark module becomes a 1x1 rectangle in the path. + /// Uses Run-Length Encoding (RLE) to combine adjoining dark modules in each row into single rectangles. /// /// PDF path commands as a string. private string CreatePathFromModules() @@ -185,14 +185,28 @@ private string CreatePathFromModules() for (int y = 0; y < size; y++) { - for (int x = 0; x < size; x++) + int x = 0; + while (x < size) { - if (matrix[y][x]) + // Skip light modules + if (!matrix[y][x]) { - // Create a 1x1 rectangle for each dark module using the 're' (rectangle) operator - // Format: x y width height re - pathCommands.Append(ToStr(x) + " " + ToStr(y) + " 1 1 re\r\n"); + x++; + continue; } + + // Found a dark module - find the run length + int startX = x; + int runLength = 0; + while (x < size && matrix[y][x]) + { + runLength++; + x++; + } + + // Create a single rectangle for the entire run of dark modules + // Format: x y width height re + pathCommands.Append(ToStr(startX) + " " + ToStr(y) + " " + ToStr(runLength) + " 1 re\r\n"); } } diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_blackwhite.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_blackwhite.approved.pdf index 0913aec724bc12e5dd79355dc827c659d2ec1655..fde095c8847342aa957b7e99e21625459c26814e 100644 GIT binary patch delta 841 zcmYjPy)pzr5U#yDKeu)lCE%!1gWD?!c6)Yj_exO$R0)(oMD7J-BoYsRP6Pr^Af^Of zL2x{Sz$;L`zMYvZU;`FYF}{;_StRr5%%8fEn^*a+DE9poVOyumr)1nYt-skDx~JN4`62%Y=Vb;Jb4N=uagv+ lIf`a{yfM`ZjbSJ&A@F%>C=k{1_-3Xbe9ATXalCi5{|D;Nv%~-Z literal 4537 zcmY+I&2Ae<5QOjh6m!uffJJh-{1G7d5GB4OK@>SRLkC7$D++8ep=iL#OXiR_NM0mg zlT=liGK{L}?&_Kz?hx3s>u=t?nw>AYvw#2l`=4&srfvJnZ{6i(H~IO`-^Vt&+Uz$U zwh!Ipdh;+ox1NNX?)B?#e7dj1;x7eP+fPsXHtQzeJ>EaJcg-SITME08grmgdhw=V# z^Y!*~dpAX6b#dMw{=4laH{@Y-%0%_mper#)KFZGQ68 zZo^b99!<5$_v6#U{?|4P%T-lA?|0+oWA{hbpATeB+p8lu^uPPa0P1h|&{b?cuYbF- zdw-zQS|8eQOc}d{;nHwrxHh~noJ|AwfkS#0f_>!ok>Af<+ed95zWUS&XMI>3+TvwL zSTmjn&YVT8FO9LLTI7X?wtSIa8m87lFeKew8z{ zmHi5?yt+2TuQKp!JP#a#9IJCe67OEWFy`pB&Yp}zljHV!=(8uS4{hx|39h`lHe^rA zU{A)2z{|iF#`sk}evQKbe!U(#_|^K*E?)d%$2iv)UZ<^cXluMS=Bb^&X!6|@=G=vu zmoQ$05fmOUl_~6QkM5y?J$dmZ8NuYnx5#$6W# zJC0ySgz6wjJA$-H){w=hk8KbZra+i{VRkIcl!a@`B4xycotR`l&NU+0g`XWqDB-(x zngq%wjK$=OH0m9}r$|{d$(nNGND7d}_xWgmxd`J}n0(=yg-KR(J2_HTl*aJ{VPuZ5 zOOsTqDJepL$3!NDF)2*GaG5lrp-D>d@J0mtPD{12uLwsMB-L1sNUCuwMI@!E*PTv< zFa^To3)jX>uwp`YldP$uP^9deWc#x3NE#qZw|yBfLCl1sH2Iaz`_+5i9m diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_color.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_color.approved.pdf index 36bb806bd89962e8fc56a2601a75a093f639c054..54874db39c3984672824cd48e1392d66be1cc5d9 100644 GIT binary patch delta 841 zcmYjPy)pzr5U#yDKeu)lCE%!1gWD?!c6)Yj_exO$R0)(oMD7J-BoYsRP6Pr^Af^Of zL2x{Sz$;L`zMYvZU;`FYF}{;_StRr5%%8fEn^*a+DE9poVOyumr)1nYt-skDx~JN4`62%Y=Vb;Jb4N=uagv+ lIf`a{yfM`ZjbSJ&A@F%>C=k{1_-3Xbe9ATXalCi5{|D;Nv%~-Z literal 4537 zcmY+I&2Ae<5QOjh6m!uffJJh-{1G7d5GB4OK@>YTLkC7$DGF>cp=iL#OXiR_NM0mg zlT=li5QM7f?&_Kz&Jx(O>u=t?nw>AYvw#2l`=4&srfvJnZ{6i(H~IO`-^Vt&+Uz$U zwh!Ipdh;+ox1NNz-Rsxg_;go^#a{}pwx6E%ZPrb`d%SyY@0&%cwiI?F2}g;^598hA z=Iia}_I`@S>f*dV{JZHUZ^!5Dr`;{xE-%~UdbhoOH}27!;I-W}noq8_PkXeU+x+CG z-Gr%HJeq2g@5iTy{jY5pmaD3K-tWfE$L^1=KOe}NwpT}R=zsgMtEE}nJsiM%-uT;% z-Th%O(^?MnU@X(eo@=L>&;o6Y?N}lzB=Yd0T7BaKY!HaYljqxIIxWTV- zhPJX_!If9nhWJ$mevRjWLy%*2PDtY2>lelxz1G>2acFYfUJrftr1hb#y(htySJ#H@ zNg3?PcoBFR_`(>!%Ezy97{IUBLkGWFAKJx>U+fs?`oin9RSs>9*T&r1y4k0TYs|T8 zop}l4MHoThLMDBZY{5xKT8d%T5!?ouCrLGC7HKKnr)Ng!v>5`m-@}f}jtBwTY81lc z2qP<8nkFEVl$v4E5v+*RmQCnvk^;3$N77AgOc6>Llt-hb8o#5<1D`-0Qkj?cUC zo;L$SH`S*t7mN9#v3&aQdwW3bNlPnUdDO>CJ?|A>miD{NZU;`FYF}{;_StRr5%%8fEn^*a+DE9poVOyumr)1nYt-skDx~JN4`62%Y=Vb;Jb4N=uagv+ lIf`a{yfM`ZjbSJ&A@F%>C=k{1_-3Xbe9ATXalCi5{|D;Nv%~-Z literal 4537 zcmY+I&2D2=5QO*lDb6MvBx3veUy5cyU^ajVnc0o9Fv*<|G!uj4kp^Cp1#bW^!q@(( zs&S;K>U4K?oj$(D(!IF(_Vw43%XxS4?|*;)(@omAZGZW#ySnN|Z~y##Xrt@Re)D1b z*o|&BkHbsrNx1D^z3PVNhe|AdF1X%)dfvB5H~RkR;ibK67OC1&*bO8cB}P9E4^NwK zwx8R(F&fkL<>K(~t{dGBFWXPMd%9g+wb9LPd;ezGqc_59yK6KbU2mWFXuY)Q$xpir zQ?+5^1SD!lJtPg9mHb3tO zYsS;SnKO^|g)!Dti@flxEza@_!=>TMkp4=Z^ns^=LvRu@lhDD7bQq2CJaD+duX1K> zX}^L?udWR7s|@@aPXmV_$LgGr#Jktm#vHxY*^_Z-a@<}IefFgFSzCEef=jQi4B3-1 z*pu-*@FMWq7{AKLuW=Z_uh&BdzgnNQ_1Q0WjB|bNb=oS2w#F-Cp4#zQlkcW5=Pt~= zgz+Mbpl~6RK1sIVq$4fGFzX0zgG`g88Z(QulW+OleTI-8_G?b4BSQyWu+5(Xvv!dMn2UzpPqt|6EpX_B%v zE=N+VJSxKNYC-^sqb=5ju_cU>Ft&ti!Y1gOfK0MSc~qn(Y=R4uRO2p;NUGrydUTI7 z;TpRMS|(_jfJ|^^lFE~mF3(Ei$1F&y)sU4&PpF10OcP;x2-jduC^Vs=N$OTNM^d@I zx{B1JYmx%xSP{gKkYizhFph=m!7<55%}bH$Zo*znuwnu-NojmRkG|+4ToW@P!GxA3 zv@~J2CiD}jV>QX1b#Wj;m34dl2r?z$k@ zaRfUeR0l!Y5u{DBhAc*XY=f{c1;XSDvtwbVEL>9-DI+HA#3cK1t`W&D{OmYF3E!>L zBv3YCEGA#1QSS&oMar5<)|4AZQh+SJ&qo8yMHt7z#_-BXfjZ znxtAyNf81(CNe3ENn!GZ%cKbnO;U=7HzL?~TB?T!g*=G+dO?3c87N^Z=U`dkRRP{ cw|gF<)4P_J{bsj6^qwu}i|*p$yPsbF4;rW#q5QX=Bir#1wz#`d0&PagZMU;4x1X1Md3@s)SEz7Wn35N!pTr!K?Ah}4s z*UhftXdu+zd-c=RLtfl{_vTf1IfRRU|NHx&(8V@RzkLihH=+6U&)>(`+#Z+Xr|CH~ zcgN@P91{We;q~hUXI^RU*o$L zj&8WR?92a$(AC5~`zMGra+|ASD+i`)hq1yNmN#EQ~rvrKE#yzUdZz@kH^p9kI-LU5!J?5MJN?Ker5*xo1cT6yPdxC7~W^KmA#LBJSb!g zT|&;`$g)>??{aE4vf@{49|sF>H}VcyM9``9XUaj$Mu7fY|jl3)#%UUZd3fROt#SO-by4 zVUWv?s(wPs)zF4eSYK17>=6SV^4oo2_9m;^Aup%6vS;mBa0^rVp%PQEZ8L&xKWN){ zmE{WA4Uy>8AS~C$lgZXmkkA$!Vq5TzshESM+9<>vYK_1NEWHCSw_7ce!&Y7`T9jIJ z1uj#l5sJbE+}0bT*|l8PHb`N6xw4%-^*Ana1G z3GU$s!ZA2FfkH-+y?LL5EG!I%UmozTfIrk{q1KG= zil-S16SG}7pbfD--p&daX$>Z zA!1%X@MkHW%yi&YM;|vOAl0bb8jO|F#9ti9O5FyKLm4iuUL(pyvj#pavlN|U5@JIMM zcB*QfD5`qhU0ttV-dLKGt8d=CnxBr{$-n>o{ZBVZQ8E)H6qy6l1`?yE#sV$C< z+D+K1#e=Oj`+j=7-~ZZ%VY#l#r~Pi)eC+<{hSN1!XYJJi930$!WCr!KyYDKvSk&)s z>fS%ovsxb-PAg^VM#H7y%5ZIXZa6;++y@Tnc?kBA-$#DGaBUy8efa8AC!F�SFTw~47c%LSWD8C@(ozhwj^H-PB1x(-vq($nK0Px+r_B*KKFy}C?1&JctwteC zjxe&qrD*~(NvSy|9l?r7ZP|p*CMi(6bR^x>#uTB1LCL-_mW9a|=JbSX2qs9Hq->4L zkrXSBituzbA%Mie7VE;;5=KcFTf#MA6ZB0$CfTDrDpC_R!G%ex@hpr;s^JoPaE~+L z8oLQvCTN*}OmJqB%JY;i&r0LVEJ&)=kd;PHsD>;|6JdG?*I-R3G@+qM>Q*;LQn^oo zy1GiM!J2?X5JN(ag#p4i7UruUNJeU2N~`WB?8O8tCLoiP#uxP9i!Q=7F%uF@XlX)A z6LxDtKao0Clk8a!E=9162MtM>9ASWPU1Jk`nb6QAb?aCi!LbQGMCy!9Qo63bBU!U- zbv6h4>%ixMnX8lVAEZyRNHQcDldO_tPjoHKW`blN4kjYmhx_IT*S`jG*c#8eAlPvP zJ0esELD~_dO|ph8Mtx|5urLL}YH1&F>Qz1-& zF!{o@F%ztq(A^|!>L?T``zG1G>^qVM$kNlk445Eh!cm%hQG>E0D4XC@q{eKL0%gq+ z<|IOv9`_^(lOs$+VfqPkbA)SKCTzvc6s~o*M$7+db{0o ZiH@&Zp7xvF{<-(C8qd3vlW%`~^FM=LntT8N diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_from_helper_2.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_from_helper_2.approved.pdf index 36bb806bd89962e8fc56a2601a75a093f639c054..54874db39c3984672824cd48e1392d66be1cc5d9 100644 GIT binary patch delta 841 zcmYjPy)pzr5U#yDKeu)lCE%!1gWD?!c6)Yj_exO$R0)(oMD7J-BoYsRP6Pr^Af^Of zL2x{Sz$;L`zMYvZU;`FYF}{;_StRr5%%8fEn^*a+DE9poVOyumr)1nYt-skDx~JN4`62%Y=Vb;Jb4N=uagv+ lIf`a{yfM`ZjbSJ&A@F%>C=k{1_-3Xbe9ATXalCi5{|D;Nv%~-Z literal 4537 zcmY+I&2Ae<5QOjh6m!uffJJh-{1G7d5GB4OK@>YTLkC7$DGF>cp=iL#OXiR_NM0mg zlT=li5QM7f?&_Kz&Jx(O>u=t?nw>AYvw#2l`=4&srfvJnZ{6i(H~IO`-^Vt&+Uz$U zwh!Ipdh;+ox1NNz-Rsxg_;go^#a{}pwx6E%ZPrb`d%SyY@0&%cwiI?F2}g;^598hA z=Iia}_I`@S>f*dV{JZHUZ^!5Dr`;{xE-%~UdbhoOH}27!;I-W}noq8_PkXeU+x+CG z-Gr%HJeq2g@5iTy{jY5pmaD3K-tWfE$L^1=KOe}NwpT}R=zsgMtEE}nJsiM%-uT;% z-Th%O(^?MnU@X(eo@=L>&;o6Y?N}lzB=Yd0T7BaKY!HaYljqxIIxWTV- zhPJX_!If9nhWJ$mevRjWLy%*2PDtY2>lelxz1G>2acFYfUJrftr1hb#y(htySJ#H@ zNg3?PcoBFR_`(>!%Ezy97{IUBLkGWFAKJx>U+fs?`oin9RSs>9*T&r1y4k0TYs|T8 zop}l4MHoThLMDBZY{5xKT8d%T5!?ouCrLGC7HKKnr)Ng!v>5`m-@}f}jtBwTY81lc z2qP<8nkFEVl$v4E5v+*RmQCnvk^;3$N77AgOc6>Llt-hb8o#5<1D`-0Qkj?cUC zo;L$SH`S*t7mN9#v3&aQdwW3bNlPnUdDO>CJ?|A>miD{N Date: Mon, 6 Oct 2025 14:51:19 -0400 Subject: [PATCH 2/3] Optimize per codebude's suggestions --- QRCoder/PdfByteQRCode.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/QRCoder/PdfByteQRCode.cs b/QRCoder/PdfByteQRCode.cs index 62f9b5a9..84b7833f 100644 --- a/QRCoder/PdfByteQRCode.cs +++ b/QRCoder/PdfByteQRCode.cs @@ -197,16 +197,19 @@ private string CreatePathFromModules() // Found a dark module - find the run length int startX = x; - int runLength = 0; while (x < size && matrix[y][x]) { - runLength++; x++; } // Create a single rectangle for the entire run of dark modules // Format: x y width height re - pathCommands.Append(ToStr(startX) + " " + ToStr(y) + " " + ToStr(runLength) + " 1 re\r\n"); + pathCommands.Append(ToStr(startX)); + pathCommands.Append(' '); + pathCommands.Append(ToStr(y)); + pathCommands.Append(' '); + pathCommands.Append(ToStr(x - startX)); + pathCommands.Append(" 1 re\r\n"); } } From ace2dddb8fae15fa4fc46908bb8c4743239655e6 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Mon, 6 Oct 2025 17:44:36 -0400 Subject: [PATCH 3/3] Update TransposeVerificationTests.pdf_renderer.approved.pdf --- ...erificationTests.pdf_renderer.approved.pdf | Bin 3346 -> 1945 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/QRCoderTests/TransposeVerificationTests.pdf_renderer.approved.pdf b/QRCoderTests/TransposeVerificationTests.pdf_renderer.approved.pdf index 51acbf0225439f6ce768b80c3afc926ccd6d7d57..085c1a5b98bbea0214817d2a633196f2b78bfa83 100644 GIT binary patch delta 603 zcmYjOyGq1B6eSNfYqCVFG}?^D1k23KW|AmqrG*G$FIZ_M2>yV-pboMKg1=&6WozR% zC|LOco|~D;E`)G$ALnuNxc zFovpy2As~PcuODFlfc5T4=yVA`*3RFQBVv?xIG)~r!EWn2&~^n+d~OcHA)*%S&|Jb zf>1dnsLs>FoMN?pi^?53q19+y5b$scYdRf$NPRwDOBXKCLh5ULBJCYGraGr3;IeaM zun9TCg_~EP`dE_AH4f014&qhZ{U*+&^-fu6tM>qe$65s;&GZcr;+8r}u2Q#$6m+bo cMBulrOtL|#Vq%Kf>&Z*ZySj;s;_&q7AJDzRIJxIG zsjBaI?wosO{G<5j;_36_@}x~ifB*gdlghlv!-vo5>?{@Uetzw9alW3`_rq-}F4njG zHdho}rpJ#{zqzqO(}xM?!}raU%T&C$yV>UVStd5u9LAo4*`j#W-`uUA3_tSw1s-L6 zdQ#2*uTpW@Z-?*WHT}-ca&a*Z*Khj?$AYx^D$~9=A2t(i+g$Gx<*N|O4$iT1@v`6C zPM>mJb_<)ioyLCsC4EcHNk`QpAI~r+xcX9rKDEN7 za3$=7r^0d(xDFir%3xRqn=<%R!KW(aQ&oY3e-)xPxjn3cIN~aB9k>nLiLvy#O zm%^2>6VlU*%PMdkIJmF{UeB_{W#HgLF0X0wO1T_YI@$@zQ~#0&x{M z1ZZ-nERHK3?Sy3U3ie363>@mo;x%Luhq>&FA8+#MAq%aCl3f+vGSY{5+uMxq(FiALC%Ac*H~+$llSQ9S5F+5WlECoYR1 zDANc_mLQu8s<7~Vab_=^ErCJLV}4B)!%rxL72 zf(>+GeI%GG5e*1UI^P**QaLlz8E~eZv+bA!c_nBk5xwk*;3KvbE=a}Le!VzLU?%aP zjGfR3@g?Xf0lBa}lAxbN>|>Xbk2Kk4&C!;DSAo00Y>Xyw?t*Becrb_GINk&bqKYEA z5mIHaMch5~3F%BoXBs+NNC|pK&_ja6E}$Ud9Io9k60^Wu8lKCUT+ZZjwp;G+Cb2U;oN3|AiFdZV610k=VrY`W!o564CABqx6QChyBm+{cFUJv9}-CO