You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by jd...@apache.org on 2008/12/21 12:12:28 UTC
svn commit: r728427 - in /wicket/trunk:
wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/
wicket-examples/src/main/webapp/ wicket-examples/src/main/webapp/WEB-INF/
wicket-extensions/src/main/java/org/apache/wicket/extensions/captc...
Author: jdonnerstag
Date: Sun Dec 21 03:12:27 2008
New Revision: 728427
URL: http://svn.apache.org/viewvc?rev=728427&view=rev
Log:
wicket-1610: Kitten-Captcha authentication added
Added:
wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/
wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html
wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.java
wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/WicketApplication.java
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.properties
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/OpaqueRegion.java
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/chick.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/chick_highlight.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/grass.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/guinea_pig.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/guinea_pig_highlight.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/hamster.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/hamster_highlight.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_01.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_01_highlight.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_02.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_02_highlight.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_03.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_03_highlight.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_04.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_04_highlight.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/puppy.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/puppy_highlight.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/rabbit.png (with props)
wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/rabbit_highlight.png (with props)
Modified:
wicket/trunk/wicket-examples/src/main/webapp/WEB-INF/web.xml
wicket/trunk/wicket-examples/src/main/webapp/index.html
Added: wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html?rev=728427&view=auto
==============================================================================
--- wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html (added)
+++ wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html Sun Dec 21 03:12:27 2008
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml" >
+<head>
+ <title>Wicket Examples - Kitten-Captch</title>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+</head>
+<body>
+ <span wicket:id="mainNavigation"/>
+ <div style="font-family: helvetica" wicket:id="captcha"></div>
+ <p/>
+ <a wicket:id="checkKittens">Check kittens!</a>
+</body>
+</html>
+
Added: wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.java?rev=728427&view=auto
==============================================================================
--- wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.java (added)
+++ wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.java Sun Dec 21 03:12:27 2008
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.examples.kittenCaptcha;
+
+import java.awt.Dimension;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.examples.WicketExamplePage;
+import org.apache.wicket.extensions.captcha.kittens.KittenCaptchaPanel;
+
+/**
+ * Kitten captcha example
+ */
+public class HomePage extends WicketExamplePage
+{
+ private static final long serialVersionUID = 1L;
+
+ private final KittenCaptchaPanel captcha;
+ private int errors;
+
+ /**
+ * Constructor that is invoked when page is invoked without a session.
+ *
+ * @param parameters
+ * Page parameters
+ */
+ public HomePage(final PageParameters parameters)
+ {
+ add(captcha = new KittenCaptchaPanel("captcha", new Dimension(400, 200)));
+
+ // In a real application, you'd check the kittens in a form
+ add(new AjaxLink("checkKittens")
+ {
+ private static final long serialVersionUID = 642245961797905032L;
+
+ @Override
+ public void onClick(final AjaxRequestTarget target)
+ {
+ if (!isSpamBot() && captcha.allKittensSelected())
+ {
+ target.appendJavascript("alert('you win! happy kittens!');");
+ }
+ else
+ {
+ errors++;
+ if (isSpamBot())
+ {
+ target.appendJavascript("alert('spammer alert');");
+ }
+ else
+ {
+ target.appendJavascript("alert('please try again');");
+ }
+ target.addComponent(captcha);
+ }
+ captcha.reset();
+ }
+ });
+ }
+
+ /**
+ *
+ * @return
+ */
+ boolean isSpamBot()
+ {
+ return errors > 3;
+ }
+}
Added: wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/WicketApplication.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/WicketApplication.java?rev=728427&view=auto
==============================================================================
--- wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/WicketApplication.java (added)
+++ wicket/trunk/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/WicketApplication.java Sun Dec 21 03:12:27 2008
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.examples.kittenCaptcha;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.protocol.http.WebApplication;
+
+/**
+ * Application object for your web application. If you want to run this application without
+ * deploying, run the Start class.
+ *
+ */
+public class WicketApplication extends WebApplication
+{
+ /**
+ * Constructor
+ */
+ public WicketApplication()
+ {
+ }
+
+ /**
+ *
+ * @see org.apache.wicket.Application#getHomePage()
+ */
+ @Override
+ public Class<? extends Page> getHomePage()
+ {
+ return HomePage.class;
+ }
+
+}
Modified: wicket/trunk/wicket-examples/src/main/webapp/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-examples/src/main/webapp/WEB-INF/web.xml?rev=728427&r1=728426&r2=728427&view=diff
==============================================================================
--- wicket/trunk/wicket-examples/src/main/webapp/WEB-INF/web.xml (original)
+++ wicket/trunk/wicket-examples/src/main/webapp/WEB-INF/web.xml Sun Dec 21 03:12:27 2008
@@ -412,6 +412,15 @@
<param-value>org.apache.wicket.examples.dates.DatesApplication</param-value>
</init-param>
</filter>
+
+ <filter>
+ <filter-name>KittenCaptcha</filter-name>
+ <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
+ <init-param>
+ <param-name>applicationClassName</param-name>
+ <param-value>org.apache.wicket.examples.kittenCaptcha.WicketApplication</param-value>
+ </init-param>
+ </filter>
<!-- The WicketSesionFilter can be used to provide thread local access to servlets/ JSPs/ etc -->
<filter>
@@ -701,6 +710,13 @@
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
+ <filter-mapping>
+ <filter-name>KittenCaptcha</filter-name>
+ <url-pattern>/kitten-captcha/*</url-pattern>
+ <dispatcher>REQUEST</dispatcher>
+ <dispatcher>INCLUDE</dispatcher>
+ </filter-mapping>
+
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Modified: wicket/trunk/wicket-examples/src/main/webapp/index.html
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-examples/src/main/webapp/index.html?rev=728427&r1=728426&r2=728427&view=diff
==============================================================================
--- wicket/trunk/wicket-examples/src/main/webapp/index.html (original)
+++ wicket/trunk/wicket-examples/src/main/webapp/index.html Sun Dec 21 03:12:27 2008
@@ -46,6 +46,7 @@
<tr><td align="right"><a href="customresourceloading">custom template loading</a></td><td> - Demonstrates custom template loading.</td></tr>
<tr><td align="right"><a href="breadcrumb">breadcrumb</a></td><td> - Don't get lost, use bread-crumbs.</td></tr>
<tr><td align="right"><a href="captcha">captcha</a></td><td> - Image-based "captcha" to distinguish humans from spammers.</td></tr>
+ <tr><td align="right"><a href="kitten-captcha">kitten-captcha</a></td><td> - Another approach to captchas</td></tr>
<tr><td align="right"><a href="authentication">authentication</a></td><td> - Demonstrates authentication for pages.</td></tr>
<tr><td align="right"><a href="authorization">authorization</a></td><td> - Demonstrates authorization for pages and components.</td></tr>
<tr><td align="right"><a href="dates">dates</a></td><td> - Date component example from the wicket-date project.</td></tr>
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html?rev=728427&view=auto
==============================================================================
--- wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html (added)
+++ wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html Sun Dec 21 03:12:27 2008
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns:wicket="http://wicket.sourceforge.net/">
+<wicket:head>
+ <script type="text/javascript">
+ function getEventX(element, event) {
+ if (event.offsetX != null) {
+ return event.offsetX;
+ }
+ return event.pageX - element.offsetLeft;
+ }
+ function getEventY(element, event) {
+ if (event.offsetY != null) {
+ return event.offsetY;
+ }
+ return event.pageY - element.offsetTop;
+ }
+ function getImage() {
+ return Wicket.$("imageContainer").getElementsByTagName("img")[0];
+
+ }
+ function showLoadingIndicator() {
+ Wicket.$('loading').style.visibility="visible";
+ }
+ function hideLoadingIndicator() {
+ Wicket.$('loading').style.visibility="hidden";
+ }
+ </script>
+</wicket:head>
+<body>
+<wicket:panel>
+ <div wicket:id="animalSelectionLabel"></div>
+ <p/>
+ <div id="imageContainer">
+ <img src="" wicket:id="image"></img>
+ <p/>
+ <div id="loading">
+ <wicket:message key="pleaseWait"></wicket:message>
+ </div>
+ </div>
+ <script type="text/javascript">
+ Wicket.Event.add(getImage(), "load", function() { hideLoadingIndicator(); } );
+ </script>
+</wicket:panel>
+</body>
+</html>
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java?rev=728427&view=auto
==============================================================================
--- wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java (added)
+++ wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java Sun Dec 21 03:12:27 2008
@@ -0,0 +1,807 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.extensions.captcha.kittens;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.awt.image.RescaleOp;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import javax.imageio.ImageIO;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+
+import org.apache.wicket.IResourceListener;
+import org.apache.wicket.RequestCycle;
+import org.apache.wicket.ajax.AjaxEventBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.image.Image;
+import org.apache.wicket.markup.html.image.NonCachingImage;
+import org.apache.wicket.markup.html.image.resource.DynamicImageResource;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.AbstractReadOnlyModel;
+import org.apache.wicket.protocol.http.WebRequest;
+import org.apache.wicket.protocol.http.WebResponse;
+import org.apache.wicket.util.time.Time;
+
+/**
+ * A unique and fun-to-use captcha technique I developed at Thoof.
+ *
+ * @author Jonathan Locke
+ */
+public class KittenCaptchaPanel extends Panel
+{
+ private static final long serialVersionUID = 2711167040323855070L;
+
+ // The background grass area
+ private static BufferedImage grass = load("images/grass.png");
+
+ // The kittens and other animals
+ private static final List<Animal> kittens = new ArrayList<Animal>();
+ private static final List<Animal> nonKittens = new ArrayList<Animal>();
+
+ // Random number generator
+ private static Random random = new Random(-1);
+
+ // Load animals
+ static
+ {
+ kittens.add(new Animal("kitten_01", true));
+ kittens.add(new Animal("kitten_02", true));
+ kittens.add(new Animal("kitten_03", true));
+ kittens.add(new Animal("kitten_04", true));
+ nonKittens.add(new Animal("chick", false));
+ nonKittens.add(new Animal("guinea_pig", false));
+ nonKittens.add(new Animal("hamster", false));
+ nonKittens.add(new Animal("puppy", false));
+ nonKittens.add(new Animal("rabbit", false));
+ }
+
+ /**
+ * @param filename
+ * The name of the file to load
+ * @return The image read form the file
+ */
+ private static BufferedImage load(final String filename)
+ {
+ try
+ {
+ return ImageIO.read(new MemoryCacheImageInputStream(
+ KittenCaptchaPanel.class.getResourceAsStream(filename)));
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * The various animals as placed animals
+ */
+ private final PlacedAnimalList animals;
+
+ /**
+ * Label that shows request status
+ */
+ private final Label animalSelectionLabel;
+
+ /**
+ * The image component
+ */
+ private final Image image;
+
+ /**
+ * The image resource referenced by the Image component
+ */
+ private final CaptchaImageResource imageResource;
+
+ /**
+ * Size of this kitten panel's image
+ */
+ private final Dimension imageSize;
+
+ /**
+ * @param id
+ * Component id
+ * @param imageSize
+ * Size of kitten captcha image
+ */
+ public KittenCaptchaPanel(final String id, final Dimension imageSize)
+ {
+ super(id);
+
+ // Save image size
+ this.imageSize = imageSize;
+
+ // Create animal list
+ animals = new PlacedAnimalList();
+
+ // Need to ajax refresh
+ setOutputMarkupId(true);
+
+ // Show how many animals have been selected
+ animalSelectionLabel = new Label("animalSelectionLabel", new AbstractReadOnlyModel()
+ {
+ private static final long serialVersionUID = 6792322972316712326L;
+
+ @Override
+ public Object getObject()
+ {
+ return imageResource.selectString();
+ }
+ });
+ animalSelectionLabel.setOutputMarkupId(true);
+ add(animalSelectionLabel);
+
+ // Image referencing captcha image resource
+ image = new NonCachingImage("image", imageResource = new CaptchaImageResource(animals));
+ image.add(new AjaxEventBehavior("onclick")
+ {
+ private static final long serialVersionUID = 7480352029955897654L;
+
+ @Override
+ protected CharSequence getCallbackScript(boolean onlyTargetActivePage)
+ {
+ // Call-back script shows loading indicator and makes wicket
+ // ajax request passing in mouse co-ordinates
+ return generateCallbackScript("showLoadingIndicator(); wicketAjaxGet('" +
+ getCallbackUrl(onlyTargetActivePage) +
+ "&x=' + getEventX(this, event) + '&y=' + getEventY(this, event)");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void onEvent(final AjaxRequestTarget target)
+ {
+ // Get clicked cursor position
+ final WebRequest request = (WebRequest)RequestCycle.get().getRequest();
+ final Map<String, String[]> parameters = request.getParameterMap();
+ final int x = Integer.parseInt(parameters.get("x")[0]);
+ final int y = Integer.parseInt(parameters.get("y")[0]);
+
+ // Force refresh
+ imageResource.clearData();
+
+ // Find any animal at the clicked location
+ final PlacedAnimal animal = animals.atLocation(new Point(x, y));
+
+ // If the user clicked on an animal
+ if (animal != null)
+ {
+ // Toggle the animal's highlighting
+ animal.isHighlighted = !animal.isHighlighted;
+
+ // Instead of reload entire image just change the src
+ // attribute, this reduces the flicker
+ final StringBuilder javascript = new StringBuilder();
+ javascript.append("Wicket.$('" + image.getMarkupId() + "').src = '");
+ javascript.append(image.urlFor(IResourceListener.INTERFACE));
+ javascript.append("&rand=" + Math.random());
+ javascript.append("'");
+ target.appendJavascript(javascript.toString());
+ }
+ else
+ {
+ // The user didn't click on an animal, so hide the loading
+ // indicator
+ target.appendJavascript(" hideLoadingIndicator();");
+ }
+
+ // Update the selection label
+ target.addComponent(animalSelectionLabel);
+ }
+ });
+ add(image);
+ }
+
+ /**
+ * @return True if all (three) kittens have been selected
+ */
+ public boolean allKittensSelected()
+ {
+ return imageResource.allKittensSelected();
+ }
+
+ /**
+ * Resets for another go-around
+ */
+ public void reset()
+ {
+ imageResource.reset();
+ }
+
+ /**
+ * @param animals
+ * List of animals
+ * @param newAnimal
+ * New animal to place
+ * @return The placed animal
+ */
+ private PlacedAnimal placeAnimal(final List<PlacedAnimal> animals, final Animal newAnimal)
+ {
+ // Try 100 times
+ for (int iter = 0; iter < 100; iter++)
+ {
+ // Get the new animal's width and height
+ final int width = newAnimal.image.getWidth();
+ final int height = newAnimal.image.getHeight();
+
+ // Pick a random position
+ final int x = random(imageSize.width - width);
+ final int y = random(imageSize.height - height);
+ final Point point = new Point(x, y);
+
+ // Determine if there is too much overlap with other animals
+ final double tooClose = new Point(width, height).distance(new Point(0, 0)) / 2.0;
+ boolean tooMuchOverlap = false;
+ for (final PlacedAnimal animal : animals)
+ {
+ if (point.distance(animal.location) < tooClose)
+ {
+ tooMuchOverlap = true;
+ break;
+ }
+ }
+
+ // If there was not too much overlap
+ if (!tooMuchOverlap)
+ {
+ // The animal is now placed at x, y
+ return new PlacedAnimal(newAnimal, new Point(x, y));
+ }
+ }
+
+ // Could not place animal
+ return null;
+ }
+
+ /**
+ * @param max
+ * Maximum size of random value
+ * @return A random number between 0 and max - 1
+ */
+ private int random(final int max)
+ {
+ return Math.abs(random.nextInt(max));
+ }
+
+ /**
+ * @return A random kitten
+ */
+ private Animal randomKitten()
+ {
+ return kittens.get(random(kittens.size()));
+ }
+
+ /**
+ * @return A random other animal
+ */
+ private Animal randomNonKitten()
+ {
+ return nonKittens.get(random(nonKittens.size()));
+ }
+
+ /**
+ * Animal, whether kitten or non-kitten
+ */
+ private static class Animal
+ {
+ /**
+ * The highlighted image
+ */
+ private final BufferedImage highlightedImage;
+
+ /**
+ * The normal image
+ */
+ private final BufferedImage image;
+
+ /**
+ * True if the animal is a kitten
+ */
+ private final boolean isKitten;
+
+ /**
+ * The visible region of the animal
+ */
+ private final OpaqueRegion visibleRegion;
+
+ /**
+ * @param filename
+ * The filename
+ * @param isKitten
+ * True if the animal is a kitten
+ */
+ private Animal(final String filename, final boolean isKitten)
+ {
+ this.isKitten = isKitten;
+ image = load("images/" + filename);
+ highlightedImage = load("images/" + filename + "_highlight");
+ visibleRegion = new OpaqueRegion(image);
+ }
+
+ /**
+ * @param filename
+ * The file to load
+ * @return The image in the file
+ */
+ private BufferedImage load(final String filename)
+ {
+ try
+ {
+ final BufferedImage loadedImage = ImageIO.read(new MemoryCacheImageInputStream(
+ KittenCaptchaPanel.class.getResourceAsStream(filename + ".png")));
+ final BufferedImage image = new BufferedImage(loadedImage.getWidth(),
+ loadedImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ final Graphics2D graphics = image.createGraphics();
+ graphics.drawImage(loadedImage, 0, 0, null);
+ graphics.dispose();
+ return image;
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Resource which renders the actual captcha image
+ */
+ private class CaptchaImageResource extends DynamicImageResource
+ {
+ private static final long serialVersionUID = -1560784998742404278L;
+
+ /**
+ * The placed animals
+ */
+ private final PlacedAnimalList animals;
+
+ /**
+ * Image data array
+ */
+ private transient SoftReference<byte[]> data = null;
+
+ /**
+ * @param animals
+ * The positioned animals
+ */
+ private CaptchaImageResource(final PlacedAnimalList animals)
+ {
+ this.animals = animals;
+ setCacheable(false);
+ setFormat("jpg");
+ }
+
+ /**
+ * @return Rendered image data
+ */
+ @Override
+ protected byte[] getImageData()
+ {
+ // Handle caching
+ setLastModifiedTime(Time.now());
+ final WebResponse response = (WebResponse)RequestCycle.get().getResponse();
+ response.setHeader("Cache-Control", "no-cache, must-revalidate, max-age=0, no-store");
+
+ // If we don't have data
+ if (data == null || data.get() == null)
+ {
+ // Create the image and turn it into data
+ final BufferedImage composedImage = animals.createImage();
+ data = new SoftReference<byte[]>(toImageData(composedImage));
+ }
+
+ // Return image data
+ return data.get();
+ }
+
+ /**
+ * Invalidates the image data
+ */
+ @Override
+ protected void invalidate()
+ {
+ data = null;
+ }
+
+ /**
+ * @return True if all kittens have been selected
+ */
+ private boolean allKittensSelected()
+ {
+ return animals.allKittensSelected();
+ }
+
+ /**
+ * Clears out image data
+ */
+ private void clearData()
+ {
+ invalidate();
+ setLastModifiedTime(Time.now());
+ }
+
+ /**
+ * Resets animals to default states
+ */
+ private void reset()
+ {
+ animals.reset();
+ }
+
+ /**
+ * @return Selection state string for animals
+ */
+ private String selectString()
+ {
+ return animals.selectString();
+ }
+ }
+
+ /**
+ * An animal that has a location
+ */
+ private class PlacedAnimal implements Serializable
+ {
+ private static final long serialVersionUID = -6703909440564862486L;
+
+ /**
+ * The animal
+ */
+ private transient Animal animal;
+
+ /**
+ * Index in kitten or nonKitten list
+ */
+ private final int index;
+
+ /**
+ * True if the animal is highlighted
+ */
+ private boolean isHighlighted;
+
+ /**
+ * True if this animal is a kitten
+ */
+ private final boolean isKitten;
+
+ /**
+ * The location of the animal
+ */
+ private final Point location;
+
+ /**
+ * Scaling values
+ */
+ private final float[] scales = { 1f, 1f, 1f, 1f };
+
+ /**
+ * @param animal
+ * The animal
+ * @param location
+ * Where to put it
+ */
+ public PlacedAnimal(final Animal animal, final Point location)
+ {
+ this.animal = animal;
+ this.location = location;
+ isKitten = animal.isKitten;
+ if (isKitten)
+ {
+ index = kittens.indexOf(animal);
+ }
+ else
+ {
+ index = nonKittens.indexOf(animal);
+ }
+ for (int i = 0; i < 3; i++)
+ {
+ scales[i] = random(0.9f, 1.0f);
+ }
+ scales[3] = random(0.7f, 1.0f);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return (isKitten ? "kitten at " : "other at ") + location.x + ", " + location.y;
+ }
+
+ /**
+ * @param point
+ * The point
+ * @return True if this placed animal contains the given point
+ */
+ private boolean contains(final Point point)
+ {
+ final Point relativePoint = new Point(point.x - location.x, point.y - location.y);
+ return getAnimal().visibleRegion.contains(relativePoint);
+ }
+
+ /**
+ * @param graphics
+ * The graphics to draw on
+ */
+ private void draw(final Graphics2D graphics)
+ {
+ final float[] offsets = new float[4];
+ final RescaleOp rop = new RescaleOp(scales, offsets, null);
+ if (isHighlighted)
+ {
+ graphics.drawImage(getAnimal().highlightedImage, rop, location.x, location.y);
+ }
+ else
+ {
+ graphics.drawImage(getAnimal().image, rop, location.x, location.y);
+ }
+ }
+
+ /**
+ * @return The animal that is placed
+ */
+ private Animal getAnimal()
+ {
+ if (animal == null)
+ {
+ if (isKitten)
+ {
+ animal = kittens.get(index);
+ }
+ else
+ {
+ animal = nonKittens.get(index);
+ }
+ }
+ return animal;
+ }
+
+ /**
+ * @param min
+ * Minimum random value
+ * @param max
+ * Maximum random value
+ * @return A random value in the given range
+ */
+ private float random(float min, float max)
+ {
+ return min + Math.abs(random.nextFloat() * (max - min));
+ }
+ }
+
+ /**
+ * Holds a list of placed animals
+ */
+ private class PlacedAnimalList implements Serializable
+ {
+ private static final long serialVersionUID = 6335852594326213439L;
+
+ /**
+ * List of placed animals
+ */
+ private final List<PlacedAnimal> animals = new ArrayList<PlacedAnimal>();
+
+ /**
+ * Arrange random animals and kittens
+ */
+ private PlacedAnimalList()
+ {
+ // Place the three kittens
+ animals.add(placeAnimal(animals, randomKitten()));
+ animals.add(placeAnimal(animals, randomKitten()));
+ animals.add(placeAnimal(animals, randomKitten()));
+
+ // Try a few times
+ for (int iter = 0; iter < 500; iter++)
+ {
+ // Place a non kitten
+ final PlacedAnimal animal = placeAnimal(animals, randomNonKitten());
+
+ // If we were able to place the animal
+ if (animal != null)
+ {
+ // add it to the list
+ animals.add(animal);
+ }
+
+ // 15 non-kittens is enough
+ if (animals.size() > 15)
+ {
+ break;
+ }
+ }
+
+ // Shuffle the animal order
+ Collections.shuffle(animals);
+
+ // Ensure kittens are visible enough
+ List<PlacedAnimal> strayKittens = new ArrayList<PlacedAnimal>();
+ for (final PlacedAnimal animal : animals)
+ {
+ // If it's a kitten
+ if (animal.isKitten)
+ {
+ // Compute the area of the visible region in pixels
+ final int kittenArea = animal.getAnimal().visibleRegion.areaInPixels();
+
+ // If at least 4/5ths of the given kitten is not visible
+ // (because it is obscured by other animal(s))
+ if (visibleRegion(animal).areaInPixels() < kittenArea * 4 / 5)
+ {
+ // The user probably can't identify it, so add to the
+ // stray kittens list
+ strayKittens.add(animal);
+ }
+ }
+ }
+
+ // Remove any the stray kittens and then re-add them so they move to
+ // the top of the z-order
+ animals.removeAll(strayKittens);
+ animals.addAll(strayKittens);
+ }
+
+ /**
+ * @return True if all kittens are selected
+ */
+ private boolean allKittensSelected()
+ {
+ for (final PlacedAnimal animal : animals)
+ {
+ if (animal.isKitten != animal.isHighlighted)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param location
+ * The cursor location that was clicked
+ * @return Any animal that might be at the given location or null if none found (the user
+ * clicked on grass)
+ */
+ private PlacedAnimal atLocation(final Point location)
+ {
+ // Reverse list for z-ordered hit-testing
+ final List<PlacedAnimal> reversedAnimals = new ArrayList<PlacedAnimal>(animals);
+ Collections.reverse(reversedAnimals);
+
+ // Return any animal at the given location
+ for (final PlacedAnimal animal : reversedAnimals)
+ {
+ if (animal.contains(location))
+ {
+ return animal;
+ }
+ }
+
+ // No animal found
+ return null;
+ }
+
+ /**
+ * @return The kitten captcha image
+ */
+ private BufferedImage createImage()
+ {
+ // Create image of the right size
+ final BufferedImage newImage = new BufferedImage(imageSize.width, imageSize.height,
+ BufferedImage.TYPE_INT_RGB);
+
+ // Draw the grass
+ final Graphics2D graphics = newImage.createGraphics();
+ graphics.drawImage(grass, 0, 0, null);
+
+ // Draw each animal in order
+ for (final PlacedAnimal animal : animals)
+ {
+ animal.draw(graphics);
+ }
+
+ // Clean up graphics resource
+ graphics.dispose();
+
+ // Return the rendered animals
+ return newImage;
+ }
+
+ /**
+ * Undo highlight states of animals
+ */
+ private void reset()
+ {
+ for (final PlacedAnimal animal : animals)
+ {
+ animal.isHighlighted = false;
+ }
+ }
+
+ /**
+ * @return Selection string to show
+ */
+ private String selectString()
+ {
+ int selected = 0;
+ for (final PlacedAnimal animal : animals)
+ {
+ if (animal.isHighlighted)
+ {
+ selected++;
+ }
+ }
+ if (selected == 0)
+ {
+ return getString("instructions");
+ }
+ else
+ {
+ return selected + " " + getString("animalsSelected");
+ }
+ }
+
+ /**
+ * @param animal
+ * The animal
+ * @return The visible region of the animal
+ */
+ private OpaqueRegion visibleRegion(final PlacedAnimal animal)
+ {
+ // The index of the animal in the animal list
+ int index = animals.indexOf(animal);
+
+ // Check sanity
+ if (index == -1)
+ {
+ // Invalid animal somehow
+ throw new IllegalArgumentException("animal not in list");
+ }
+ else
+ {
+ // Get the animal's visible region
+ OpaqueRegion visible = animal.getAnimal().visibleRegion;
+
+ // Go through the animals above the given animal
+ for (index++; index < animals.size(); index++)
+ {
+
+ // Remove the higher animal's visible region
+ final PlacedAnimal remove = animals.get(index);
+ visible = visible.subtract(remove.getAnimal().visibleRegion, new Point(
+ remove.location.x - animal.location.x, remove.location.y -
+ animal.location.y));
+ }
+ return visible;
+ }
+ }
+ }
+}
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.properties
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.properties?rev=728427&view=auto
==============================================================================
--- wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.properties (added)
+++ wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.properties Sun Dec 21 03:12:27 2008
@@ -0,0 +1,3 @@
+pleaseWait=Please wait...
+animalsSelected=of 3 animals selected
+instructions=Select all three kittens below
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/OpaqueRegion.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/OpaqueRegion.java?rev=728427&view=auto
==============================================================================
--- wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/OpaqueRegion.java (added)
+++ wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/OpaqueRegion.java Sun Dec 21 03:12:27 2008
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.extensions.captcha.kittens;
+
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.util.string.StringList;
+
+/**
+ * Processes a buffered image with alpha transparency by scan-lines, creating a simple rectangle
+ * list enclosing all opaque pixels. The list is used by {@link #contains(Point)} to do hit testing.
+ * An effective z-ordering of hit test regions is enabled by {@link #subtract(OpaqueRegion, Point)}.
+ *
+ * @author Jonathan Locke
+ */
+class OpaqueRegion
+{
+ /**
+ * The list of rectangles in this region
+ */
+ private final List<Rectangle> rectangles;
+
+ /**
+ * @param image
+ * The image to process
+ * @throws IllegalArgumentException
+ * Thrown if image is not TYPE_INT_ARGB
+ */
+ OpaqueRegion(final BufferedImage image)
+ {
+ // Check image type
+ if (image.getType() != BufferedImage.TYPE_INT_ARGB)
+ {
+ throw new IllegalArgumentException("image must be TYPE_INT_ARGB");
+ }
+
+ // Initialize rectangle list
+ rectangles = new ArrayList<Rectangle>();
+
+ // Get color model for image
+ final ColorModel colorModel = image.getColorModel();
+
+ // Process scan-lines
+ final int dx = image.getWidth();
+ final int dy = image.getHeight();
+ for (int y = 0; y < dy; y++)
+ {
+ // No start line yet
+ int startx = -1;
+
+ // Process pixels in scan line
+ for (int x = 0; x < dx; x++)
+ {
+ // Get pixel
+ final int pixel = image.getRGB(x, y);
+
+ // If the pixel is opaque
+ if (colorModel.getAlpha(pixel) > 0)
+ {
+ // If no opaque rectangle has been started
+ if (startx == -1)
+ {
+ // start one at the current pixel
+ startx = x;
+ }
+ }
+ else
+ {
+ // Pixel is transparent. If we started a rectangle
+ if (startx != -1)
+ {
+ // close the rectangle and add it to the list
+ rectangles.add(new Rectangle(startx, y, x - startx - 1, 1));
+
+ // The next rectangle is not started yet
+ startx = -1;
+ }
+ }
+ }
+
+ // If there is a rectangle open still
+ if (startx != -1)
+ {
+ // close the rectangle and add it to the list
+ rectangles.add(new Rectangle(startx, y, dx - startx - 1, 1));
+ }
+ }
+ }
+
+ /**
+ * @param rectangles
+ * List of rectangles in opaque area
+ */
+ private OpaqueRegion(final List<Rectangle> rectangles)
+ {
+ this.rectangles = rectangles;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return StringList.valueOf(rectangles).join();
+ }
+
+ /**
+ * @return The total area in pixels of this non-rectangular opaque region
+ */
+ int areaInPixels()
+ {
+ int area = 0;
+ for (final Rectangle rectangle : rectangles)
+ {
+ area += rectangle.width * rectangle.height;
+ }
+ return area;
+ }
+
+ /**
+ * @param point
+ * The point to hit test
+ * @return True if this opaque region contains the point
+ */
+ boolean contains(final Point point)
+ {
+ for (final Rectangle rectangle : rectangles)
+ {
+ if (rectangle.contains(point.x, point.y))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return The height of this opaque region in pixels
+ */
+ int height()
+ {
+ int height = 0;
+ for (final Rectangle rectangle : rectangles)
+ {
+ int y2 = rectangle.y + rectangle.height;
+ if (y2 > height)
+ {
+ height = y2;
+ }
+ }
+ return height;
+ }
+
+ /**
+ * @param removeRegion
+ * The region to subtract from this one
+ * @param offset
+ * An offset to apply to the remove region
+ * @return This region with the given region removed
+ */
+ OpaqueRegion subtract(final OpaqueRegion removeRegion, final Point offset)
+ {
+ // Create new rectangle list
+ final List<Rectangle> newRectangles = new ArrayList<Rectangle>();
+
+ // For each rectangle
+ for (final Rectangle rectangle : rectangles)
+ {
+ // Get y
+ final int y = rectangle.y;
+
+ // Work list starts with the current rectangle in this opaque region
+ final List<Rectangle> workList = new ArrayList<Rectangle>();
+ workList.add(new Rectangle(rectangle));
+
+ // For each rectangle in the region to remove
+ for (final Rectangle remove : removeRegion.rectangles)
+ {
+ // Offset the rectangle
+ final Rectangle offsetRemove = new Rectangle(remove);
+ offsetRemove.translate(offset.x, offset.y);
+
+ // If we've gone past a possible match
+ if (offsetRemove.y > y)
+ {
+ // quit
+ break;
+ }
+
+ // If we're processing a rectangle on the right scan-line
+ if (offsetRemove.y == y)
+ {
+ // Get rectangle x1 and x2
+ int rx1 = offsetRemove.x;
+ int rx2 = offsetRemove.x + offsetRemove.width;
+
+ // Go through work list to remove the given rectangle
+ for (int i = 0; i < workList.size(); i++)
+ {
+ // Get rectangle from work list
+ final Rectangle work = workList.get(i);
+
+ // Get work x1, x2
+ int x1 = work.x;
+ int x2 = work.x + work.width;
+
+ // Compare left and right sides
+ if (rx1 <= x1 && rx2 >= x2)
+ {
+ // Whole rectangle is obscured
+ workList.remove(i);
+ }
+ else
+ {
+ // Check which sides are in
+ boolean leftIn = rx1 >= x1 && rx1 < x2;
+ boolean rightIn = rx2 > x1 && rx2 <= x2;
+
+ if (leftIn)
+ {
+ if (rightIn)
+ {
+ // Split in two
+ if (rx1 - x1 > 0)
+ {
+ workList.set(i, new Rectangle(x1, y, rx1 - x1, 1));
+ }
+
+ if (x2 - rx2 > 0)
+ {
+ workList.add(i + 1, new Rectangle(rx2, y, x2 - rx2, 1));
+ }
+
+ }
+ else
+ {
+ // Chop off right side
+ if (rx1 - x1 > 0)
+ {
+ workList.set(i, new Rectangle(x1, y, rx1 - x1, 1));
+ }
+ }
+ }
+ else if (rightIn)
+ {
+ // Chop off left side
+ if (x2 - rx2 > 0)
+ {
+ workList.set(i, new Rectangle(rx2, y, x2 - rx2, 1));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Add the rectangle(s) to the new list
+ newRectangles.addAll(workList);
+ }
+
+ // Return new opaque region
+ return new OpaqueRegion(newRectangles);
+ }
+
+ /**
+ * @return A visual representation of this region for debugging purposes
+ */
+ BufferedImage toDebugImage()
+ {
+ // Create a new image the same size as this region
+ final int dx = width();
+ final int dy = height();
+ final BufferedImage image = new BufferedImage(dx, dy, BufferedImage.TYPE_INT_ARGB);
+
+ // Black out all pixels
+ for (int y = 0; y < dy; y++)
+ {
+ for (int x = 0; x < dx; x++)
+ {
+ image.setRGB(x, y, 0);
+ }
+ }
+
+ // Make pixels purple
+ for (final Rectangle rectangle : rectangles)
+ {
+ for (int x = rectangle.x; x < rectangle.x + rectangle.width; x++)
+ {
+ image.setRGB(x, rectangle.y, 0xff00ff00);
+ }
+ }
+
+ // Return debug image
+ return image;
+ }
+
+ /**
+ * @return Width of this opaque region in pixels
+ */
+ int width()
+ {
+ int width = 0;
+ for (final Rectangle rectangle : rectangles)
+ {
+ int x2 = rectangle.x + rectangle.width;
+ if (x2 > width)
+ {
+ width = x2;
+ }
+ }
+ return width;
+ }
+}
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/chick.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/chick.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/chick.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/chick_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/chick_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/chick_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/grass.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/grass.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/grass.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/guinea_pig.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/guinea_pig.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/guinea_pig.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/guinea_pig_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/guinea_pig_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/guinea_pig_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/hamster.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/hamster.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/hamster.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/hamster_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/hamster_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/hamster_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_01.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_01.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_01.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_01_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_01_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_01_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_02.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_02.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_02.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_02_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_02_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_02_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_03.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_03.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_03.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_03_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_03_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_03_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_04.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_04.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_04.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_04_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_04_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/kitten_04_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/puppy.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/puppy.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/puppy.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/puppy_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/puppy_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/puppy_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/rabbit.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/rabbit.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/rabbit.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/rabbit_highlight.png
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/rabbit_highlight.png?rev=728427&view=auto
==============================================================================
Binary file - no diff available.
Propchange: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/images/rabbit_highlight.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream