From 23e8d0a7edc921f8f82e73bb5c2bf0093f6bc55d Mon Sep 17 00:00:00 2001 From: hrj Date: Sun, 31 Mar 2019 16:53:32 +0530 Subject: [PATCH 1/6] updated deps --- build.sbt | 2 +- project/Dependencies.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index adfd79e..7f90eae 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ lazy val root = (project in file(".")). libraryDependencies += "com.sksamuel.scrimage" %% "scrimage-filters" % "2.1.8", - libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.6.1" + libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.6.5" ) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 69e5af0..60c09c1 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,5 +1,5 @@ import sbt._ object Dependencies { - lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.3" + lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.7" } From 9d9657d373ecde43840a9f7ec557625a2cdb6544 Mon Sep 17 00:00:00 2001 From: hrj Date: Sun, 31 Mar 2019 18:53:16 +0530 Subject: [PATCH 2/6] rain drop single frame --- src/main/scala/lc/RainDropsCaptcha.scala | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/scala/lc/RainDropsCaptcha.scala diff --git a/src/main/scala/lc/RainDropsCaptcha.scala b/src/main/scala/lc/RainDropsCaptcha.scala new file mode 100644 index 0000000..b173d14 --- /dev/null +++ b/src/main/scala/lc/RainDropsCaptcha.scala @@ -0,0 +1,53 @@ +package lc + +import java.awt.image.BufferedImage +import java.awt.RenderingHints +import java.awt.Font +import java.awt.Color +import java.io.ByteArrayOutputStream +import javax.imageio.ImageIO + +class RainDropsCP extends ChallengeProvider { + private val alphabet = "abcdefghijklmnopqrstuvwxyz" + private val n = 8 + + def getId = "FilterChallenge" + + def returnChallenge(): Challenge = { + val r = new scala.util.Random + val secret = Stream.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString + val width = 225 + val height = 100 + + val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) + val g = canvas.createGraphics() + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + + // clear the canvas + g.setColor(Color.WHITE) + g.fillRect(0, 0, canvas.getWidth, canvas.getHeight) + + // paint the rain + g.setColor(Color.BLACK) + val xOffset = 1+r.nextInt(10) + val yOffset = 1+r.nextInt(10) + for (i <- 0 until 1000) { + val x = r.nextInt(width) + val y = r.nextInt(height) + g.drawLine(x, y, x+xOffset, y+yOffset) + } + + // paint the text + g.setColor(Color.WHITE) + g.setFont(new Font("Sans", Font.BOLD, 42)) + g.drawString(secret, 5, 50) + + g.dispose() + val baos = new ByteArrayOutputStream(); + ImageIO.write(canvas,"png",baos); + new Challenge(baos.toByteArray, "image/png", secret) + } + def checkAnswer(secret: String, answer: String): Boolean = { + secret == answer + } +} From 80cf6b66fd0503913eded953ac17b68a2014c23d Mon Sep 17 00:00:00 2001 From: hrj Date: Sun, 31 Mar 2019 19:03:22 +0530 Subject: [PATCH 3/6] add attribution to gif sequence writer class --- src/main/java/lc/GifSequenceWriter.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/java/lc/GifSequenceWriter.java b/src/main/java/lc/GifSequenceWriter.java index 46c2f70..0cf014a 100644 --- a/src/main/java/lc/GifSequenceWriter.java +++ b/src/main/java/lc/GifSequenceWriter.java @@ -1,3 +1,6 @@ +// This code was adapted from http://elliot.kroo.net/software/java/GifSequenceWriter/ +// It was available under CC By 3.0 + package lc; import javax.imageio.*; import javax.imageio.metadata.*; @@ -139,13 +142,4 @@ public class GifSequenceWriter { rootNode.appendChild(node); return(node); } - - /** - public GifSequenceWriter( - BufferedOutputStream outputStream, - int imageType, - int timeBetweenFramesMS, - boolean loopContinuously) { - - */ } From beb184d7a905a397ebced83be551e97c2b19585e Mon Sep 17 00:00:00 2001 From: hrj Date: Sun, 31 Mar 2019 22:58:58 +0530 Subject: [PATCH 4/6] rain drop animation --- src/main/scala/lc/RainDropsCaptcha.scala | 100 +++++++++++++++++------ 1 file changed, 74 insertions(+), 26 deletions(-) diff --git a/src/main/scala/lc/RainDropsCaptcha.scala b/src/main/scala/lc/RainDropsCaptcha.scala index b173d14..8ff7383 100644 --- a/src/main/scala/lc/RainDropsCaptcha.scala +++ b/src/main/scala/lc/RainDropsCaptcha.scala @@ -6,45 +6,93 @@ import java.awt.Font import java.awt.Color import java.io.ByteArrayOutputStream import javax.imageio.ImageIO +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; + +class Drop { + var x = 0 + var y = 0 + var yOffset = 0 + var color = 0 + var colorChange = 10 + def mkColor = { + new Color(color, color, 200) + } +} class RainDropsCP extends ChallengeProvider { private val alphabet = "abcdefghijklmnopqrstuvwxyz" - private val n = 8 + private val n = 6 def getId = "FilterChallenge" def returnChallenge(): Challenge = { val r = new scala.util.Random val secret = Stream.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString - val width = 225 + val width = 450 val height = 100 - - val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) - val g = canvas.createGraphics() - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) - - // clear the canvas - g.setColor(Color.WHITE) - g.fillRect(0, 0, canvas.getWidth, canvas.getHeight) - - // paint the rain - g.setColor(Color.BLACK) - val xOffset = 1+r.nextInt(10) - val yOffset = 1+r.nextInt(10) - for (i <- 0 until 1000) { - val x = r.nextInt(width) - val y = r.nextInt(height) - g.drawLine(x, y, x+xOffset, y+yOffset) + val imgType = BufferedImage.TYPE_INT_RGB + val xOffset = 1+r.nextInt(2) + val xBias = (height / 10) - 2 + val drops = Array.fill[Drop](1500)( new Drop()) + for (d <- drops) { + d.x = r.nextInt(width) - (xBias/2)*xOffset + d.yOffset = 6+r.nextInt(6) + d.y = r.nextInt(height) + d.color = r.nextInt(240) + if (d.color > 128) { + d.colorChange *= -1 + } } - - // paint the text - g.setColor(Color.WHITE) - g.setFont(new Font("Sans", Font.BOLD, 42)) - g.drawString(secret, 5, 50) - g.dispose() val baos = new ByteArrayOutputStream(); - ImageIO.write(canvas,"png",baos); + val ios = new MemoryCacheImageOutputStream(baos); + val writer = new GifSequenceWriter(ios, imgType, 100, true); + for(i <- 0 until 30){ + val yOffset = 5+r.nextInt(5) + val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) + val g = canvas.createGraphics() + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + + // clear the canvas + g.setColor(Color.WHITE) + g.fillRect(0, 0, canvas.getWidth, canvas.getHeight) + + // paint the rain + for (d <- drops) { + g.setColor(d.mkColor) + g.drawLine(d.x, d.y, d.x+xOffset, d.y+d.yOffset) + d.x += xOffset + d.y += d.yOffset + d.color += d.colorChange + if (d.x > width+xOffset || d.y > height+d.yOffset) { + d.x = r.nextInt(width) - xBias*xOffset + d.y = 0 + } + if (d.color > 200 || d.color < 21) { + d.colorChange *= -1 + } + } + + // center the text + g.setFont(new Font("Sans", Font.BOLD, 70)) + val textWidth = g.getFontMetrics().charsWidth(secret.toCharArray, 0, secret.toCharArray.length) + val textX = (width - textWidth)/2 + + // paint the top outline + g.setColor(Color.BLUE) + g.drawString(secret, textX, 69) + // paint the text + g.setColor(Color.WHITE) + g.drawString(secret, textX, 70) + + g.dispose() + writer.writeToSequence(canvas) + } + writer.close + ios.close + + // ImageIO.write(canvas,"png",baos); new Challenge(baos.toByteArray, "image/png", secret) } def checkAnswer(secret: String, answer: String): Boolean = { From a207b9dee6792efca4887dd8082bfe9a9365a8c4 Mon Sep 17 00:00:00 2001 From: hrj Date: Mon, 1 Apr 2019 00:30:45 +0530 Subject: [PATCH 5/6] refining the Rain drop captcha --- src/main/scala/lc/RainDropsCaptcha.scala | 41 ++++++++++++++++++------ 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/main/scala/lc/RainDropsCaptcha.scala b/src/main/scala/lc/RainDropsCaptcha.scala index 8ff7383..b14126d 100644 --- a/src/main/scala/lc/RainDropsCaptcha.scala +++ b/src/main/scala/lc/RainDropsCaptcha.scala @@ -3,6 +3,7 @@ package lc import java.awt.image.BufferedImage import java.awt.RenderingHints import java.awt.Font +import java.awt.font.TextAttribute import java.awt.Color import java.io.ByteArrayOutputStream import javax.imageio.ImageIO @@ -23,19 +24,32 @@ class Drop { class RainDropsCP extends ChallengeProvider { private val alphabet = "abcdefghijklmnopqrstuvwxyz" private val n = 6 + private val bgColor = new Color(245, 245, 245) + private val textColor = new Color(248, 248, 248) + private val textHighlightColor = new Color(208, 208, 255) def getId = "FilterChallenge" + private def extendDrops(drops: Array[Drop], steps: Int, xOffset: Int) = { + drops.map(d => { + val nd = new Drop() + nd.x + xOffset*steps + nd.y + d.yOffset*steps + nd + }) + } + def returnChallenge(): Challenge = { val r = new scala.util.Random val secret = Stream.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString + // val secret = "mmmmmm" val width = 450 val height = 100 val imgType = BufferedImage.TYPE_INT_RGB val xOffset = 1+r.nextInt(2) val xBias = (height / 10) - 2 - val drops = Array.fill[Drop](1500)( new Drop()) - for (d <- drops) { + val dropsOrig = Array.fill[Drop](600)( new Drop()) + for (d <- dropsOrig) { d.x = r.nextInt(width) - (xBias/2)*xOffset d.yOffset = 6+r.nextInt(6) d.y = r.nextInt(height) @@ -44,10 +58,17 @@ class RainDropsCP extends ChallengeProvider { d.colorChange *= -1 } } + val drops = dropsOrig ++ extendDrops(dropsOrig, 2, xOffset) ++ extendDrops(dropsOrig, 4, xOffset) + + val baseFont = new Font(Font.MONOSPACED, Font.BOLD, 80) + val attributes = new java.util.HashMap[TextAttribute, Object]() + attributes.put(TextAttribute.TRACKING, Double.box(0.2)) + attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_EXTRABOLD) + val spacedFont = baseFont.deriveFont(attributes) val baos = new ByteArrayOutputStream(); val ios = new MemoryCacheImageOutputStream(baos); - val writer = new GifSequenceWriter(ios, imgType, 100, true); + val writer = new GifSequenceWriter(ios, imgType, 60, true); for(i <- 0 until 30){ val yOffset = 5+r.nextInt(5) val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) @@ -55,7 +76,7 @@ class RainDropsCP extends ChallengeProvider { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) // clear the canvas - g.setColor(Color.WHITE) + g.setColor(bgColor) g.fillRect(0, 0, canvas.getWidth, canvas.getHeight) // paint the rain @@ -66,8 +87,10 @@ class RainDropsCP extends ChallengeProvider { d.y += d.yOffset d.color += d.colorChange if (d.x > width+xOffset || d.y > height+d.yOffset) { - d.x = r.nextInt(width) - xBias*xOffset - d.y = 0 + val ySteps = 1 + height / d.yOffset + d.x -= xOffset*ySteps + d.y -= yOffset*ySteps + } if (d.color > 200 || d.color < 21) { d.colorChange *= -1 @@ -75,15 +98,15 @@ class RainDropsCP extends ChallengeProvider { } // center the text - g.setFont(new Font("Sans", Font.BOLD, 70)) + g.setFont(spacedFont) val textWidth = g.getFontMetrics().charsWidth(secret.toCharArray, 0, secret.toCharArray.length) val textX = (width - textWidth)/2 // paint the top outline - g.setColor(Color.BLUE) + g.setColor(textHighlightColor) g.drawString(secret, textX, 69) // paint the text - g.setColor(Color.WHITE) + g.setColor(textColor) g.drawString(secret, textX, 70) g.dispose() From 1d3573c59f0ca7e495398ebe95ee825b88aa5737 Mon Sep 17 00:00:00 2001 From: hrj Date: Mon, 1 Apr 2019 18:32:56 +0530 Subject: [PATCH 6/6] rain drops captcha tweaks --- src/main/scala/lc/RainDropsCaptcha.scala | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/scala/lc/RainDropsCaptcha.scala b/src/main/scala/lc/RainDropsCaptcha.scala index b14126d..0b79add 100644 --- a/src/main/scala/lc/RainDropsCaptcha.scala +++ b/src/main/scala/lc/RainDropsCaptcha.scala @@ -17,16 +17,16 @@ class Drop { var color = 0 var colorChange = 10 def mkColor = { - new Color(color, color, 200) + new Color(color, color, math.min(200, color+100)) } } class RainDropsCP extends ChallengeProvider { private val alphabet = "abcdefghijklmnopqrstuvwxyz" private val n = 6 - private val bgColor = new Color(245, 245, 245) - private val textColor = new Color(248, 248, 248) - private val textHighlightColor = new Color(208, 208, 255) + private val bgColor = new Color(200, 200, 200) + private val textColor = new Color(208, 208, 218) + private val textHighlightColor = new Color(100, 100, 125) def getId = "FilterChallenge" @@ -42,13 +42,12 @@ class RainDropsCP extends ChallengeProvider { def returnChallenge(): Challenge = { val r = new scala.util.Random val secret = Stream.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString - // val secret = "mmmmmm" val width = 450 val height = 100 val imgType = BufferedImage.TYPE_INT_RGB - val xOffset = 1+r.nextInt(2) + val xOffset = 2+r.nextInt(3) val xBias = (height / 10) - 2 - val dropsOrig = Array.fill[Drop](600)( new Drop()) + val dropsOrig = Array.fill[Drop](2000)( new Drop()) for (d <- dropsOrig) { d.x = r.nextInt(width) - (xBias/2)*xOffset d.yOffset = 6+r.nextInt(6) @@ -58,7 +57,8 @@ class RainDropsCP extends ChallengeProvider { d.colorChange *= -1 } } - val drops = dropsOrig ++ extendDrops(dropsOrig, 2, xOffset) ++ extendDrops(dropsOrig, 4, xOffset) + val drops = dropsOrig ++ extendDrops(dropsOrig, 1, xOffset) ++ extendDrops(dropsOrig, 2, xOffset) ++ extendDrops(dropsOrig, 3, xOffset) + val baseFont = new Font(Font.MONOSPACED, Font.BOLD, 80) val attributes = new java.util.HashMap[TextAttribute, Object]() @@ -69,8 +69,8 @@ class RainDropsCP extends ChallengeProvider { val baos = new ByteArrayOutputStream(); val ios = new MemoryCacheImageOutputStream(baos); val writer = new GifSequenceWriter(ios, imgType, 60, true); - for(i <- 0 until 30){ - val yOffset = 5+r.nextInt(5) + for(i <- 0 until 60){ + // val yOffset = 5+r.nextInt(5) val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) val g = canvas.createGraphics() g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) @@ -83,13 +83,13 @@ class RainDropsCP extends ChallengeProvider { for (d <- drops) { g.setColor(d.mkColor) g.drawLine(d.x, d.y, d.x+xOffset, d.y+d.yOffset) - d.x += xOffset - d.y += d.yOffset + d.x += xOffset/2 + d.y += d.yOffset/2 d.color += d.colorChange - if (d.x > width+xOffset || d.y > height+d.yOffset) { - val ySteps = 1 + height / d.yOffset + if (d.x > width || d.y > height) { + val ySteps = (height / d.yOffset) + 1 d.x -= xOffset*ySteps - d.y -= yOffset*ySteps + d.y -= d.yOffset*ySteps } if (d.color > 200 || d.color < 21) {