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) +
+					"&amp;x=' + getEventX(this, event) + '&amp;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