You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cocoon.apache.org by Steven Dolg <st...@indoqa.com> on 2009/01/14 22:09:03 UTC

[C3] Imaging Module

Hi,

I planned to come up with an Imaging Module for quite some time now.
There were several situations in some of the projects I worked on where 
such a module would have been handy IMO.
I also thought this might serve as another example of how the Pipeline 
API could be used.


I did not invest much time (only a couple of hours this afternoon) and 
I'm actually not very versatile in using the Imaging API and/or Java2D.
So please don't blame me when the implementation of the transformations 
is not ideal or not really fast.
(Actually if someone know how to implement some or all of those 
manipulations better, I'd love to hear about that).
The transformers still miss alot of configuration possiblities (most of 
it is simply hardwired) and are not really suited to be used in the 
Sitemap, yet.
But I intend to continue working on this...


So far I created the following transformers:
* CropImageTransformer
    Crops the image to an area of the configured size. The area is 
centered on the image

* MaxSizeImageTransformer
    Reduces the size of the image so that it fits inside the given 
dimensions. The aspect ratio of the image is maintained. If the image 
already fits inside the given bound it will remain unchanged.

*RotateImageTransformer
    Rotates the whole image by a given amount. Any value is possible.

*ScaleImageTransformer
    Scales the image by a given factor, maininting the aspect ratio.

*WatermarkImageTransformer
    Write a configurable text centered on the image. Color and 
transparancy of the text is configurable.


Additionally there is an ImageGenerator and an ImageSerializer.
Both ImageGenerator and ImageSerializer use the Imaging API of Java. So 
by default the image formats JPG, PNG, GIF and BMP are supported (IIRC).
Additional formats may be supported by registering them with the Imaging 
API.

All the components mentioned above a fully compatible to each other - 
and not compatible to any other Cocoon 3 components currently available 
- and the transformers can be combined in every way.
The basic rules of the PipelineAPI still apply of course (Generator -> 
Transformer* -> Serializer)

I have provided a unit test that uses several pipelines using all 
components.

Attached you can find the patch for the code and a sample image (in case 
you have none handy).
The generator uses the URL mechanism already implemented in other 
generators in Cocoon 3, so images can also be loaded directly from a web 
site or some other location.


Regards,
Steven

Re: [C3] Imaging Module

Posted by Simone Tripodi <si...@gmail.com>.
Hi Steven,
that's really cool, I like it very much, confratulations!
The most important thing is IMHO the Pipeline APIs are adaptable to
build various kind of Pipeline not just SAX/XML... and that's great!
Thanks to share your work with us!
Best regards,
Simone

2009/1/14 Steven Dolg <st...@indoqa.com>:
> Hi,
>
> I planned to come up with an Imaging Module for quite some time now.
> There were several situations in some of the projects I worked on where such
> a module would have been handy IMO.
> I also thought this might serve as another example of how the Pipeline API
> could be used.
>
>
> I did not invest much time (only a couple of hours this afternoon) and I'm
> actually not very versatile in using the Imaging API and/or Java2D.
> So please don't blame me when the implementation of the transformations is
> not ideal or not really fast.
> (Actually if someone know how to implement some or all of those
> manipulations better, I'd love to hear about that).
> The transformers still miss alot of configuration possiblities (most of it
> is simply hardwired) and are not really suited to be used in the Sitemap,
> yet.
> But I intend to continue working on this...
>
>
> So far I created the following transformers:
> * CropImageTransformer
>   Crops the image to an area of the configured size. The area is centered on
> the image
>
> * MaxSizeImageTransformer
>   Reduces the size of the image so that it fits inside the given dimensions.
> The aspect ratio of the image is maintained. If the image already fits
> inside the given bound it will remain unchanged.
>
> *RotateImageTransformer
>   Rotates the whole image by a given amount. Any value is possible.
>
> *ScaleImageTransformer
>   Scales the image by a given factor, maininting the aspect ratio.
>
> *WatermarkImageTransformer
>   Write a configurable text centered on the image. Color and transparancy of
> the text is configurable.
>
>
> Additionally there is an ImageGenerator and an ImageSerializer.
> Both ImageGenerator and ImageSerializer use the Imaging API of Java. So by
> default the image formats JPG, PNG, GIF and BMP are supported (IIRC).
> Additional formats may be supported by registering them with the Imaging
> API.
>
> All the components mentioned above a fully compatible to each other - and
> not compatible to any other Cocoon 3 components currently available - and
> the transformers can be combined in every way.
> The basic rules of the PipelineAPI still apply of course (Generator ->
> Transformer* -> Serializer)
>
> I have provided a unit test that uses several pipelines using all
> components.
>
> Attached you can find the patch for the code and a sample image (in case you
> have none handy).
> The generator uses the URL mechanism already implemented in other generators
> in Cocoon 3, so images can also be loaded directly from a web site or some
> other location.
>
>
> Regards,
> Steven
>
> Index:
> cocoon-imaging/src/test/java/org/apache/cocoon/imaging/PipelineTest.java
> ===================================================================
> --- cocoon-imaging/src/test/java/org/apache/cocoon/imaging/PipelineTest.java
>    (revision 0)
> +++ cocoon-imaging/src/test/java/org/apache/cocoon/imaging/PipelineTest.java
>    (revision 0)
> @@ -0,0 +1,130 @@
> +package org.apache.cocoon.imaging;
> +
> +import java.io.File;
> +import java.io.FileOutputStream;
> +
> +import org.apache.cocoon.imaging.components.CropImageTransformer;
> +import org.apache.cocoon.imaging.components.MaxSizeImageTransformer;
> +import org.apache.cocoon.imaging.components.RotateImageTransformer;
> +import org.apache.cocoon.imaging.components.ScaleImageTransformer;
> +import org.apache.cocoon.imaging.components.WatermarkImageTransformer;
> +import org.apache.cocoon.pipeline.NonCachingPipeline;
> +import org.apache.cocoon.pipeline.Pipeline;
> +import org.junit.Test;
> +
> +public class PipelineTest {
> +
> +    @Test
> +    public void writeUnchanged() throws Exception {
> +        Pipeline pipeline = new NonCachingPipeline();
> +
> +        // just read and write the image without changing it
> +        // this might be used to change the image format, etc. as soon as
> this is supported by the
> +        // ImageSerializer
> +        pipeline.addComponent(new ImageGenerator(new
> File("src/test/resources/sample.JPG").toURI().toURL()));
> +        pipeline.addComponent(new ImageSerializer());
> +
> +        FileOutputStream outputStream = new
> FileOutputStream("target/unchanged.jpg");
> +        pipeline.setup(outputStream);
> +        pipeline.execute();
> +        outputStream.close();
> +    }
> +
> +    @Test
> +    public void addWatermark() throws Exception {
> +        Pipeline pipeline = new NonCachingPipeline();
> +
> +        pipeline.addComponent(new ImageGenerator(new
> File("src/test/resources/sample.JPG").toURI().toURL()));
> +        // write the given text centered on the image, using the specified
> color and alpha values
> +        pipeline.addComponent(new WatermarkImageTransformer("Cocoon 3
> Imaging", "0xFFFFFF", 128));
> +        pipeline.addComponent(new ImageSerializer());
> +
> +        FileOutputStream outputStream = new
> FileOutputStream("target/watermark.jpg");
> +        pipeline.setup(outputStream);
> +        pipeline.execute();
> +        outputStream.close();
> +    }
> +
> +    @Test
> +    public void scaleImage() throws Exception {
> +        Pipeline pipeline = new NonCachingPipeline();
> +
> +        pipeline.addComponent(new ImageGenerator(new
> File("src/test/resources/sample.JPG").toURI().toURL()));
> +        // scale the image to 50% of the original size, maintaining the
> aspect ratio
> +        pipeline.addComponent(new ScaleImageTransformer(0.5));
> +        pipeline.addComponent(new ImageSerializer());
> +
> +        FileOutputStream outputStream = new
> FileOutputStream("target/scaled.jpg");
> +        pipeline.setup(outputStream);
> +        pipeline.execute();
> +        outputStream.close();
> +    }
> +
> +    @Test
> +    public void cropImage() throws Exception {
> +        Pipeline pipeline = new NonCachingPipeline();
> +
> +        pipeline.addComponent(new ImageGenerator(new
> File("src/test/resources/sample.JPG").toURI().toURL()));
> +        // reduce the image to the given area (centered)
> +        pipeline.addComponent(new CropImageTransformer(600, 400));
> +        pipeline.addComponent(new ImageSerializer());
> +
> +        FileOutputStream outputStream = new
> FileOutputStream("target/cropped.jpg");
> +        pipeline.setup(outputStream);
> +        pipeline.execute();
> +        outputStream.close();
> +    }
> +
> +    @Test
> +    public void createThumbnail() throws Exception {
> +        Pipeline pipeline = new NonCachingPipeline();
> +
> +        pipeline.addComponent(new ImageGenerator(new
> File("src/test/resources/sample.JPG").toURI().toURL()));
> +        // scale the image so that it's size does not exceed the given
> values, maintaining aspect
> +        // ration
> +        pipeline.addComponent(new MaxSizeImageTransformer(100, 100));
> +        pipeline.addComponent(new ImageSerializer());
> +
> +        FileOutputStream outputStream = new
> FileOutputStream("target/thumbnail.jpg");
> +        pipeline.setup(outputStream);
> +        pipeline.execute();
> +        outputStream.close();
> +    }
> +
> +    @Test
> +    public void rotateImage() throws Exception {
> +        Pipeline pipeline = new NonCachingPipeline();
> +
> +        pipeline.addComponent(new ImageGenerator(new
> File("src/test/resources/sample.JPG").toURI().toURL()));
> +        // rotate the image by the given amount in radians (negative
> rotates counterclockwise)
> +        // will change the size of the image if necessary (iow if the angle
> is not a multiple of PI)
> +        pipeline.addComponent(new RotateImageTransformer(-Math.PI / 2));
> +        pipeline.addComponent(new ImageSerializer());
> +
> +        FileOutputStream outputStream = new
> FileOutputStream("target/rotated.jpg");
> +        pipeline.setup(outputStream);
> +        pipeline.execute();
> +        outputStream.close();
> +    }
> +
> +    @Test
> +    public void multipleTransformations() throws Exception {
> +        Pipeline pipeline = new NonCachingPipeline();
> +
> +        pipeline.addComponent(new ImageGenerator(new
> File("src/test/resources/sample.JPG").toURI().toURL()));
> +        // scale to 15% of the original size
> +        pipeline.addComponent(new ScaleImageTransformer(0.15));
> +        // rotate by 45° counterclockwise, fill the remaining area with the
> given color
> +        pipeline.addComponent(new RotateImageTransformer(-Math.PI / 4,
> "0xFF8000"));
> +        // add the given text to the image, using the given color and alpha
> values
> +        pipeline.addComponent(new WatermarkImageTransformer("Cocoon 3
> Imaging", "0x1C93BB", 255));
> +        // resize to so that is fits into the given size
> +        pipeline.addComponent(new MaxSizeImageTransformer(150, 150));
> +        pipeline.addComponent(new ImageSerializer());
> +
> +        FileOutputStream outputStream = new
> FileOutputStream("target/multiple.jpg");
> +        pipeline.setup(outputStream);
> +        pipeline.execute();
> +        outputStream.close();
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\test\java\org\apache\cocoon\imaging\PipelineTest.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index: cocoon-imaging/src/test/resources/sample.JPG
> ===================================================================
> Cannot display: file marked as a binary type.
> svn:mime-type = image/jpeg
>
> Property changes on: cocoon-imaging\src\test\resources\sample.JPG
> ___________________________________________________________________
> Added: svn:mime-type
>   + image/jpeg
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/utils/ImageUtils.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/utils/ImageUtils.java
>        (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/utils/ImageUtils.java
>        (revision 0)
> @@ -0,0 +1,32 @@
> +package org.apache.cocoon.imaging.utils;
> +
> +import java.awt.geom.AffineTransform;
> +import java.awt.image.AffineTransformOp;
> +import java.awt.image.BufferedImage;
> +import java.awt.image.BufferedImageOp;
> +import java.awt.image.ColorModel;
> +import java.awt.image.WritableRaster;
> +
> +public class ImageUtils {
> +
> +    public static BufferedImage createCompatibleImage(BufferedImage image,
> int targetWidth, int targetHeight) {
> +        ColorModel colorModel = image.getColorModel();
> +        WritableRaster raster =
> colorModel.createCompatibleWritableRaster(targetWidth, targetHeight);
> +
> +        return new BufferedImage(colorModel, raster,
> colorModel.isAlphaPremultiplied(), null);
> +    }
> +
> +    public static BufferedImage getScaledImage(BufferedImage image, double
> scaleFactor) {
> +        // calculate the target size
> +        int targetWidth = (int) (image.getWidth() * scaleFactor);
> +        int targetHeight = (int) (image.getHeight() * scaleFactor);
> +
> +        // build the target image
> +        BufferedImage result = createCompatibleImage(image, targetWidth,
> targetHeight);
> +
> +        // apply the transformation
> +        BufferedImageOp op = new
> AffineTransformOp(AffineTransform.getScaleInstance(scaleFactor,
> scaleFactor),
> +                AffineTransformOp.TYPE_BICUBIC);
> +        return op.filter(image, result);
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\utils\ImageUtils.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/WatermarkImageTransformer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/WatermarkImageTransformer.java
>    (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/WatermarkImageTransformer.java
>    (revision 0)
> @@ -0,0 +1,58 @@
> +package org.apache.cocoon.imaging.components;
> +
> +import java.awt.Color;
> +import java.awt.Font;
> +import java.awt.Graphics2D;
> +import java.awt.RenderingHints;
> +import java.awt.font.LineMetrics;
> +import java.awt.geom.Rectangle2D;
> +import java.awt.image.BufferedImage;
> +
> +import org.apache.cocoon.imaging.AbstractImageTransformer;
> +
> +public class WatermarkImageTransformer extends AbstractImageTransformer {
> +
> +    private final int alpha;
> +    private final String color;
> +    private final String text;
> +
> +    public WatermarkImageTransformer() {
> +        this("WATERMARK", "0xFF0000", 80);
> +    }
> +
> +    public WatermarkImageTransformer(String text, String color, int alpha)
> {
> +        super();
> +
> +        this.color = color;
> +        this.text = text;
> +        this.alpha = alpha;
> +    }
> +
> +    /**
> +     * {@inheritDoc}
> +     *
> +     * @see
> org.apache.cocoon.imaging.AbstractImageTransformer#transformImage(java.awt.image.BufferedImage)
> +     */
> +    @Override
> +    protected BufferedImage transformImage(BufferedImage bufferedImage) {
> +        int width = bufferedImage.getWidth();
> +        int height = bufferedImage.getHeight();
> +
> +        Graphics2D graphics = bufferedImage.createGraphics();
> +        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
> RenderingHints.VALUE_ANTIALIAS_ON);
> +        graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
> RenderingHints.VALUE_RENDER_QUALITY);
> +
> +        Color rawColor = Color.decode(this.color);
> +        graphics.setColor(new Color(rawColor.getRed(), rawColor.getGreen(),
> rawColor.getBlue(), this.alpha));
> +        graphics.setFont(new Font("Arial", Font.BOLD | Font.ITALIC, 24));
> +
> +        Rectangle2D stringBounds =
> graphics.getFontMetrics().getStringBounds(this.text, graphics);
> +        LineMetrics lineMetrics =
> graphics.getFontMetrics().getLineMetrics(this.text, graphics);
> +
> +        int x = (int) (width - stringBounds.getWidth()) / 2;
> +        int y = (int) ((height - stringBounds.getHeight()) / 2 +
> lineMetrics.getAscent());
> +        graphics.drawString(this.text, x, y);
> +
> +        return bufferedImage;
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\components\WatermarkImageTransformer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/RotateImageTransformer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/RotateImageTransformer.java
>       (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/RotateImageTransformer.java
>       (revision 0)
> @@ -0,0 +1,61 @@
> +package org.apache.cocoon.imaging.components;
> +
> +import java.awt.Color;
> +import java.awt.Graphics2D;
> +import java.awt.geom.AffineTransform;
> +import java.awt.geom.Rectangle2D;
> +import java.awt.image.AffineTransformOp;
> +import java.awt.image.BufferedImage;
> +import java.awt.image.BufferedImageOp;
> +
> +import org.apache.cocoon.imaging.AbstractImageTransformer;
> +import org.apache.cocoon.imaging.utils.ImageUtils;
> +
> +public class RotateImageTransformer extends AbstractImageTransformer {
> +
> +    private final String background;
> +    private final double radians;
> +
> +    public RotateImageTransformer() {
> +        this(Math.PI / 2);
> +    }
> +
> +    public RotateImageTransformer(double radians) {
> +        this(radians, null);
> +    }
> +
> +    public RotateImageTransformer(double radians, String background) {
> +        super();
> +        this.radians = radians;
> +        this.background = background;
> +    }
> +
> +    @Override
> +    protected BufferedImage transformImage(BufferedImage image) {
> +        // create the transformation for rotating the image
> +        AffineTransform transformation =
> AffineTransform.getRotateInstance(this.radians);
> +
> +        // calculate the target bounds after the rotation
> +        Rectangle2D transformedBounds =
> transformation.createTransformedShape(
> +                new Rectangle2D.Double(0, 0, image.getWidth(),
> image.getHeight())).getBounds2D();
> +
> +        // create the target image
> +        int targetWidth = (int) transformedBounds.getWidth();
> +        int targetHeight = (int) transformedBounds.getHeight();
> +        BufferedImage result = ImageUtils.createCompatibleImage(image,
> targetWidth, targetHeight);
> +
> +        if (this.background != null) {
> +            // fill with background
> +            Graphics2D graphics = result.createGraphics();
> +            graphics.setColor(Color.decode(this.background));
> +            graphics.fillRect(0, 0, targetWidth, targetHeight);
> +        }
> +
> +        // prepend a transformation that moves the image back to the (0, 0)
> coordinates
> +
>  transformation.preConcatenate(AffineTransform.getTranslateInstance(-transformedBounds.getX(),
> -transformedBounds.getY()));
> +
> +        // apply the transformation
> +        BufferedImageOp op = new AffineTransformOp(transformation,
> AffineTransformOp.TYPE_BICUBIC);
> +        return op.filter(image, result);
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\components\RotateImageTransformer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/MaxSizeImageTransformer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/MaxSizeImageTransformer.java
>      (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/MaxSizeImageTransformer.java
>      (revision 0)
> @@ -0,0 +1,58 @@
> +package org.apache.cocoon.imaging.components;
> +
> +import java.awt.image.BufferedImage;
> +
> +import org.apache.cocoon.imaging.AbstractImageTransformer;
> +import org.apache.cocoon.imaging.utils.ImageUtils;
> +
> +public class MaxSizeImageTransformer extends AbstractImageTransformer {
> +
> +    private int maxHeight;
> +    private int maxWidth;
> +
> +    public MaxSizeImageTransformer() {
> +        this(-1, -1);
> +    }
> +
> +    public MaxSizeImageTransformer(int maxWidth, int maxHeight) {
> +        super();
> +
> +        this.maxHeight = maxHeight;
> +        this.maxWidth = maxWidth;
> +    }
> +
> +    @Override
> +    protected BufferedImage transformImage(BufferedImage image) {
> +        double scaleFactor = this.getScaleFactor(image);
> +        if (scaleFactor == 1) {
> +            // no scaling needed
> +            return image;
> +        }
> +
> +        return ImageUtils.getScaledImage(image, scaleFactor);
> +    }
> +
> +    private double getScaleFactor(BufferedImage image) {
> +        int imageWidth = image.getWidth();
> +        int imageHeight = image.getHeight();
> +
> +        if (this.maxHeight == -1 && this.maxWidth == -1) {
> +            return 1;
> +        }
> +
> +        if (imageWidth <= this.maxWidth && imageHeight <= this.maxHeight) {
> +            return 1;
> +        }
> +
> +        double imageRatio = (double) imageWidth / imageHeight;
> +        double maxRatio = (double) this.maxWidth / this.maxHeight;
> +
> +        if (imageRatio > maxRatio) {
> +            // scale by width
> +            return (double) this.maxWidth / image.getWidth();
> +        }
> +
> +        // scale by height
> +        return (double) this.maxHeight / image.getHeight();
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\components\MaxSizeImageTransformer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/CropImageTransformer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/CropImageTransformer.java
> (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/CropImageTransformer.java
> (revision 0)
> @@ -0,0 +1,36 @@
> +package org.apache.cocoon.imaging.components;
> +
> +import java.awt.image.BufferedImage;
> +
> +import org.apache.cocoon.imaging.AbstractImageTransformer;
> +
> +public class CropImageTransformer extends AbstractImageTransformer {
> +
> +    private int cropHeight;
> +    private int cropWidth;
> +
> +    public CropImageTransformer(int cropWidth, int cropHeight) {
> +        super();
> +
> +        this.cropWidth = cropWidth;
> +        this.cropHeight = cropHeight;
> +    }
> +
> +    @Override
> +    protected BufferedImage transformImage(BufferedImage image) {
> +        int imageWidth = image.getWidth();
> +        int imageHeight = image.getHeight();
> +
> +        if (imageWidth < this.cropWidth && imageHeight < this.cropHeight) {
> +            return image;
> +        }
> +
> +        int targetWidth = Math.min(this.cropWidth, imageWidth);
> +        int targetHeight = Math.min(this.cropHeight, imageHeight);
> +
> +        int dx = (imageWidth - targetWidth) / 2;
> +        int dy = (imageHeight - targetHeight) / 2;
> +
> +        return image.getSubimage(dx, dy, targetWidth, targetHeight);
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\components\CropImageTransformer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/ScaleImageTransformer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/ScaleImageTransformer.java
>        (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/components/ScaleImageTransformer.java
>        (revision 0)
> @@ -0,0 +1,25 @@
> +package org.apache.cocoon.imaging.components;
> +
> +import java.awt.image.BufferedImage;
> +
> +import org.apache.cocoon.imaging.AbstractImageTransformer;
> +import org.apache.cocoon.imaging.utils.ImageUtils;
> +
> +public class ScaleImageTransformer extends AbstractImageTransformer {
> +
> +    private double scaleFactor;
> +
> +    public ScaleImageTransformer(double scaleFactor) {
> +        super();
> +        this.scaleFactor = scaleFactor;
> +    }
> +
> +    @Override
> +    protected BufferedImage transformImage(BufferedImage image) {
> +        if (this.scaleFactor <= 0) {
> +            return image;
> +        }
> +
> +        return ImageUtils.getScaledImage(image, this.scaleFactor);
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\components\ScaleImageTransformer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageProducer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageProducer.java
> (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageProducer.java
> (revision 0)
> @@ -0,0 +1,7 @@
> +package org.apache.cocoon.imaging;
> +
> +import org.apache.cocoon.pipeline.component.Producer;
> +
> +public interface ImageProducer extends Producer {
> +
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\ImageProducer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/AbstractImageTransformer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/AbstractImageTransformer.java
>        (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/AbstractImageTransformer.java
>        (revision 0)
> @@ -0,0 +1,13 @@
> +package org.apache.cocoon.imaging;
> +
> +import java.awt.image.BufferedImage;
> +
> +public abstract class AbstractImageTransformer extends
> AbstractImageProducer implements ImageConsumer {
> +
> +    public void process(BufferedImage image) {
> +        BufferedImage transformedImage = this.transformImage(image);
> +        this.getConsumer().process(transformedImage);
> +    }
> +
> +    protected abstract BufferedImage transformImage(BufferedImage image);
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\AbstractImageTransformer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageGenerator.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageGenerator.java
>  (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageGenerator.java
>  (revision 0)
> @@ -0,0 +1,40 @@
> +package org.apache.cocoon.imaging;
> +
> +import java.awt.image.BufferedImage;
> +import java.io.IOException;
> +import java.net.URL;
> +
> +import javax.imageio.ImageIO;
> +
> +import org.apache.cocoon.imaging.utils.ImageUtils;
> +import org.apache.cocoon.pipeline.ProcessingException;
> +import org.apache.cocoon.pipeline.component.Starter;
> +
> +public class ImageGenerator extends AbstractImageProducer implements
> Starter {
> +
> +    private final URL source;
> +
> +    public ImageGenerator() {
> +        this(null);
> +    }
> +
> +    public ImageGenerator(URL source) {
> +        super();
> +        this.source = source;
> +    }
> +
> +    public void execute() {
> +        BufferedImage image;
> +        try {
> +            image = ImageIO.read(this.source);
> +        } catch (IOException e) {
> +            throw new ProcessingException("Could not load image from " +
> this.source, e);
> +        }
> +
> +        // make sure the WritableRaster of the image is okay
> +        BufferedImage bufferedImage =
> ImageUtils.createCompatibleImage(image, image.getWidth(),
> image.getHeight());
> +        image.copyData(bufferedImage.getRaster());
> +
> +        this.getConsumer().process(bufferedImage);
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\ImageGenerator.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/AbstractImageProducer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/AbstractImageProducer.java
>   (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/AbstractImageProducer.java
>   (revision 0)
> @@ -0,0 +1,24 @@
> +package org.apache.cocoon.imaging;
> +
> +import org.apache.cocoon.pipeline.SetupException;
> +import org.apache.cocoon.pipeline.component.AbstractPipelineComponent;
> +import org.apache.cocoon.pipeline.component.Consumer;
> +
> +public abstract class AbstractImageProducer extends
> AbstractPipelineComponent implements ImageProducer {
> +
> +    private ImageConsumer consumer;
> +
> +    public final void setConsumer(Consumer consumer) {
> +        if (!(consumer instanceof ImageConsumer)) {
> +            throw new SetupException("ImageProducer cannot accept consumer
> of type "
> +                    + (consumer == null ? "null" : consumer.getClass() + ".
> A component of type " + ImageConsumer.class
> +                            + " is required."));
> +        }
> +
> +        this.consumer = (ImageConsumer) consumer;
> +    }
> +
> +    protected ImageConsumer getConsumer() {
> +        return this.consumer;
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\AbstractImageProducer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageSerializer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageSerializer.java
> (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageSerializer.java
> (revision 0)
> @@ -0,0 +1,33 @@
> +package org.apache.cocoon.imaging;
> +
> +import java.awt.image.BufferedImage;
> +import java.io.IOException;
> +import java.io.OutputStream;
> +
> +import javax.imageio.ImageIO;
> +
> +import org.apache.cocoon.pipeline.ProcessingException;
> +import org.apache.cocoon.pipeline.component.AbstractPipelineComponent;
> +import org.apache.cocoon.pipeline.component.Finisher;
> +
> +public class ImageSerializer extends AbstractPipelineComponent implements
> ImageConsumer, Finisher {
> +
> +    private OutputStream outputStream;
> +
> +    public String getContentType() {
> +        return "image/jpeg";
> +    }
> +
> +    public void process(BufferedImage image) {
> +        try {
> +            ImageIO.write(image, "JPG", this.outputStream);
> +            this.outputStream.flush();
> +        } catch (IOException e) {
> +            throw new ProcessingException("Failed to write image data to
> the output.", e);
> +        }
> +    }
> +
> +    public void setOutputStream(OutputStream outputStream) {
> +        this.outputStream = outputStream;
> +    }
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\ImageSerializer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index:
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageConsumer.java
> ===================================================================
> ---
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageConsumer.java
> (revision 0)
> +++
> cocoon-imaging/src/main/java/org/apache/cocoon/imaging/ImageConsumer.java
> (revision 0)
> @@ -0,0 +1,11 @@
> +package org.apache.cocoon.imaging;
> +
> +import java.awt.image.BufferedImage;
> +
> +import org.apache.cocoon.pipeline.component.Consumer;
> +
> +public interface ImageConsumer extends Consumer {
> +
> +    void process(BufferedImage image);
> +
> +}
>
> Property changes on:
> cocoon-imaging\src\main\java\org\apache\cocoon\imaging\ImageConsumer.java
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/plain
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index: cocoon-imaging/pom.xml
> ===================================================================
> --- cocoon-imaging/pom.xml      (revision 0)
> +++ cocoon-imaging/pom.xml      (revision 0)
> @@ -0,0 +1,65 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!--
> +  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.
> + -->
> +<!-- $Id$ -->
> +<project xmlns="http://maven.apache.org/POM/4.0.0"
> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
> http://maven.apache.org/maven-v4_0_0.xsd">
> +
> +  <modelVersion>4.0.0</modelVersion>
> +  <packaging>jar</packaging>
> +
> +  <parent>
> +    <groupId>org.apache.cocoon.parent</groupId>
> +    <artifactId>cocoon-parent</artifactId>
> +    <version>3.0.0-alpha-2-SNAPSHOT</version>
> +    <relativePath>../parent</relativePath>
> +  </parent>
> +
> +  <groupId>org.apache.cocoon.imaging</groupId>
> +  <artifactId>cocoon-imaging</artifactId>
> +  <name>Cocoon 3: Imaging</name>
> +  <version>3.0.0-alpha-2-SNAPSHOT</version>
> +  <description>Cocoon 3 Imaging pipeline components.</description>
> +
> +  <dependencies>
> +    <dependency>
> +      <groupId>org.apache.cocoon.pipeline</groupId>
> +      <artifactId>cocoon-pipeline</artifactId>
> +    </dependency>
> +    <dependency>
> +      <groupId>commons-logging</groupId>
> +      <artifactId>commons-logging</artifactId>
> +      <exclusions>
> +        <exclusion>
> +          <artifactId>logkit</artifactId>
> +          <groupId>logkit</groupId>
> +        </exclusion>
> +        <exclusion>
> +          <artifactId>avalon-framework</artifactId>
> +          <groupId>avalon-framework</groupId>
> +        </exclusion>
> +      </exclusions>
> +    </dependency>
> +
> +    <dependency>
> +      <groupId>junit</groupId>
> +      <artifactId>junit</artifactId>
> +      <scope>test</scope>
> +    </dependency>
> +  </dependencies>
> +</project>
>
> Property changes on: cocoon-imaging\pom.xml
> ___________________________________________________________________
> Added: svn:mime-type
>   + text/xml
> Added: svn:keywords
>   + Id
> Added: svn:eol-style
>   + native
>
> Index: pom.xml
> ===================================================================
> --- pom.xml     (revision 734187)
> +++ pom.xml     (working copy)
> @@ -40,6 +40,7 @@
>     <module>cocoon-stringtemplate</module>
>     <module>parent</module>
>     <module>cocoon-sax</module>
> +    <module>cocoon-imaging</module>
>   </modules>
>
>   <build>
>
>



-- 
My LinkedIn profile: http://www.linkedin.com/in/simonetripodi
My GoogleCode profile: http://code.google.com/u/simone.tripodi/
My Picasa: http://picasaweb.google.com/simone.tripodi/
My Tube: http://www.youtube.com/user/stripodi
My Del.icio.us: http://del.icio.us/simone.tripodi

Re: [C3] Imaging Module

Posted by Andy Stevens <in...@googlemail.com>.
2009/1/20 Steven Dolg <st...@indoqa.com>:
> Andy Stevens schrieb:
>> 2009/1/19 Steven Dolg <st...@indoqa.com>:
>>> Vadim Gritsenko schrieb:
>>>> On Jan 15, 2009, at 9:05 AM, Reinhard Pötz wrote:
>>>> But question is, can you aggregate, include images?
>>>
>>> There is no component for that yet. But I'm confident this is easily
>>> possible.
>>
>> Interesting problem - what exactly would an "aggregate" image be?  The
>> relevant parts side by side, one below the other, as separate layers
>> in a combined image?  If the latter, what "mode" would you use to
>> combine them - blend/burn/multiply/as a mask - and what opacities?
>> Should they be scaled to the same size before combining or not?
>> Doesn't really fit with the usual map:aggregate/map:part without a ton
>> of extra info as to how it should be done.
>>
>
> Yes, we could easily end up with having thousands of possible ways users
> would like to use.
>
> I thought about this and implemented a simple first approach
> * use one image as the "background"
> * use one image as the "overlay"
> * define an anchor for the overlay (Center, North, South, Southeast, ...)
> relative to the background
> * allow for an additional offset to the anchor
> * draw the overlay on the background, overwriting it - but applying existing
> transparency information in the overlay
>
> [This is also the way Amazon does it:
> e.g.
> http://ecx.images-amazon.com/images/I/41CHSFA4GTL._BO2,204,203,200_PIsitb-sticker-arrow-click,TopRight,35,-76_AA240_SH20_OU01_.jpg
> "TopRight,35,-75" is anchor, offsetX, offsetY - try changing it, it's fun
> ;-)]
>
> In the imaging module this would look like:
>       Pipeline pipeline = new NonCachingPipeline();
>
>       // the background
>       pipeline.addComponent(new ImageGenerator(backgroundUrl));
>       // scale the background to a fixed size (to have a predictable
> "background-to-overlay" size ratio)
>       pipeline.addComponent(new MaxSizeImageTransformer(200, 200));
>       // apply the overlay (TopRight, slightly beyond the right and the top
> border)
>       pipeline.addComponent(new AggregateImageTransformer(additionalUrl,
> Anchor.NORTHEAST, 22, -20));
>       // scale again to fixed size (overlay increases image size)
>       pipeline.addComponent(new MaxSizeImageTransformer(200, 200));
>       // write the image
>       pipeline.addComponent(new ImageSerializer());
>
> Theoretically this should allow to create what you called "combined images"
> (if I understood your idea correct) as well as the "side by side" approach.
> And this would only require 3 additional parameters (making the offset
> optional would reduce it to one required parameter).

Yes, that'd work, although it's more akin to (in the "old" ways)
<map:generate src="firstimage.jpg"/>
<map:transform type="combineimages" src="secondimage.jpg">
<map:parameter ... (settings for how to position/combine them) />
...
rather than
<map:aggregate>
<map:part src="firstimage.jpg"/>
<map:part src="secondimage.jpg"/>
...

But perhaps I should have mentioned that I've not been following the
C3 stuff, so I've no idea how things are done there...  It was just
the concept of image-based pipelines rather than XML-based ones that
caught my imagination :-)

> However blend/burn/multiply/as mask would certainly require more (and me
> finding out what this actually means and how to achieve that :-) ).

I was thinking along the lines of aggregation being equivalent to
combining the images as layers, in which case there are a number of
ways they could be combined e.g. see
http://docs.gimp.org/en/gimp-concepts-layer-modes.html

> As to the scaling (or generally transforming) before combining: I guess this
> could be achieved by applying a transformation to the overlay before using
> it.
> In a sitemap this would be rather simple - with some little changes, this
> should also be possible with the pure Pipeline API, like allowing the
> AggregationImageTransformer (already existing on my machine, but yet to be
> published) to read the overlay from an InputStream which is created by
> another pipeline, or something like that.

Indeed, in the same way as before you could have had
<map:transform type="combineimages" src="cocoon:/secondimage">
where the secondimage pipeline reads secondimage.jpg and has a scaling
transformer before its serialiser.

> I still think it should be possible to provide some general purpose (and
> rather basic) transformers. As long as you can combine them, a lot of
> different effects should be possible.
> E.g. the AggregateImageTransformer will increase the image size so that both
> background and overlay are fully contained. If that's not desired, one can
> apply an CropImageTransformer afterwards, cutting of the unwanted parts.
> Of course this will require some more flexibility as some of the
> configuration of the transformers must be determined while executing the
> pipeline - but I think it should be possible in most cases...
>
>> OTOH, something like the include transformer might work better - an
>> input document describing how the images should be combined, with
>> :include elements specifying the various other input sources.  But
>> then you need a pipeline that handles both XML and image data...
>> Sounds like fun ;-)
>>
>
> Not sure I understand what you mean.
> Is that like describing the image transformation in XML and having a
> transformer evaluate the XML and then process images based on that
> description?

I guess I was thinking something like how in the past you could
aggregate XML pipelines using <map:aggregate> or by using the
cinclude/xinclude transformer.  So you could have an XML document,
say, with <imagecombine:include> elements that describe how to combine
the images and when you run that through the imageinclude transformer
it outputs the aggregated image.  Doesn't really work with a pure
image pipeline, unless you can figure out some way to nest the include
commands in the first image (EXIF-type metadata, maybe?), hence the
use of an XML input document.  But it has the advantage over a purely
pipeline-based aggregation that the images to be combined (and how to
combine them) could be generated dynamically by some other pipeline or
transformation (SQLTransformer with SELECT image, transformation FROM
IMAGE_LIST, XSLed into the relevant input document, anyone?)

> I think such an approach would limit the usage scenarios for the
> transformer, as it could not be used after some other transformation that
> returns only the resulting image.

Well, you can't put a map:aggregate after another transformation
either, you have to use the cocoon pseudo-protocol in the part's src.
Not that different.

> But I guess, we could have a transformer that is configured with an URL to
> the XML document describing the transformation. That would even allow to
> have another pipeline build that document (if that makes any sense) or
> retrieving it from somewhere else - making it easier to adjust it during
> runtime than having it directly in your code/sitemap/whatever.
>
> An interesting idea...

It's amazing how many of those you can have when you're not limited by
knowing what you're talking about ;-)


Andy.
-- 
http://pseudoq.sourceforge.net/

RE: [C3] Imaging Module

Posted by Jakob Spörk <ja...@gmx.at>.
Maybe it's even possible to package all the aggregation or blending
information into another image (mask). For example, use a 8 bit grayscale
image where black means image1, white means image2 and all different gray
scales means a blending of the two images. That mask would allow to code
areas as well as blending operations.

One problem would be how to describe that image2 with size 200x200 shall be
placed in the middle of image1 downscaled to a size of 100x100. 

+---------+
|    1    |
|  +---+  |
|  | 2 |  |
|  +---+  |
|         |
+---------+

It would be possible to use a resize transformer for image2 but how do the
aggregation transformer know where to start including image2 into image1. 

My experience with image processing is that as much as possible is
represented using images. For example using graphics hardware, you use
textures for texturing itself as well as for normal mapping, bump mapping or
other effects. I've even read a paper where database operations were
implemented using graphics hardware and where the author used textures to
represent the database. So I think to use graphics to represent additional
configuration information would also be a possibility.

Best Regards,
Jakob

-----Original Message-----
From: Steven Dolg [mailto:steven.dolg@indoqa.com] 
Sent: Dienstag, 20. Januar 2009 11:20
To: dev@cocoon.apache.org
Subject: Re: [C3] Imaging Module

Andy Stevens schrieb:
> 2009/1/19 Steven Dolg <st...@indoqa.com>:
>   
>> Vadim Gritsenko schrieb:
>>     
>>> On Jan 15, 2009, at 9:05 AM, Reinhard Pötz wrote:
>>> But question is, can you aggregate, include images?
>>>       
>> There is no component for that yet. But I'm confident this is easily
>> possible.
>>     
>
> Interesting problem - what exactly would an "aggregate" image be?  The
> relevant parts side by side, one below the other, as separate layers
> in a combined image?  If the latter, what "mode" would you use to
> combine them - blend/burn/multiply/as a mask - and what opacities?
> Should they be scaled to the same size before combining or not?
> Doesn't really fit with the usual map:aggregate/map:part without a ton
> of extra info as to how it should be done.
>   
Yes, we could easily end up with having thousands of possible ways users 
would like to use.

I thought about this and implemented a simple first approach
* use one image as the "background"
* use one image as the "overlay"
* define an anchor for the overlay (Center, North, South, Southeast, 
...) relative to the background
* allow for an additional offset to the anchor
* draw the overlay on the background, overwriting it - but applying 
existing transparency information in the overlay

[This is also the way Amazon does it:
e.g. 
http://ecx.images-amazon.com/images/I/41CHSFA4GTL._BO2,204,203,200_PIsitb-st
icker-arrow-click,TopRight,35,-76_AA240_SH20_OU01_.jpg
"TopRight,35,-75" is anchor, offsetX, offsetY - try changing it, it's 
fun ;-)]

In the imaging module this would look like:
        Pipeline pipeline = new NonCachingPipeline();

        // the background
        pipeline.addComponent(new ImageGenerator(backgroundUrl));
        // scale the background to a fixed size (to have a predictable 
"background-to-overlay" size ratio)
        pipeline.addComponent(new MaxSizeImageTransformer(200, 200));
        // apply the overlay (TopRight, slightly beyond the right and 
the top border)
        pipeline.addComponent(new 
AggregateImageTransformer(additionalUrl, Anchor.NORTHEAST, 22, -20));
        // scale again to fixed size (overlay increases image size)
        pipeline.addComponent(new MaxSizeImageTransformer(200, 200));
        // write the image
        pipeline.addComponent(new ImageSerializer());


Theoretically this should allow to create what you called "combined 
images" (if I understood your idea correct) as well as the "side by 
side" approach.
And this would only require 3 additional parameters (making the offset 
optional would reduce it to one required parameter).

However blend/burn/multiply/as mask would certainly require more (and me 
finding out what this actually means and how to achieve that :-) ).

As to the scaling (or generally transforming) before combining: I guess 
this could be achieved by applying a transformation to the overlay 
before using it.
In a sitemap this would be rather simple - with some little changes, 
this should also be possible with the pure Pipeline API, like allowing 
the AggregationImageTransformer (already existing on my machine, but yet 
to be published) to read the overlay from an InputStream which is 
created by another pipeline, or something like that.


I still think it should be possible to provide some general purpose (and 
rather basic) transformers. As long as you can combine them, a lot of 
different effects should be possible.
E.g. the AggregateImageTransformer will increase the image size so that 
both background and overlay are fully contained. If that's not desired, 
one can apply an CropImageTransformer afterwards, cutting of the 
unwanted parts.
Of course this will require some more flexibility as some of the 
configuration of the transformers must be determined while executing the 
pipeline - but I think it should be possible in most cases...


> OTOH, something like the include transformer might work better - an
> input document describing how the images should be combined, with
> :include elements specifying the various other input sources.  But
> then you need a pipeline that handles both XML and image data...
> Sounds like fun ;-)
>   
Not sure I understand what you mean.
Is that like describing the image transformation in XML and having a 
transformer evaluate the XML and then process images based on that 
description?
I think such an approach would limit the usage scenarios for the 
transformer, as it could not be used after some other transformation 
that returns only the resulting image.

But I guess, we could have a transformer that is configured with an URL 
to the XML document describing the transformation. That would even allow 
to have another pipeline build that document (if that makes any sense) 
or retrieving it from somewhere else - making it easier to adjust it 
during runtime than having it directly in your code/sitemap/whatever.

An interesting idea...

> Regards,
>
>
> Andy
>   


Regards,
Steven


Re: [C3] Imaging Module

Posted by Steven Dolg <st...@indoqa.com>.
Andy Stevens schrieb:
> 2009/1/19 Steven Dolg <st...@indoqa.com>:
>   
>> Vadim Gritsenko schrieb:
>>     
>>> On Jan 15, 2009, at 9:05 AM, Reinhard Pötz wrote:
>>> But question is, can you aggregate, include images?
>>>       
>> There is no component for that yet. But I'm confident this is easily
>> possible.
>>     
>
> Interesting problem - what exactly would an "aggregate" image be?  The
> relevant parts side by side, one below the other, as separate layers
> in a combined image?  If the latter, what "mode" would you use to
> combine them - blend/burn/multiply/as a mask - and what opacities?
> Should they be scaled to the same size before combining or not?
> Doesn't really fit with the usual map:aggregate/map:part without a ton
> of extra info as to how it should be done.
>   
Yes, we could easily end up with having thousands of possible ways users 
would like to use.

I thought about this and implemented a simple first approach
* use one image as the "background"
* use one image as the "overlay"
* define an anchor for the overlay (Center, North, South, Southeast, 
...) relative to the background
* allow for an additional offset to the anchor
* draw the overlay on the background, overwriting it - but applying 
existing transparency information in the overlay

[This is also the way Amazon does it:
e.g. 
http://ecx.images-amazon.com/images/I/41CHSFA4GTL._BO2,204,203,200_PIsitb-sticker-arrow-click,TopRight,35,-76_AA240_SH20_OU01_.jpg
"TopRight,35,-75" is anchor, offsetX, offsetY - try changing it, it's 
fun ;-)]

In the imaging module this would look like:
        Pipeline pipeline = new NonCachingPipeline();

        // the background
        pipeline.addComponent(new ImageGenerator(backgroundUrl));
        // scale the background to a fixed size (to have a predictable 
"background-to-overlay" size ratio)
        pipeline.addComponent(new MaxSizeImageTransformer(200, 200));
        // apply the overlay (TopRight, slightly beyond the right and 
the top border)
        pipeline.addComponent(new 
AggregateImageTransformer(additionalUrl, Anchor.NORTHEAST, 22, -20));
        // scale again to fixed size (overlay increases image size)
        pipeline.addComponent(new MaxSizeImageTransformer(200, 200));
        // write the image
        pipeline.addComponent(new ImageSerializer());


Theoretically this should allow to create what you called "combined 
images" (if I understood your idea correct) as well as the "side by 
side" approach.
And this would only require 3 additional parameters (making the offset 
optional would reduce it to one required parameter).

However blend/burn/multiply/as mask would certainly require more (and me 
finding out what this actually means and how to achieve that :-) ).

As to the scaling (or generally transforming) before combining: I guess 
this could be achieved by applying a transformation to the overlay 
before using it.
In a sitemap this would be rather simple - with some little changes, 
this should also be possible with the pure Pipeline API, like allowing 
the AggregationImageTransformer (already existing on my machine, but yet 
to be published) to read the overlay from an InputStream which is 
created by another pipeline, or something like that.


I still think it should be possible to provide some general purpose (and 
rather basic) transformers. As long as you can combine them, a lot of 
different effects should be possible.
E.g. the AggregateImageTransformer will increase the image size so that 
both background and overlay are fully contained. If that's not desired, 
one can apply an CropImageTransformer afterwards, cutting of the 
unwanted parts.
Of course this will require some more flexibility as some of the 
configuration of the transformers must be determined while executing the 
pipeline - but I think it should be possible in most cases...


> OTOH, something like the include transformer might work better - an
> input document describing how the images should be combined, with
> :include elements specifying the various other input sources.  But
> then you need a pipeline that handles both XML and image data...
> Sounds like fun ;-)
>   
Not sure I understand what you mean.
Is that like describing the image transformation in XML and having a 
transformer evaluate the XML and then process images based on that 
description?
I think such an approach would limit the usage scenarios for the 
transformer, as it could not be used after some other transformation 
that returns only the resulting image.

But I guess, we could have a transformer that is configured with an URL 
to the XML document describing the transformation. That would even allow 
to have another pipeline build that document (if that makes any sense) 
or retrieving it from somewhere else - making it easier to adjust it 
during runtime than having it directly in your code/sitemap/whatever.

An interesting idea...

> Regards,
>
>
> Andy
>   


Regards,
Steven

Re: [C3] Imaging Module

Posted by Andy Stevens <in...@googlemail.com>.
2009/1/19 Steven Dolg <st...@indoqa.com>:
> Vadim Gritsenko schrieb:
>> On Jan 15, 2009, at 9:05 AM, Reinhard Pötz wrote:
>> But question is, can you aggregate, include images?
>
> There is no component for that yet. But I'm confident this is easily
> possible.

Interesting problem - what exactly would an "aggregate" image be?  The
relevant parts side by side, one below the other, as separate layers
in a combined image?  If the latter, what "mode" would you use to
combine them - blend/burn/multiply/as a mask - and what opacities?
Should they be scaled to the same size before combining or not?
Doesn't really fit with the usual map:aggregate/map:part without a ton
of extra info as to how it should be done.

OTOH, something like the include transformer might work better - an
input document describing how the images should be combined, with
:include elements specifying the various other input sources.  But
then you need a pipeline that handles both XML and image data...
Sounds like fun ;-)

Regards,


Andy
-- 
http://pseudoq.sourceforge.net/

Re: [C3] Imaging Module

Posted by Steven Dolg <st...@indoqa.com>.
Vadim Gritsenko schrieb:
> On Jan 15, 2009, at 9:05 AM, Reinhard Pötz wrote:
>
>> Steven Dolg wrote:
>>> I planned to come up with an Imaging Module for quite some time now.
>>
>> I'm +1 to add the patch to the C3 code base.
>
> +1.
>
> But question is, can you aggregate, include images?
There is no component for that yet. But I'm confident this is easily 
possible.

AFAICS Java2D is quite powerful when it comes to manipulating images.
However there are are lot of things to consider like using direct models 
(I think this is the correct term - but not sure) as I did or not, etc. 
- and I'm not really an expert in this field...
>
> This module also would need a good demo.
Definitely!

I'm already thinking about providing some propery documentation, 
explaining the different transformations (like "input-image" -> 
"transformation" -> "output-image").
But a demo might be a really cool addition to that.


Regards,
Steven
>
> Vadim


Re: [C3] Imaging Module

Posted by Vadim Gritsenko <va...@reverycodes.com>.
On Jan 15, 2009, at 9:05 AM, Reinhard Pötz wrote:

> Steven Dolg wrote:
>> I planned to come up with an Imaging Module for quite some time now.
>
> I'm +1 to add the patch to the C3 code base.

+1.

But question is, can you aggregate, include images?

This module also would need a good demo.

Vadim

Re: [C3] Imaging Module

Posted by Reinhard Pötz <re...@apache.org>.
Steven Dolg wrote:
> Hi,
> 
> I planned to come up with an Imaging Module for quite some time now.
> There were several situations in some of the projects I worked on where
> such a module would have been handy IMO.
> I also thought this might serve as another example of how the Pipeline
> API could be used.
> 
> 
> I did not invest much time (only a couple of hours this afternoon) and
> I'm actually not very versatile in using the Imaging API and/or Java2D.
> So please don't blame me when the implementation of the transformations
> is not ideal or not really fast.
> (Actually if someone know how to implement some or all of those
> manipulations better, I'd love to hear about that).
> The transformers still miss alot of configuration possiblities (most of
> it is simply hardwired) and are not really suited to be used in the
> Sitemap, yet.
> But I intend to continue working on this...
> 
> 
> So far I created the following transformers:
> * CropImageTransformer
>    Crops the image to an area of the configured size. The area is
> centered on the image
> 
> * MaxSizeImageTransformer
>    Reduces the size of the image so that it fits inside the given
> dimensions. The aspect ratio of the image is maintained. If the image
> already fits inside the given bound it will remain unchanged.
> 
> *RotateImageTransformer
>    Rotates the whole image by a given amount. Any value is possible.
> 
> *ScaleImageTransformer
>    Scales the image by a given factor, maininting the aspect ratio.
> 
> *WatermarkImageTransformer
>    Write a configurable text centered on the image. Color and
> transparancy of the text is configurable.
> 
> 
> Additionally there is an ImageGenerator and an ImageSerializer.
> Both ImageGenerator and ImageSerializer use the Imaging API of Java. So
> by default the image formats JPG, PNG, GIF and BMP are supported (IIRC).
> Additional formats may be supported by registering them with the Imaging
> API.
> 
> All the components mentioned above a fully compatible to each other -
> and not compatible to any other Cocoon 3 components currently available
> - and the transformers can be combined in every way.
> The basic rules of the PipelineAPI still apply of course (Generator ->
> Transformer* -> Serializer)
> 
> I have provided a unit test that uses several pipelines using all
> components.
> 
> Attached you can find the patch for the code and a sample image (in case
> you have none handy).
> The generator uses the URL mechanism already implemented in other
> generators in Cocoon 3, so images can also be loaded directly from a web
> site or some other location.

Since I had the chance to look over Steven's shoulder when he
implemented these components yesterday, I have to say that it is *really
cool stuff*. Thanks Steven!

It also demonstrates the power of pipelines and the usage of the
interfaces (starter, finisher, consumer, producer) very well. Everybody
who wants to get familiar with the basics of C3, should take a closer look.

I'm +1 to add the patch to the C3 code base.

-- 
Reinhard Pötz                           Managing Director, {Indoqa} GmbH
                         http://www.indoqa.com/en/people/reinhard.poetz/

Member of the Apache Software Foundation
Apache Cocoon Committer, PMC member                  reinhard@apache.org
________________________________________________________________________