@@ -65,34 +65,97 @@ class CrumpledTextCaptcha extends ChallengeProvider {
6565 val oldTransform = g.getTransform()
6666 g.translate(xOffset, yOffset)
6767 g.scale(scaleX, 1d )
68-
69- // Draw characters with random rotation and vertical offset to simulate being on different paper folds
70- var currentX = 0
71- for (char <- secret) {
72- val charTransform = g.getTransform()
73- g.translate(currentX, r.nextInt(height / 4 ) - height / 8 )
74- g.rotate((r.nextDouble() - 0.5 ) * 1.0 )
75- g.drawString(char.toString, 0 , 0 )
76- currentX += g.getFontMetrics().charWidth(char)
77- g.setTransform(charTransform)
78- }
79-
68+ g.drawString(secret, 0 , 0 )
8069 g.setTransform(oldTransform)
8170 g.dispose()
8271
83- val image = ImmutableImage .fromAwt(canvas)
84-
85- // Use a Triangle ripple to create sharp "folds" in the image
86- val ripple = new RippleFilter (com.sksamuel.scrimage.filter.RippleType .Triangle , 10 .toFloat, 10 .toFloat, 0.05f , 0.05f )
87-
88- val filteredImage = image.filter(ripple)
72+ var img = canvas
73+ val numFolds = level match {
74+ case " easy" => 15
75+ case " medium" => 25
76+ case " hard" => 35
77+ case _ => 25
78+ }
79+ for (_ <- 0 until numFolds) {
80+ img = applyFold(img, r)
81+ }
8982
90- val img = filteredImage.awt()
9183 val baos = new ByteArrayOutputStream ()
9284 PngImageWriter .write(baos, img);
9385 new Challenge (baos.toByteArray, " image/png" , secret)
9486 }
9587
88+ private def applyFold (img : BufferedImage , r : scala.util.Random ): BufferedImage = {
89+ val width = img.getWidth
90+ val height = img.getHeight
91+ val newImg = new BufferedImage (width, height, img.getType)
92+
93+ val x1 = r.nextInt(width)
94+ val y1 = r.nextInt(height)
95+ val angle = r.nextDouble() * 2 * Math .PI
96+ val L = (r.nextDouble() * 0.5 + 0.3 ) * width
97+ val W = (r.nextDouble() * 0.3 + 0.1 ) * height
98+
99+ val dx = Math .cos(angle)
100+ val dy = Math .sin(angle)
101+ val nx = - dy
102+ val ny = dx
103+
104+ val maxShift = (r.nextDouble() - 0.5 ) * 20.0
105+
106+ for (y <- 0 until height) {
107+ for (x <- 0 until width) {
108+ val px = x - x1
109+ val py = y - y1
110+ val projS = px * dx + py * dy
111+ val projN = px * nx + py * ny
112+
113+ if (projS >= 0 && projS <= L && Math .abs(projN) <= W ) {
114+ val shift = (projN / W ) * maxShift
115+ val srcX = x - shift * dx
116+ val srcY = y - shift * dy
117+
118+ if (srcX >= 0 && srcX < width - 1 && srcY >= 0 && srcY < height - 1 ) {
119+ val xf = Math .floor(srcX).toInt
120+ val yf = Math .floor(srcY).toInt
121+ val xt = srcX - xf
122+ val yt = srcY - yf
123+
124+ val rgb00 = img.getRGB(xf, yf)
125+ val rgb10 = img.getRGB(xf + 1 , yf)
126+ val rgb01 = img.getRGB(xf, yf + 1 )
127+ val rgb11 = img.getRGB(xf + 1 , yf + 1 )
128+
129+ val r00 = (rgb00 >> 16 ) & 0xFF
130+ val g00 = (rgb00 >> 8 ) & 0xFF
131+ val b00 = rgb00 & 0xFF
132+ val r10 = (rgb10 >> 16 ) & 0xFF
133+ val g10 = (rgb10 >> 8 ) & 0xFF
134+ val b10 = rgb10 & 0xFF
135+ val r01 = (rgb01 >> 16 ) & 0xFF
136+ val g01 = (rgb01 >> 8 ) & 0xFF
137+ val b01 = rgb01 & 0xFF
138+ val r11 = (rgb11 >> 16 ) & 0xFF
139+ val g11 = (rgb11 >> 8 ) & 0xFF
140+ val b11 = rgb11 & 0xFF
141+
142+ val rInterp = (r00 * (1 - xt) * (1 - yt) + r10 * xt * (1 - yt) + r01 * (1 - xt) * yt + r11 * xt * yt).toInt
143+ val gInterp = (g00 * (1 - xt) * (1 - yt) + g10 * xt * (1 - yt) + g01 * (1 - xt) * yt + g11 * xt * yt).toInt
144+ val bInterp = (b00 * (1 - xt) * (1 - yt) + b10 * xt * (1 - yt) + b01 * (1 - xt) * yt + b11 * xt * yt).toInt
145+
146+ val rgb = (0xFF << 24 ) | (rInterp << 16 ) | (gInterp << 8 ) | bInterp
147+ newImg.setRGB(x, y, rgb)
148+ } else {
149+ newImg.setRGB(x, y, 0xFFFFFFFF )
150+ }
151+ } else {
152+ newImg.setRGB(x, y, img.getRGB(x, y))
153+ }
154+ }
155+ }
156+ newImg
157+ }
158+
96159 def checkAnswer (secret : String , answer : String ): Boolean = {
97160 secret.toLowerCase == answer.toLowerCase
98161 }
0 commit comments