From cdf727e5c7eb7611a2ecb24ec7bdbfebc319501a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 4 Oct 2024 15:10:42 -0500 Subject: [PATCH] captcha: use an already transparent png with stroke to make the hole --- captcha/puzzle-hole.png | Bin 0 -> 2343 bytes captcha/puzzle-mask.png | Bin 0 -> 1031 bytes captcha/puzzle.png | Bin 4802 -> 0 bytes src/controllers/api/captcha.ts | 29 ++++++++--------------------- 4 files changed, 8 insertions(+), 21 deletions(-) create mode 100644 captcha/puzzle-hole.png create mode 100644 captcha/puzzle-mask.png delete mode 100644 captcha/puzzle.png diff --git a/captcha/puzzle-hole.png b/captcha/puzzle-hole.png new file mode 100644 index 0000000000000000000000000000000000000000..89bb8608d2322b42001b387b7051a169be1e6743 GIT binary patch literal 2343 zcmV+?3E1|DP)D000008FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H12((E= zK~#90?VD|ER9P9ve|N4k(`jd>EiEmyEN$g&%PxIY-bAv{g^-vS#7bg>u$v$lO;G6v z1rvh?jgpvPjS4|i#TewJ`^6AY6c$3vwoF^JRM5`scKfm;-4@!;bf%rR>xX+UxehbK zyq3<&|4Yv~_nc>*`|oqldCv2kBS@0O|2uTgTY>q2kIm6gpu7+Z}Vx z5k-+JSFX6WZQJ&m(liBp8hP@ZGo)*Yd;^7m(b3V~)vH(YECK+6!64hWZ@1WNHub-6 z1-_Feo#IA1EI|)+14WV~&8%FxveRz2=OrS*Y&HkS$H$i%42DHO5cmY}Fjd9kMLH~D z9Z&?&(b17G0RV2dTWo7<>s6{Eurm#M#f@~3uvsm%wzkeDE9Cg`CaG=B2r$xVVFsH@6ul61Ml&FJgM1aR)$xwj7-I8Znr2>@tqZ7phPY0lX`J5)5h~)+#XH5C{acb5>H;n-wOX zX}lnT7bE~LNC4(0`7)O{1biO&0O&;C|NQ$|gFb4OU`5{jHUUR~w}A`5E3pP;JqhFE zI1oY}q@fPoMs)-|tsL_ag8E;2V)-X!j09$a@EzPUqjt%F0p>l00wZ<>g7Y zZ{H5p)zz(4nnJ)H>@w3aXq+yz*?c=4-SwrrW23q>=D>FH^qzP?_(b?esOlqMZ= zG)k}S7d|KaTKOwpzI=K4(xpphGOXvEpsA_J_~_B2exV1m(>{-sfyzfFqgri{B+29TdPjr7U;rQ#3Q2u^ zea_vxck4z*MobyfJ?)sDo-WJH&0UO~{CyeuoJ7<#Tpn;7_yRGOQJ2frpP!#UIbWks z;jr0k{>H|}^-6Us@BpH};56_}#Pc+wFdLWMpK_=YHMpfgM)*46%`eAN|PUXC;2}f zu$F{!wb0eol{IUBI7&)NX7}vbGtt%6HQUqEv$nXnc(wA#k9_8#FMN!4Vzj7*si~=~ zIr`q*+&ppq{Q0#2f*|MwL5Or_>LK!}*&C5$WPOa$Xf#SXoz4>F@g#D1?I*}*BqL(Z zNN8winAGd_A)C!s5DW%0wbytL+1&e)14K7~XQmW0PJ+o~3iS8)>&wf_w*t7`?*8iP z>e1oh;hblUO#;6Mev#;)jC*@0PMnx5FE4-Mew)qa6_+kunpg3OjFYf=^X8|%*Hx=l zl|<_MypVAcL{WTdr4a;SUO8b|vH8g~v?M&)P#BHIw9Vc|qfr~#odys^Q6Hsy!mwB@ z+C+;t85vp<-d78?wY6z;naj$`ik0FN@K1oeyu4DQ(HM$8Dt>I(utEC{&SYe01q=dX z0BhE)d8MGBAm!vsQ&ZDqZf zRIdY%l93ZWG5!oW6GeS}{inLSyJrdu3lk=zy1IILXlN)eKRjGWg{FMx%dPUpSO&d#x+p`jo#?noL8 z23<`}jj^$@ag|=LSA7i$_%`snxZXGdqU9Yn0l!4K(UJo0BS&$0FauY@-t_f!yUbxM`e*rYs!TbxgWBvdD N002ovPDHLkV1o0PY5)KL literal 0 HcmV?d00001 diff --git a/captcha/puzzle-mask.png b/captcha/puzzle-mask.png new file mode 100644 index 0000000000000000000000000000000000000000..3171f4b3829f5371f90fea77b35ef7249b9eb6ea GIT binary patch literal 1031 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 DPao_# diff --git a/src/controllers/api/captcha.ts b/src/controllers/api/captcha.ts index 9fc34de8..e276db63 100644 --- a/src/controllers/api/captcha.ts +++ b/src/controllers/api/captcha.ts @@ -22,13 +22,13 @@ const captchas = new TTLCache(); export const captchaController: AppController = async (c) => { const { bg, puzzle, 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)), + await Deno.readFile(new URL('../../../captcha/puzzle-mask.png', import.meta.url)), + await Deno.readFile(new URL('../../../captcha/puzzle-hole.png', import.meta.url)), { cw: 370, ch: 400, pw: 65, ph: 65, - alpha: 0.8, }, ); @@ -52,15 +52,15 @@ export const captchaController: AppController = async (c) => { async function generateCaptcha( from: Uint8Array, mask: Uint8Array, + hole: Uint8Array, opts: { pw: number; ph: number; cw: number; ch: number; - alpha: number; }, ) { - const { pw, ph, cw, ch, alpha } = opts; + const { pw, ph, cw, ch } = opts; const bg = createCanvas(cw, ch); const ctx = bg.getContext('2d'); const image = await loadImage(from); @@ -71,28 +71,15 @@ async function generateCaptcha( const solution = getPieceCoords(bg.width, bg.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'; + const holeImage = await loadImage(hole); + pctx.drawImage(maskImage, 0, 0, pw, ph); pctx.globalCompositeOperation = 'source-in'; pctx.drawImage(bg, solution.x, solution.y, pw, ph, 0, 0, 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); + ctx.globalCompositeOperation = 'source-atop'; + ctx.drawImage(holeImage, solution.x, solution.y, pw, ph); return { bg,