Weighted Average smoothing with Java2d
Since my original post I've done a little bit more playing with processing images to try to give them a "vectorised" look. I really didn't change much, just played with the algorithm for choosing pixel colour. The algorithms all work by examining the colours of the current and surrounding pixels to determine what colour to paint the current pixel. The colour selection is performed by classes that implement the following simple interface:
interface Counter {
// add a colour channel value for one of
// the neighbouring pixels
public void add(int aValue);
// calculates the value we should use
// for the current colour channel of the
// current pixel based on its neighbours
public int mostFrequent();
}
The client code that deals this interface invokes the add method 3 times for each pixel - once for each colour channel (red / green /blue). The client code looks like this:
// calculate the colour
private int count(int anOffset, int[] aSurrounding) {
Counter _c = getCounter();
int _length = aSurrounding.length-(2-anOffset);
for (int i=anOffset; i<_length; i+=3) {
_c.add(aSurrounding[i]);
}
return _c.mostFrequent();
}
Here you can see that I can easily supply different implementations of Counter, so I can try different algorithms easily.
The original colour-selection algorithm
The original algorithm simply takes the most frequent value of each colour channel so that, for example, if 51% of the surrounding pixels are bright blue, and the other 49% are bright red, this pixel will be bright red.
class SimpleCounter implements Counter {
int[] values = new int[256];
public SimpleCounter() {
Arrays.fill(values, -1);
}
public void add(int aValue) {
values[aValue] += 1;
}
public int mostFrequent() {
int _highest = -1;
int _colour = -1;
for (int i=0; i<256; i++) {
if (values[i] > _highest) {
_highest = values[i];
_colour = i;
}
}
return _colour;
}
}
This algorithm does produce the effect I'm looking for, but I get some sharp edges and colour bleeding that I don't really want (see example output below).
The weighted-average algorithm
This algorithm weights the values of the surrounding pixels relative to the most common value, so we get a weighted average that does take some account of less popular colours in the neighbourhood of the current pixel.
class WeightedAverageCounter implements Counter {
int[] values = new int[256];
public WeightedAverageCounter() {
Arrays.fill(values, 0);
}
public void add(int aValue) {
values[aValue] += 1;
}
public int mostFrequent() {
int _max = max();
double _weight=0, _colour=0, _count=0;
for (int i=0; i<256; i++) {
if (values[i] > 0) {
_weight = (((double)values[i]) / _max);
_weight = Math.pow(_weight, 5);
_colour += _weight * i;
_count += _weight;
}
}
return (int) (_colour / _count);
}
private int max() {
int _highest = 0;
for (int i=0; i<256; i++) {
if (values[i] > _highest) {
_highest = values[i];
}
}
return _highest;
}
}
The result of the weighted average algorithm is a much smoother colour composition with a reduction in the aliasing effects that can be seen in the more simple algorithm's output. It does have a significant performance penalty however - on my quad core i7 laptop on an image of 600x650 pixels and a 16x16 neighbourhood, the simple algorithm completes in 10 seconds, whereas the weighted-average needs 90 seconds!
Output for comparison
Here are some examples for comparison. First is the original image, then then the image processed with the simple colour selection algorithm, followed by the weighted average algorithm.
It might be a little difficult to see the difference, so here's one final composite image showing the difference close-up and side-by-side
The left-side shows the image processed with the simple algorithm, whilst the right-side shows the smoother result of the weighted-average algorithm.
Comment on this postMortgage/Loan Overpayment Calculator
A few weeks back I was talking with someone who was looking into over-paying a loan. The premise is that, provided your lender allows overpayments, by over-paying you can save a fortune in interest payments.
The amount you can save isn't all that straight-forward to work out, due to the miracle that is compound interest. I knocked up a quick overpayment calculator using gwt.progressive
, which I'm hosting at Amazon S3 with a registered domain name.
Here's one of the classes that's being bound - or "activated" if you like - by gwt.progressive:
public class OverpaymentSummary extends Composite {
interface MyActivator extends WidgetActivator<OverpaymentSummary>{}
MyActivator activator = GWT.create(MyActivator.class);
@RuntimeUiWidget(id="saving") InlineLabel saving;
@RuntimeUiWidget(id="years") InlineLabel years;
@RuntimeUiWidget(id="months") InlineLabel months;
public OverpaymentSummary(Element anElement) {
initWidget(activator.activate(this, anElement));
}
public void setResults(
Money aStandardTotal, int aStandardMonths,
Money anOverpaidTotal, int anOverMonths) {
saving.setText(aStandardTotal.minus(anOverpaidTotal).toString());
int _monthsSaved = aStandardMonths - anOverMonths;
setYears(_monthsSaved/12);
setMonths(_monthsSaved - ((_monthsSaved/12)*12));
removeStyleName("hidden");
}
private void setYears(int aYears) {
years.setText(""+aYears);
}
private void setMonths(int aMonths) {
months.setText(""+aMonths);
}
}
Which is bound onto html that looks like this:
<div class="summary hidden">By overpaying you could save
<span id="saving">???</span>, and reduce your mortgage
term by <span id="years">?</span> years and
<span id="months">?</span> months!</div>
Notice the <span>
elements with the id's "saving", "years", and "months" - these are bound automatically to the three InlineLabel
's annotated with @RuntimeUiWidget
.
Creating a "Vectorisation" effect with Java2D
On my about me page I have a picture of myself wearing a stupid hat and an even more stupid grin. This picture started out as an old photograph from which I removed the background using GIMP, then vectorized using an online tool called Vector Magic.
Vector Magic is a very cool tool - it converts bitmap's to vector images by "tracing" the bitmap. Once vectorised you can rescale the image as large as you like without losing the smooth edges.
Here are the two images of me - on the left is the bitmap (before vectorisation) and on the right is a bitmap of the vectorized image created by Vector Magic (I vectorised the image for the comic-book effect of vectorisation, not in order to use the vector image itself):
Hopefully you can see the effect I'm talking about - large areas of colour are blocked out, and generally you get a kind of comic-book or oil-painting effect.
After vectorizing that image I started wondering how it worked and if I could whip up something myself, and so I began playing with Java2D to see if I could get close.
"Hatching" the image
I began my efforts with a very simple approach of testing pixels in each row and "dragging" a pixel colour across adjacent pixels until I encounter a pixel with a significantly different colour, at which point I swap the current colour and repeat the process.
Thanks to Java2D doing all the heavy lifting, the code is pretty short:
public void hatch() {
// load the image and extract the raster data
BufferedImage _img = loadImage("cat.jpg");
Raster _raster = _img.getData();
// prepare a new raster to write the hatched
// version of the image to
BufferedImage _copy = new BufferedImage(
_img.getWidth(), _img.getHeight(), _img.getType());
WritableRaster _writable = _copy.getRaster();
double[] _current = new double[3];
double[] _previous = new double[3];
double[] _write = new double[3];
int _threshold = 75;
for (int y=0; y<_img.getHeight(); y++) {
for (int x=0; x<_img.getWidth(); x++) {
// for each pixel in the image, decide whether
// to use its original colour, or to drag in the
// colour of a preceding pixel
_current = _raster.getPixel(x, y, _current);
_write = decide(_previous, _current, _threshold);
_writable.setPixel(x, y, _write);
_previous = Arrays.copyOf(_write, _write.length);
}
}
// save the result to a new png file
ImageIO.write(_copy, "png", new File("cross-hatch.png"));
}
private double[] decide(double[] aThis, double[] aThat, int aThreshold) {
for (int i=0; i<aThis.length; i++) {
if (Math.abs(aThis[i] - aThat[i]) > aThreshold) {
return aThat;
}
}
return aThis;
}
This has an interesting arty effect of "hatching" the image, but before the vectorisation effect becomes significant I start to get colour runs, where the colour gets dragged too far.
I played around with combining vertical and horizontal (and even diagonal) hatching with lower thresholds, which again produces a nice arty effect - particularly on hi-res images, but it isn't really the effect i'm looking for.
Below are some (lo-res) examples, using a high threshold value to make the effect more pronounced.
If you look carefully at certain areas of the images you can see the colour runs - for example on the dark area just where the cats leg joins its body at bottom right of the image, you can see the lighter brown vertical lines in the vertical-hatching image, and similar horizontal lines in the horizontal hatching image.
Notice that the cross-hatching image is starting to get the blocky effect somewhat similar to vectorisation, but pushing the thresholds high enough to create significant blocks of colour ruins the overall effect by dragging too much colour across the image.
I tried various techniques to reduce the drag effect - including an inverse-square factor of the distance since the colour was first encountered. This did indeed smooth things quite a bit, but still doesn't achieve the effect i'm looking for. Time for a new approach.
Edge Detection
Next I pondered some obvious techniques like edge detection. The classic mechanism for edge detection is to apply a filter, or convolution kernel, where each pixel is treated with respect to several surrounding pixels. Simple edge detection kernels look something like this:
|
|
---|
Here's what running the "horizontal" edge-detection kernel on the cat image produces:
And the code for that, again made very simple by Java2D's built in convolution support:
public void edgeDetect() throws Exception {
BufferedImage _input = loadImage("cat.jpg");
BufferedImage _horiz = convolve(_input, newHorizontalKernel());
ImageIO.write(_horiz, "png", new File("edge.png"));
}
private BufferedImage convolve(BufferedImage anImage, Kernel aKernel) {
BufferedImage _out = new BufferedImage(
anImage.getWidth(), anImage.getHeight(), anImage.getType());
ConvolveOp _op = new ConvolveOp(aKernel, ConvolveOp.EDGE_NO_OP, null);
_op.filter(anImage, _out);
return _out;
}
private Kernel newHorizontalKernel() {
return new Kernel(3, 3, new float[] {
-2, -4, -2,
0, 0, 0,
2, 4, 2
});
}
private Kernel newVerticalKernel() {
return new Kernel(3, 3, new float[] {
-2, 0, 2,
-4, 0, 4,
-2, 0, 2
});
}
This is a neat result, and probably I could use the edges thus detected as indicators of where to switch colours, but this just seems like a very round-about way of producing what i want. Time for a new approach.
Convolution with a dynamic kernel
After scratching my head for a while I figured that what I really want to do is somewhere between the two approaches outlined above. I want to use a weighted function of the surrounding pixels (like a convolution kernel), but I want to do something a bit more complex than I can achieve with a static convolution kernel.
After quite a bit more monkeying around I finally nailed it. This time I'm examining pixels around the current one, finding the most common value for each colour channel, and giving the current pixel the most common value for each colour channel. Strictly speaking this isn't a convolution operation at all, but the analogy is a useful one.
I get some slightly funky alias effects on some images, so my next task is to smooth that out, but overall the results are very close to what I was after.
Here's the code:
public void vectorEffect()
throws Exception
{
BufferedImage _img = loadImage("cat.jpg");
Raster _raster = _img.getData();
BufferedImage _copy = new BufferedImage(
_img.getWidth(), _img.getHeight(), _img.getType());
WritableRaster _writable = _copy.getRaster();
int _blockWidth = 8;
int _blockHeight = 8;
int _halfWidth = _blockWidth / 2;
int _halfHeight = _blockWidth / 2;
int[] _lens = new int[(_blockWidth * _blockHeight) * 3];
for (int y=0; y<_img.getHeight(); y++) {
for (int x=0; x<_img.getWidth(); x++) {
// get the surround NxM pixel grid as a
// one dimensional array of r,g,b values
_lens = _raster.getPixels(
Math.max(0, x-_halfWidth),
Math.max(0, y-_halfHeight),
Math.min(_img.getWidth()-x, _blockWidth),
Math.min(_img.getHeight()-y, _blockHeight),
_lens
);
_writable.setPixel(x, y, getPixelColour(_lens));
}
}
ImageIO.write(_copy, "png", new File("vector-effect.png"));
}
// choose the colour for the current pixel by finding the most
// common value for each colour channel in the surrounding pixels
private int[] getPixelColour(int[] aSurrounding) {
int[] _result = new int[3];
_result[0] = mostFrequent(0, aSurrounding);
_result[1] = mostFrequent(1, aSurrounding);
_result[2] = mostFrequent(2, aSurrounding);
return _result;
}
// find the most frequent colour level for a given channel
// within the surrounding pixels. offset of 0 gives red channel,
// 1 gives green channel, 2 is blue channel
private int mostFrequent(int anOffset, int[] aSurrounding) {
Counter _c = new Counter();
int _length = aSurrounding.length-(2-anOffset);
for (int i=anOffset; i<_length; i+=3) {
_c.add(aSurrounding[i]);
}
return _c.mostFrequent();
}
// relatively efficient counter for quickly racking up
// a count of occurrences of any given colour level
class Counter {
int[] values = new int[256];
public Counter() {
Arrays.fill(values, -1);
}
public void add(int aValue) {
values[aValue] += 1;
}
public int mostFrequent() {
int _highest = -1;
int _colour = -1;
for (int i=0; i<256; i++) {
if (values[i] > _highest) {
_highest = values[i];
_colour = i;
}
}
return _colour;
}
}
And here's what the resulting "vector-effect" image looks like:
Because of the low resolution of this image, a large grid size quickly reduces the resultant image quality. On larger images (e.g. 8MP photos) a 16x16 or even 32x32 grid produces good results. The time to render is proportional to the grid size though.
Me Again
Here's the results of processing my viking-hat picture with four different grid sizes: 2x2, 4x4, 8x8 and 16x16. You can see that at such a low resolution the image quickly degenerates with a large grid size - I'm looking pretty demonic by the time we get to 16x16:
A High-Resolution Image
An 8MP image (3456x2304) processed with a 16x16 grid takes about 50 seconds on my quad-core i7 laptop. The same image processed with an 8x8 grid takes 27 seconds.
This last image was processed at its full size (3456x2304) with a 16x16 grid, then the result was cropped and resized (by more than 50%) in GIMP to fit the width of my blog.
What's next?
Well its still some way off from the clean look that Vector Magic produces, but I think its a good start. I'm going to try some ideas to de-fuzz and clean up the edges a bit.
After that ... I'm wondering if I can take the next step and identify bounds for the areas of colour, and thus create a vector image from my "vector-effect" bitmaps?
Comment on this postSyntax Highlighting with GWT.Progressive
There are many good syntax-highlighters available for progressively enhancing code on website's such as my blog. On my old blog I used Alex Gorbachev's - which is exceptionally good.
For this blog, I really wanted to write my own. Why? Well, for the same reason I wrote the site-generation software that builds the blog from Markdown templates - because I love programming, and doing this stuff is FUN!
Having just released my GWT.Progressive
library I had two very good reasons to write a syntax-highlighter that works by progressive-enhancement:
- I need good examples of progressive enhancement written in
GWT
usingGWT.Progressive
- Those very same code-samples need syntax-highlighting to aid readability!
For this first attempt I'm not setting out to write the world's greatest syntax-highlighter. My aims are more modest: merely to illustrate the GWT.Progressive
library and add basic syntax-highlighting (Java) capabilities to my blog.
The syntax-highlighter will work as follows:
- After the page is loaded, identify
<pre><code> ... </code></pre>
blocks on my blog. - Try to ascertain if the text in the block is Java, or a similar language (e.g. JavaScript).
- Mark keywords, literals, comments, and a few other choice elements using inline html markup to add css classes, allowing the actual colours and styles to be selected independently of the process of identify the parts to highlight.
The script that kicks this off is loaded by the very last line in the html body of each page on my blog:
<script
type="text/javascript" language="javascript"
src="blog/blog.nocache.js">
</script>
The Java class that uses the GWT.Progressive
library to scan for <pre><code>
blocks is, in full, as follows:
package com.sjl.blog.client.syntax;
import com.google.gwt.core.client.*;
import com.google.gwt.dom.client.*;
import com.knowledgeview.gwt.activator.client.*;
import com.knowledgeview.gwt.activator.client.widgets.*;
@RootBinding(tag="code")
public class Code extends BoundRootPanel {
interface MyActivator extends ElementActivator<Code>{}
static MyActivator activator = GWT.create(MyActivator.class);
static JavaSyntaxHighlighter highlighter =
new JavaSyntaxHighlighter();
public Code(Element anElement) {
setElement(activator.activate(this, anElement));
if (isJavaCode(getElement().getInnerText())) {
getElement().setInnerHTML(
highlighter.highlight(getElement().getInnerText()));
}
}
private boolean isJavaCode(String aString) {
return !aString.trim().startsWith("<");
}
}
The GWT
module descriptor looks like this:
<module rename-to="blog">
<inherits name="com.google.gwt.user.User"/>
<inherits name="com.google.gwt.user.Debug"/>
<inherits name="com.sjl.gwt.progressive.Progressive"/>
<entry-point class="com.sjl.blog.client.BlogApplication" />
<!-- allows the script to be used cross domain -->
<add-linker name="xs" />
</module>
I will take the time to write a better highlighter some time in the future (using ANTLR or similar to generate a parser), but for now I knocked up a simple version using regular expressions.
This highlighter was built by TDD'ing my way through highlighting the different components, starting simple with keywords. The design changed a few times along the way, but in total, including the Code
class, the finished highlighter took around 90 minutes to build.
The slowest part, actually, was round-trip testing and fixing "for real" with GWT production mode, since the difference in regular expression engines between development and production mode caught me out.
Here's the Java syntax highlighter in full:
public class JavaSyntaxHighlighter implements SyntaxHighlighter {
private List<Replacement> replacements;
public JavaSyntaxHighlighter() {
replacements = new ArrayList<Replacement>();
// order is important
replacements.add(new CommentReplacement());
replacements.add(new AnnotationReplacement());
replacements.add(new LiteralReplacement());
replacements.add(new KeywordReplacement());
}
public String highlight(String anInput) {
List<Component> _components = new ArrayList<Component>();
_components.add(new SplittableComponent(anInput));
for (Replacement _r : replacements) {
_components = _r.process(_components);
}
StringBuilder _sb = new StringBuilder();
for (Component _c : _components) {
_sb.append(_c);
}
return _sb.toString();
}
}
interface Component {
boolean canSplit();
String toString();
}
class SplittableComponent implements Component {
protected String value;
public SplittableComponent(String aValue) {
value = aValue;
}
public boolean canSplit() {
return true;
}
public String toString() {
return value;
}
}
class UnsplittableComponent extends SplittableComponent {
public UnsplittableComponent(String aValue) {
super(aValue);
}
public boolean canSplit() {
return false;
}
}
interface Replacement {
public List<Component> process(List<Component> anInput);
}
abstract class AbstractReplacement implements Replacement {
private String cssClass;
public AbstractReplacement(String aCssClass) {
cssClass = aCssClass;
}
protected abstract String replace(String aString);
@Override
public List<Component> process(List<Component> aComponents)
{
List<Component> _result = new ArrayList<Component>();
for (Component _c : aComponents) {
if (_c.canSplit()) {
String _replaced = replace(_c.toString());
_result.addAll(createComponents(_replaced, cssClass));
} else {
_result.add(_c);
}
}
return _result;
}
private List<Component> createComponents(
String anInput, String aClass) {
List<Component> _result = new ArrayList<Component>();
for (String _s : anInput.split("\\[\\[\\[|\\]\\]\\]")) {
if (_s.startsWith("<span class=\"" + aClass)) {
_result.add(new UnsplittableComponent(_s));
} else {
_result.add(new SplittableComponent(_s));
}
}
return _result;
}
protected String replacement(String aClass) {
return "[ [ [<span class=\"" + aClass + "\">$1</span>] ] ]";
}
}
class KeywordReplacement extends AbstractReplacement
{
public static String[] KEYWORDS = new String[] {
"class", "package", "import", "public", "private", "protected",
"interface", "extends", "this", "implements", "throws", "try",
"catch", "finally", "final", "return", "new", "void", "for",
"if", "else", "while", "static", "transient", "synchronized",
"byte", "short", "int ", "char ", "long ", "float ", "double ",
"boolean ", "true", "false", "abstract", "volatile", "switch",
"case"
};
public KeywordReplacement() {
super("sh-keyword");
}
protected String replace(String aString) {
String _result = aString;
for (String _k : KEYWORDS) {
_result = _result.replaceAll("(" + _k + ")",
replacement("sh-keyword"));
}
return _result;
}
}
class LiteralReplacement extends AbstractReplacement {
public LiteralReplacement() {
super("sh-literal");
}
protected String replace(String aString) {
return aString.replaceAll("(\"[^\"]*\")",
replacement("sh-literal"));
}
}
class CommentReplacement extends AbstractReplacement {
public CommentReplacement() {
super("sh-comment");
}
protected String replace(String aString) {
String _result = aString.replaceAll("(//.*)",
replacement("sh-comment"));
_result = _result.replaceAll("(\\/\\*[\\*]?.*\\*\\/)",
replacement("sh-comment"));
return _result;
}
}
class AnnotationReplacement extends AbstractReplacement {
public AnnotationReplacement() {
super("sh-annotation");
}
protected String replace(String aString) {
return aString.replaceAll("(@\\w+)",
replacement("sh-annotation"));
}
}
You can find out more about GWT.Progressive
in the release announcement, or at the github repository.
gwt.progressive
Easy Progressive-Enhancement with GWT
Progressive-enhancement is a technique for delivering a basic website suitable for search-engines and older web-browsers, then layering on polish, effects, interactivity and general goodness using javascript - the idea being to have a graceful degradation of service, not an all-or-nothing experience.
Traditionally GWT
works the other way around - it builds all of the DOM
structure of your web-app in script, so search-engines (and browsers with script disabled) only see an inscrutable mass of script.
GWT.Progressive
helps to bring your ninja GWT
skills to bear on web-sites that need progressive enhancement.
You can get GWT.Progressive
from the git repository at github.
What can GWT.Progressive do for me?
- Bind
GWT
widgets to html elements on static (or server-generated) html pages, using the html as a template andGWT
magic to "enhance" the user-experience - Automatically bind nested html elements to properties of widget classes (child widgets, grand-child widgets, etc)
- Bind repeated html elements to
List<MyWidgetClass>
properties of widget classes - Automatically bind attributes of html elements to properties of widget classes - with automatic type coercion
- Make partial page-updates a breeze (replacing parts of the
DOM
with new server-generated content)
How GWT.Progressive works
Element Binding
You can bind a GWT
widget to any html element on your website - just by adding an annotation to the widget class:
// bind by id
@RootBinding(id="mywidget")
class MyWidget extends Composite {
//...
}
// bind by tag name
@RootBinding(tag="h1")
class MyWidget extends Composite {
//...
}
// bind by css class
@RootBinding(cssClass="bind-me")
class MyWidget extends Composite {
//...
}
// bind by tag name and css class
@RootBinding(tag="div", cssClass="bind-me")
class MyWidget extends Composite {
//...
}
Bind nested html to child widgets:
// bind the first <h1> tag we find by breadth first
// search of the DOM within our widget's root element
@RuntimeUiWidget(tag="h1") MyHeaderWidget header;
// bind the first element we find by breadth first
// search that has the css class "foo"
@RuntimeUiWidget(cssClass="foo") MyFooWidget foo;
Bind repeated elements to a List of child widgets:
// bind all the <img> elements we find inside our
// widget's root element to the list of Image widgets
@RuntimeUiWidget(tag="img") List<Image> images;
Attribute Binding
Bind html attributes as properties of your widgets, with automatic type coercion:
// the following attributes will have their default values (set below)
// overridden by the values of attributes in the html, if they exist
@RuntimeUiAttribute("text-attr") String textAttr = "default";
@RuntimeUiAttribute("int-attr") Integer integerAttr = 22;
@RuntimeUiAttribute("long-attr") Long longAttr = 9L;
@RuntimeUiAttribute("float-attr") Float floatAttr = 2.3f;
@RuntimeUiAttribute("double-attr") Double doubleAttr = 2222222.222d;
@RuntimeUiAttribute("boolean-attr") Boolean booleanAttr = false;
Bind attributes to custom types by supplying converters - given html like: <div coords="200x400"> ... </div>
, automatically bind the coords property of our widget as an instance of Coords, converted by CoordsAttributeConverter...
In the Widget
we're binding:
// automatically bind our custom Coords class
@RuntimeUiAttribute("coords") Coords coords;
The Coords
class:
class Coords {
public Integer x, y;
public Coords(Integer anX, Integer aY) {
x = anX;
y = aY;
}
}
The AttributeConverter
:
@RuntimeUiAttributeConverter
public class CoordsAttributeConverter
extends AttributeConverter<Coords> {
public Coords convert(String aString) {
String[] _parts = aString.split("x");
return new Coords(
Integer.parseInt(_parts[0]),
Integer.parseInt(_parts[1])
);
}
}
Partial Page Updates
Replace chunks of html with new html from the server. Your server can provide this html as it pleases - for example you can call servlets, jsp's, PHP or whatever you like to get the html for the replacement. Your widgets will re-bind to the new html after adding it to the DOM
.
// UiBinder afficionados will be familiar with this plumbing
// which we'll use to invoke our page update...
interface MyActivator extends ElementActivator<Partial>{}
MyActivator activator = GWT.create(MyActivator.class);
// here's the widget we're going to update
@RuntimeUiWidget(tag="div") UpdateMe updateMe;
...
// call this method to trigger the update
private void someMethod() {
activator.update(
updateMe, "/update-me.jsp",
new CallMeWhenUpdated()
);
}
Partial Page Update callbacks
You can register callbacks to trigger when a partial update completes - successfully or otherwise. Here's a more complete partial update example:
@RootBinding(tag="div")
public class Partial extends BoundRootPanel
// UiBinder afficionados will be familiar with this
interface MyActivator extends ElementActivator<Partial>{}
MyActivator activator = GWT.create(MyActivator.class);
@RuntimeUiWidget(tag="div") UpdateMe updateMe;
public Partial(Element anElement)
{
setElement(activator.activate(this, anElement));
// Update the page when we're clicked...
addDomHandler(new ClickHandler() {
public void onClick(ClickEvent aEvent) {
activator.update(
Partial.this, "/update-me.jsp",
new CallMeWhenUpdated()
);
});
}
}, ClickEvent.getType());
class CallMeWhenUpdated
implements PageUpdateCallback<Partial>() {
public void onSuccess(Partial anUpdated) {
Window.alert("Updated!");
}
public void onError(Throwable anExc) {
GWT.log("oops, something bad happened", anExc);
}
}
}
Complete example - Progressive Enhancement with GWT
Here's the HTML we're going to enhance:
<html>
<body>
<div class="widget">
<img class="first" src="/image-1.png">
<img class="second" src="/image-2.png">
</div>
<!-- our script loads last, and progressive
enhancement begins -->
<script
type="text/javascript" language="javascript"
src="example/example.nocache.js?cache=20111112180652">
</script>
</body>
</html>
Here's the widget we're going to bind to the <div>
of css-class "widget":
@RootBinding(cssClass="widget")
class MyWidget extends BoundRootPanel {
interface MyActivator extends ElementActivator<MyWidget>{}
MyActivator activator = GWT.create(MyWidget.class);
@RuntimeUiWidget(cssClass="first") Image first;
@RuntimeUiWidget(cssClass="second") Image second;
public MyWidget(Element anElement) {
setElement(activator.activate(this, anElement);
// ... now we do some enhancements, e.g. add
// click-handlers to our image's or something
}
}
We'll need an EntryPoint - this is still a GWT
application:
class Example1 implements EntryPoint {
public void onModuleLoad() {
PageActivator _activator = GWT.create(PageActivator.class);
_activator.activate();
}
}
... and a GWT
module descriptor (.gwt.xml
) of course:
<module rename-to="example">
<inherits name="com.google.gwt.user.User"/>
<inherits name="com.sjl.gwt.progressive.Progressive"/>
<entry-point class="com.sjl.example.client.Example" />
<!-- for maximum compatibility, use the xs linker -->
<add-linker name="xs" />
</module>
Rules of engagement
GWT.Progressive
requires that you follow a few simple rules to ensure that your widgets get "activated" correctly, integrated into the GWT/browser eventing system, etc.
Your widgets must:
- Have a public
Constructor
that takes acom.google.gwt.dom.client.Element
argument, OR - Have a
public static <T> wrap(Element anElement)
method that returns an instance of your widget class (this is how native GWT widgets like Image and Label are bound by GWT.Progressive). GWT.create()
a special class that allowsGWT.Progressive
's generator to work its magic on your widgets (eithercom.sjl.gwt.progressive.client.ElementActivator
orcom.sjl.gwt.progressive.client.WidgetActivator
).- Set your Widget's Element as the return value from activation (
setElement(activator.activate(this, anElement))
) or init your Composite with it (initWidget(activator.activate(this, anElement))
).
If you've ever used UiBinder, this pattern will be very familiar to you.
Also, make sure to use the "xs" linker by specifying <add-linker name="xs"/>
in your gwt module descriptor for maximum compatibility.
Where can I find more examples of GWT.progressive?
In the github repository, in the test source tree.
Are there any examples in the wild?
There are two examples on this page! - The Twitter feed in the sidebar, and the code syntax-highlighting are both implemented using gwt.progressive
.
You can also try my mortgage/loan overpayment calculator which is built with gwt.progressive.
Can I use this in my products?
Sure, its available under the Apache-2.0 license, and is a github fork away, but ... disclaimer: use at your own risk and discretion, I cannot accept responsibility for any issues that may arise from using this library.
Can I modify the code, fix bugs, etc?
Knock yourself out :) ... I'd love to hear about it if you do.
Final Words
I just want to say - props to my employer (KnowledgeView ltd.) for allowing me to release the code, some of which was developed on their dime. Mucho thanks KV :)
You can find some more background on GWT.Progressive
in some of my earlier blog posts: part-1, part-2, and part-3