001    /*
002     * Shear
003     * 
004     * Copyright (c) 2001, 2002 Marco Schmidt <marcoschmidt@users.sourceforge.net>
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.geometry;
009    
010    import net.sourceforge.jiu.data.PixelImage;
011    import net.sourceforge.jiu.data.IntegerImage;
012    import net.sourceforge.jiu.ops.ImageToImageOperation;
013    import net.sourceforge.jiu.ops.MissingParameterException;
014    import net.sourceforge.jiu.ops.WrongParameterException;
015    
016    /**
017     * Shears an image by a given angle.
018     * The angle must be larger than -90 and smaller than 90 degrees.
019     * Shearing works with all image types that implement {@link net.sourceforge.jiu.data.IntegerImage}.
020     * <h3>Usage example</h3>
021     * <pre>
022     * Shear shear = new Shear();
023     * shear.setInputImage(image); // some IntegerImage
024     * shear.setAngle(5.0);
025     * shear.process();
026     * PixelImage shearedImage = shear.getOutputImage();
027     * </pre>
028     * <p>
029     * This is an adjusted version of Jef Poskanzer's shearing code from his ACME
030     * package; see the
031     * <a target="_top" href="http://www.acme.com/java/software/Acme.JPM.Filters.Shear.html">API
032     * documentation page</a> of ACME's Shear class.
033     *
034     * @author Jef Poskanzer
035     * @author Marco Schmidt
036     */
037    public class Shear extends ImageToImageOperation
038    {
039            private double angle;
040    
041            /**
042             * For a given image width and shearing angle this method
043             * computes the width of the resulting image.
044             * This method is static so that it can be called easily from a GUI dialog
045             * or other objects that want to present the width of a sheared image.
046             * @param oldImageWidth horizontal resolution of the image to be sheared
047             * @param height height of the image to be sheared
048             * @param angle the angle to be used in the shearing operation
049             * @return width of the sheared image
050             */
051            public static int computeNewImageWidth(int oldImageWidth, int height, double angle)
052            {
053                    double shearfac = Math.tan(angle * Math.PI / 180.0);
054                    if (shearfac < 0.0)
055                    {
056                            shearfac = -shearfac;
057                    }
058                    return (int)(height * shearfac + oldImageWidth + 0.999999);
059            }
060    
061            /**
062             * Returns the angle associated with this shearing operation object.
063             * @return shearing angle, between -90 and 90
064             * @see #setAngle
065             */
066            public double getAngle()
067            {
068                    return angle;
069            }
070    
071            private void process(IntegerImage in, IntegerImage out)
072            {
073                    final int WIDTH = in.getWidth();
074                    final int HEIGHT = in.getHeight();
075                    int totalItems = in.getNumChannels() * HEIGHT;
076                    int processedItems = 0;
077                    double angle = getAngle() * Math.PI / 180.0;
078                    double shearfac = Math.tan(angle);
079                    if (shearfac < 0.0)
080                    {
081                            shearfac = -shearfac;
082                    }
083                    int NEW_WIDTH = (int)(HEIGHT * shearfac + WIDTH + 0.999999);
084                    if (out == null)
085                    {
086                            out = (IntegerImage)in.createCompatibleImage(NEW_WIDTH, HEIGHT);
087                            setOutputImage(out);
088                    }
089                    for (int c = 0; c < in.getNumChannels(); c++)
090                    {
091                            for (int y = 0; y < HEIGHT; y++)
092                            {
093                                    double new0;
094                                    if (angle > 0.0)
095                                    {
096                                            new0 = y * shearfac;
097                                    }
098                                    else
099                                    {
100                                            new0 = (HEIGHT - y) * shearfac;
101                                    }
102                                    int intnew0 = (int)new0;
103                                    double fracnew0 = new0 - intnew0;
104                                    double omfracnew0 = 1.0 - fracnew0;
105                                    int prev = 0;
106                                    for (int x = 0; x < WIDTH; x++)
107                                    {
108                                            int value = in.getSample(c, x, y);
109                                            out.putSample(c, intnew0 + x, y, (int)(fracnew0 * prev + omfracnew0 * value));
110                                            prev = value;
111                                    }
112                                    out.putSample(c, intnew0 + WIDTH, y, (int)(fracnew0 * prev));
113                                    setProgress(processedItems++, totalItems);
114                            }
115                    }
116            }
117    
118            public void process() throws
119                    MissingParameterException,
120                    WrongParameterException
121            {
122                    ensureInputImageIsAvailable();
123                    PixelImage in = getInputImage();
124                    ensureOutputImageResolution(computeNewImageWidth(in.getWidth(), in.getHeight(), getAngle()), in.getHeight());
125                    if (in instanceof IntegerImage)
126                    {
127                            process((IntegerImage)in, (IntegerImage)getOutputImage());
128                    }
129                    else
130                    {
131                            throw new WrongParameterException("Input image must implement IntegerImage.");
132                    }
133            }
134    
135            /**
136             * Sets the angle to be used in the shearing operation to the argument value.
137             * The angle must be larger than -90.0 and smaller than 90.0.
138             * @param newAngle the angle to be used in this operation
139             * @throws IllegalArgumentException if the argument is not in the above mentioned interval
140             * @see #getAngle
141             */
142            public void setAngle(double newAngle)
143            {
144                    if (newAngle <= -90.0 || newAngle >= 90.0)
145                    {
146                            throw new IllegalArgumentException("Angle must be > -90 and < 90; got " + newAngle);
147                    }
148                    else
149                    {
150                            angle = newAngle;
151                    }
152            }
153    }