views:

490

answers:

1

For the needs of my program I have created a facility to distort an image and place it on a map (my program is a map based program).

I wrote my own mechanism for placing and distorting the image using three points that are placed on the image, three points that are placed on the map and then what I simply do it create an AffineTransform that transforms the first triangle to the second, in effect placing the image exactly where I want it and transformed to fit what the user wanted.

The problem is that with AffineTransforms you can only perform the most basic of transformations. You can translate, rotate, scale and skew your image. This is enough for most cases but I recently wanted to implement a 4 point transform similar to Photoshop's free transform.

I did some research and found JAI's Perspective transform which with the help of a WarpPerspective I can achieve what I want. I initially had one issue with having the resulting background of the transformed image transparent but i found a solution to that as well. This is the code i have:

package com.hampton.utils;

import java.awt.Dimension;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.SampleModel;
import java.awt.image.renderable.ParameterBlock;

import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.PerspectiveTransform;
import javax.media.jai.PlanarImage;
import javax.media.jai.RasterFactory;
import javax.media.jai.RenderedOp;
import javax.media.jai.WarpPerspective;
import javax.media.jai.operator.CompositeDescriptor;

/**
 * @author Savvas Dalkitsis
 */
public class PerspectiveTransformation {


    public static PlanarImage getPrespective(PlanarImage img) {

        int numBands = img.getSampleModel().getNumBands();

        ParameterBlock pb = new ParameterBlock();
        pb.add(new Float(img.getWidth())).add(new Float(img.getHeight()));
        pb.add(new Byte[]{new Byte((byte)0xFF)});
        RenderedOp alpha = JAI.create("constant", pb);


        pb = new ParameterBlock();
        pb.addSource(img).addSource(img);
        pb.add(alpha).add(alpha).add(Boolean.FALSE);
        pb.add(CompositeDescriptor.DESTINATION_ALPHA_LAST);
        SampleModel sm =
            RasterFactory.createComponentSampleModel(img.getSampleModel(),
                                                     DataBuffer.TYPE_BYTE,
                                                     img.getTileWidth(),
                                                     img.getTileHeight(),
                                                     numBands + 1);
        ColorSpace cs =
            ColorSpace.getInstance(numBands == 1 ?
                                   ColorSpace.CS_GRAY : ColorSpace.CS_sRGB);
        ColorModel cm =
            RasterFactory.createComponentColorModel(DataBuffer.TYPE_BYTE,
                                                    cs, true, false,
                                                    Transparency.BITMASK);
        ImageLayout il = new ImageLayout();
        il.setSampleModel(sm).setColorModel(cm);
        RenderingHints rh = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, il);
        RenderedOp srca = JAI.create("composite", pb, rh);


        Dimension d = new Dimension(img.getWidth(),img.getHeight());
        PerspectiveTransform p =  PerspectiveTransform.getQuadToQuad(0, 0, d.width, 0, d.width, d.height, 0, d.height, 100, 0, 300, 0, d.width, d.height, 0, d.height);
        WarpPerspective wp = new WarpPerspective(p);

        pb = (new ParameterBlock()).addSource(srca);
        pb.add(wp);
        pb.add(Interpolation.getInstance(Interpolation.INTERP_BICUBIC));

        PlanarImage i = (PlanarImage)JAI.create("warp",pb);
        return i;
    }

}

It works (I have hard coded the transformation for the sake of simplicity, later on i will incorporate it with my transformation system only this time i will use 4 points instead of 3).

The problem i have is that so far with my old method all i had to do is store an AffineTransform (that maps the image to the map) and the simply do a g2.drawImage(img,transform) and all was good. It was really fast and it worked like a charm. My problem now is that creating the transformed PlanarImage takes a lot of time (2-3 seconds) and thus is is not useful for real-time use. I know i can simply save the transformed image and then draw that which will be really fast but the reason i want this is that in my 3 point transformation mechanism i provide real time view of the resulting transformation. When the user drags each of the points he immediately sees the resulting image.

My question then is, is there a fast way to use WarpPerspectives in Graphics2D? What I am looking for is something similar to Graphics2D draw(Image,AffineTransform) method. Or if you have a third party image transformation library that performs really fast that would be welcome as well.

+1  A: 

Try switching off the bicubic interpolation - that should save you some time, but it would produce lower quality results.

Before 3D acceleration became widespread, games used smart tricks for drawing images in perspective. Wikipedia has a great article explaining this in detail. I am not aware of a Java-based library utilizing those concepts, but you could implement the affine-mapping version quite easily - just split the original image into several triangles and map each triangle using a different affine transform.

If you want really smooth, correct, anti-aliased preview, I would suggest using a 3D library like Java3D o JOGL.

Roman Zenka
i thought of the triangle based approach but if you think about it a while you will see that it is not correct...You are assuming that the two triangles can be transformed independently from each other while this is not the case... I found a better approach by using the Filters library from Java Image Editor...It is slightly faster but requires me to create an extra buffered image each step. It is faster that this but a memory hog...
Savvas Dalkitsis