From 03c9340eb29febee21f034e305bb483e492a2a83 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 3 Oct 2024 20:15:16 -0500 Subject: [PATCH] Mask the puzzle piece --- captcha/puzzle.png | Bin 0 -> 4802 bytes src/controllers/api/captcha.ts | 30 +++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 captcha/puzzle.png diff --git a/captcha/puzzle.png b/captcha/puzzle.png new file mode 100644 index 0000000000000000000000000000000000000000..37d762c9e50c52c4820f4d59e799426185d65fcb GIT binary patch literal 4802 zcmY+IXHe5kw8sAlMTj(M(nJKLB}5QjdI=q=QWC0|(2*7a5h)Ussvu385D<{wf&u9e z0qF!QO{z2zgwO>QZoHrF{jj?;=j=K2JhL#OWvgeA=u6i{1h1I@w5|PLyo4SuMI_bloXI z9lGunqSCxE-J1-FR?bBKVrC9=NpScqA=w_4}#BgZ$CD>Z10?Z8b zOW4R(8}dcmC-O>r5Ap+V6k?VvKvFM}IKZ3B3IrCAgEu^Za7rTnf2dLy%{MQD@EDTsE{n z5!lwpF;1&${am{YEv22*&{6vKy8UnaTZ5{diWGLV*f|Z^Qv4OWfD+n}>E)s&2whrg zIc4+oJ`-}n-R{M?n>ngMF8JrI<2vURQv$0=5s!{Bc9~;q6=y`)(?3TiYiq}6XO3pW zV|y{nM-zv;XJ=pg9m#yisQb7F0s8$03xEDHt!Kp!qT=bkNAmSyCWzoEs{$^b+#zr+ z(1vmtox*luT#Z>!%dd3KJ*BUsE_1uH%gy9y|*3=MY?8 z%pTZg1Htw7n(>ZHr(rWpbJ~bg`NP+IAVFu7km_>}1e?1K1Co9#YB;`=W9XfCa)^}P zdK>!&qO>9k#fDANI`)bo+uVkQ`5mJtwFh6iuq~XB1`cXTP~)|b{t~d^f2TENQD*PT z%bw1z(gT~E@G`GpVUdY8*~JKXdp{s`!&&gLBmjiS#N@Z7GtPaXY1vsClPr=;O{cZJ zyy?l0;$vm0P56DyTb@k#`P=ll+7MU#u9Zd0yJYDS$ezLoHyzEvRZ=TDk&+mCWGanc z_UR}CK=85T0nLjUVLNjHFEsDd^Za%MS;k@Mbe%T1U#DW8i>&w{D|frXxFZcSm{$USLf z2C~^&aw!<)NCGJww)JC0Hr4#s`!1~u^13OBgXS*E?V0kIcy+r4RaoBtSzYP5vhP33 zK@pGCD8*$rj6uwYy&1Q-_dY4x4%$37?*`?JjNWJ(#-GWgU*OYAJ6T^$Mo$E2?x1_<-JMy3PMVDziHN{L z6_ddGuJ7M0nd8>nMPiPq4eKB>7cGUtsM_AQ);ZSgRhVB;Gk))}`|yS??zh)%b-BlV z5H%+{7OUvW7TA^{j*%cu;&^Li!b`XU5+l6`JJp9f^sSpSl<%$7&;S#sZAc`2J~0JfbAuYV}<+oj0-eye^kY3BKF!b(cl z%GlA~e0uxV8|k7BkW=Abmq#`e&5txluHd~U8_iCZh4UjZ`G7IHlcdIvS$LDa2SC~x z5h?eDr)U-~Xnarx1lApe2F$frnJJhr_|EUImW({GF5yTvMRG}CwUxIy&Lgx(uMBG@ zCw&_f#L{??6s^eUkWe{}m{+PbXJN&$W{n=Kp8Js_u}4!ee#dC#tGq+mz0JrJ8&6yd zW`$?tL22vYkBWJ(B>IpyZ8@tLiDzY2T`8=L6anKkrG&f@_D|l=yz1i2ORvwBvQ_zC zf5n~)McQV5OrLr9Ac3xOqC1&uvJ`Un zSWN{k`!{z>t|IL-kzzLGJnC$_B^ z^N{XR>ZeOFUkmvDw`41dxh+qKP5BZ^2}dl;a!VMZ`0`Sq5X_Qh5eAp{49d|xRleVs z1ePXRQyXO-<~)m`n$L}me=Ny`SZ?@8+qFtHLAd=#=~FQ{K98kJu6v3e*WV#^X@ZDt zNg7zkn$^-@$@2x2s?FqK3E(of)LWmup0xwSSEebikXri7LAEucc^zq?r5xx+womrU23}V5MN{eHoD`fh!2}*wo?@TvnVN4**}y3R)kD;gv}JMAkw1ZXUe2t!(7)4bE;pQGePYZNd)B)Q z!ZO55_YBmRBcj0YTze;A_#R;f|IPt+UU*EbM*l(ds2Fs&{vIaRMvll8pbPq z2Otcj6e%BiY{>w0;PNrk-I7O}nOY!Qt}SLt<1a7~R!8T4d~_nNgYy*OnzRCf<4~fw zT&sl!*kvcUIA|+G5jq62=W1?ccnm9!1OGDHq@9>*@AIP*wTK+J9_oslnA|OVqHCu? zs7P_!X9A4Pn832o-~iHS)(0X0ySwI~o#t_td7NP8y~?UQLMwCoIshnLPy@EV=xES2 z>-FRS?p&xfepMbQx-+$ezInw51JfVH?y3@DC3}|!W5&dldY$)@VE>U0{aW0`vOXMw zmg83-i4545BYJ5@LR7pJ3&zI7oaA15NfGm0){PuQkYV@b?}p?F z8PenqTn%!;(sa4{lk7k7RD;@%=3~s@QeAo3YPi8saS}6VdACJNEx5zx<(+q~r_ZW$ zYNH-NuS#y#;Et=@eL=MK8I8fmkbM;C&xwryAM?V2w7@t1dCgf>H|4WN@aja@ z2cR=H*}r1n@HA?oQgBytO{w`5`7GFPt_@uG{uHxUbfj`&@+kzzg@YA@*`WR4QN-Au zI19isVguyo5V{f}rf8xO!t;^amGyl32&k&_0GNRGS31<)n$A}!1Ls<(#Cx6!w|QSy ztBdMy3{~V-dp8m0{Q8^lw5W z$!=W{E-FRrWjp+>qKVfk$X%VGx7~{kB~~sdC%tZb;22GG)K-j(p^=xMGJ`&laEpoz zw9iPTLBXrAre5aMx%VoK|8T~ z>f00}w%*!@;U*OvfShlvE|o#|>QlcSE-Q2%aBCbdrV#OU+1VcXj7|`Y3%k8O7n8T)m81=f8?#e>~h^P#i&uq@xx zTV81HhudeeUKs0*^qHm=-ocIWEtyF z8?oIpoZROt-Y!+9ZsVGfxD{-r?BEH29>T5nc8~Aa9(zeruD8-X&Fu7tTnT8EIry8~ zs5)#~{jt2!J0eF?iByt?n6~@j=!V{tG?ayQPgOuEW|Zp2eET)LK-0KgRC*-!JWXH) zvlc%#i;0>UPk8(3Cj+GeVXckh)Mu>@{8x^xMuHwbB1l8yzxzIzTU@eF1C~@YBpvsti%fXe9T`1^Yx{$6@vGR;EEZfy9fjeMC_#6E8Uv;H1p{8%#a)9NjMk_+ z;SqbPcy}h*h^k#7jvPQz!5vMU*RKm-Z41V)2`m5O!}6tN ze9lmwXjW3aIh-#(xzv*2%m4&C+~cGag=*SCLGQLzWHO zX;{vxR?G!yNDr^!H0|skjXQW4-+s||2e4JI6U}n08k|@)P%DD1mG8w*H7=%$lQu?S zyBPS?VZe;kZVWPb%2V>g?|AAZF)Apy z@JTH(%J!blJ~*T?*p!f5$7Ld=AFCs^)nudN)*~^jg)dBft!mYgQ^8ZKNSb(Ul*ZT# zCjJTgOOtVm#zrAo>;%uYzlc4U3;G+W{_{>wm=T`Kq``AzPLZ9oMGov%q1%0%GWPA` zKoCuc;|=4IN>TyNjlq)`lDWi6ab34Ki=Sc;B; zUn)N?$~K2wEN^q8*^p$Ht&fb#YfvC+2JkhI1IdUlb+UYG6( zmk{ZlWj!Vx?8qZO*!1NA4Z4!qBKL77l*9@9_~WdRe}r{(Pj{30DFxinGt;frMnC!= DPao_# literal 0 HcmV?d00001 diff --git a/src/controllers/api/captcha.ts b/src/controllers/api/captcha.ts index 7ca8cc4c..9599c641 100644 --- a/src/controllers/api/captcha.ts +++ b/src/controllers/api/captcha.ts @@ -8,12 +8,13 @@ import { aesEncrypt } from '@/utils/aes.ts'; export const captchaController: AppController = async (c) => { const { puzzle, piece, solution } = await generateCaptcha( await Deno.readFile(new URL('../../../captcha/tj-holowaychuk.jpg', import.meta.url)), + await Deno.readFile(new URL('../../../captcha/puzzle.png', import.meta.url)), { cw: 300, ch: 300, pw: 50, ph: 50, - alpha: 0.6, + alpha: 0.8, }, ); @@ -41,6 +42,7 @@ interface Point { async function generateCaptcha( from: Uint8Array, + mask: Uint8Array, opts: { pw: number; ph: number; @@ -54,12 +56,34 @@ async function generateCaptcha( const ctx = puzzle.getContext('2d'); const image = await loadImage(from); ctx.drawImage(image, 0, 0, image.width(), image.height(), 0, 0, cw, ch); + const piece = createCanvas(pw, ph); const pctx = piece.getContext('2d'); + const solution = getPieceCoords(puzzle.width, puzzle.height, pw, ph); + + // Draw the piece onto the puzzle piece canvas but only where the mask allows + const maskImage = await loadImage(mask); + pctx.globalCompositeOperation = 'source-over'; + pctx.drawImage(maskImage, 0, 0, pw, ph); + pctx.globalCompositeOperation = 'source-in'; pctx.drawImage(puzzle, solution.x, solution.y, pw, ph, 0, 0, pw, ph); - ctx.fillStyle = `rgba(0, 0, 0, ${alpha})`; - ctx.fillRect(solution.x, solution.y, pw, ph); + + // Reset composite operation + pctx.globalCompositeOperation = 'source-over'; + + // Create a temporary canvas to draw the darkened shape + const tempCanvas = createCanvas(pw, ph); + const tempCtx = tempCanvas.getContext('2d'); + + // Draw the darkened shape onto the temporary canvas but only where the mask allows + tempCtx.fillStyle = `rgba(0, 0, 0, ${alpha})`; + tempCtx.fillRect(0, 0, pw, ph); + tempCtx.globalCompositeOperation = 'destination-in'; + tempCtx.drawImage(maskImage, 0, 0, pw, ph); + + // Draw the temporary canvas onto the puzzle at the piece's location + ctx.drawImage(tempCanvas, solution.x, solution.y, pw, ph); return { puzzle,