001/*
002Copyright 2006 Jerry Huxtable
003
004Licensed under the Apache License, Version 2.0 (the "License");
005you may not use this file except in compliance with the License.
006You may obtain a copy of the License at
007
008   http://www.apache.org/licenses/LICENSE-2.0
009
010Unless required by applicable law or agreed to in writing, software
011distributed under the License is distributed on an "AS IS" BASIS,
012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013See the License for the specific language governing permissions and
014limitations under the License.
015*/
016
017package com.jhlabs.image;
018
019import java.awt.*;
020import java.awt.image.*;
021
022/**
023 * An abstract superclass for filters which distort images in some way. The subclass only needs to override
024 * two methods to provide the mapping between source and destination pixels.
025 */
026public abstract class TransformFilter extends AbstractBufferedImageOp {
027
028    /**
029     * Treat pixels off the edge as zero.
030     */
031        public final static int ZERO = 0;
032
033    /**
034     * Clamp pixels to the image edges.
035     */
036        public final static int CLAMP = 1;
037
038    /**
039     * Wrap pixels off the edge onto the oppsoite edge.
040     */
041        public final static int WRAP = 2;
042
043    /**
044     * Clamp pixels RGB to the image edges, but zero the alpha. This prevents gray borders on your image.
045     */
046        public final static int RGB_CLAMP = 3;
047
048    /**
049     * Use nearest-neighbout interpolation.
050     */
051        public final static int NEAREST_NEIGHBOUR = 0;
052
053    /**
054     * Use bilinear interpolation.
055     */
056        public final static int BILINEAR = 1;
057
058    /**
059     * The action to take for pixels off the image edge.
060     */
061        protected int edgeAction = RGB_CLAMP;
062
063    /**
064     * The type of interpolation to use.
065     */
066        protected int interpolation = BILINEAR;
067
068    /**
069     * The output image rectangle.
070     */
071        protected Rectangle transformedSpace;
072
073    /**
074     * The input image rectangle.
075     */
076        protected Rectangle originalSpace;
077
078    /**
079     * Set the action to perform for pixels off the edge of the image.
080     * @param edgeAction one of ZERO, CLAMP or WRAP
081     * @see #getEdgeAction
082     */
083        public void setEdgeAction(int edgeAction) {
084                this.edgeAction = edgeAction;
085        }
086
087    /**
088     * Get the action to perform for pixels off the edge of the image.
089     * @return one of ZERO, CLAMP or WRAP
090     * @see #setEdgeAction
091     */
092        public int getEdgeAction() {
093                return edgeAction;
094        }
095        
096    /**
097     * Set the type of interpolation to perform.
098     * @param interpolation one of NEAREST_NEIGHBOUR or BILINEAR
099     * @see #getInterpolation
100     */
101        public void setInterpolation(int interpolation) {
102                this.interpolation = interpolation;
103        }
104
105    /**
106     * Get the type of interpolation to perform.
107     * @return one of NEAREST_NEIGHBOUR or BILINEAR
108     * @see #setInterpolation
109     */
110        public int getInterpolation() {
111                return interpolation;
112        }
113        
114    /**
115     * Inverse transform a point. This method needs to be overriden by all subclasses.
116     * @param x the X position of the pixel in the output image
117     * @param y the Y position of the pixel in the output image
118     * @param out the position of the pixel in the input image
119     */
120        protected abstract void transformInverse(int x, int y, float[] out);
121
122    /**
123     * Forward transform a rectangle. Used to determine the size of the output image.
124     * @param rect the rectangle to transform
125     */
126        protected void transformSpace(Rectangle rect) {
127        }
128
129    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
130        int width = src.getWidth();
131        int height = src.getHeight();
132                int type = src.getType();
133                WritableRaster srcRaster = src.getRaster();
134
135                originalSpace = new Rectangle(0, 0, width, height);
136                transformedSpace = new Rectangle(0, 0, width, height);
137                transformSpace(transformedSpace);
138
139        if ( dst == null ) {
140            ColorModel dstCM = src.getColorModel();
141                        dst = new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(transformedSpace.width, transformedSpace.height), dstCM.isAlphaPremultiplied(), null);
142                }
143                WritableRaster dstRaster = dst.getRaster();
144
145                int[] inPixels = getRGB( src, 0, 0, width, height, null );
146
147                if ( interpolation == NEAREST_NEIGHBOUR )
148                        return filterPixelsNN( dst, width, height, inPixels, transformedSpace );
149
150                int srcWidth = width;
151                int srcHeight = height;
152                int srcWidth1 = width-1;
153                int srcHeight1 = height-1;
154                int outWidth = transformedSpace.width;
155                int outHeight = transformedSpace.height;
156                int outX, outY;
157                int index = 0;
158                int[] outPixels = new int[outWidth];
159
160                outX = transformedSpace.x;
161                outY = transformedSpace.y;
162                float[] out = new float[2];
163
164                for (int y = 0; y < outHeight; y++) {
165                        for (int x = 0; x < outWidth; x++) {
166                                transformInverse(outX+x, outY+y, out);
167                                int srcX = (int)Math.floor( out[0] );
168                                int srcY = (int)Math.floor( out[1] );
169                                float xWeight = out[0]-srcX;
170                                float yWeight = out[1]-srcY;
171                                int nw, ne, sw, se;
172
173                                if ( srcX >= 0 && srcX < srcWidth1 && srcY >= 0 && srcY < srcHeight1) {
174                                        // Easy case, all corners are in the image
175                                        int i = srcWidth*srcY + srcX;
176                                        nw = inPixels[i];
177                                        ne = inPixels[i+1];
178                                        sw = inPixels[i+srcWidth];
179                                        se = inPixels[i+srcWidth+1];
180                                } else {
181                                        // Some of the corners are off the image
182                                        nw = getPixel( inPixels, srcX, srcY, srcWidth, srcHeight );
183                                        ne = getPixel( inPixels, srcX+1, srcY, srcWidth, srcHeight );
184                                        sw = getPixel( inPixels, srcX, srcY+1, srcWidth, srcHeight );
185                                        se = getPixel( inPixels, srcX+1, srcY+1, srcWidth, srcHeight );
186                                }
187                                outPixels[x] = ImageMath.bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se);
188                        }
189                        setRGB( dst, 0, y, transformedSpace.width, 1, outPixels );
190                }
191                return dst;
192        }
193
194        final private int getPixel( int[] pixels, int x, int y, int width, int height ) {
195                if (x < 0 || x >= width || y < 0 || y >= height) {
196                        switch (edgeAction) {
197                        case ZERO:
198                        default:
199                                return 0;
200                        case WRAP:
201                                return pixels[(ImageMath.mod(y, height) * width) + ImageMath.mod(x, width)];
202                        case CLAMP:
203                                return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)];
204                        case RGB_CLAMP:
205                                return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)] & 0x00ffffff;
206                        }
207                }
208                return pixels[ y*width+x ];
209        }
210
211        protected BufferedImage filterPixelsNN( BufferedImage dst, int width, int height, int[] inPixels, Rectangle transformedSpace ) {
212                int srcWidth = width;
213                int srcHeight = height;
214                int outWidth = transformedSpace.width;
215                int outHeight = transformedSpace.height;
216                int outX, outY, srcX, srcY;
217                int[] outPixels = new int[outWidth];
218
219                outX = transformedSpace.x;
220                outY = transformedSpace.y;
221                int[] rgb = new int[4];
222                float[] out = new float[2];
223
224                for (int y = 0; y < outHeight; y++) {
225                        for (int x = 0; x < outWidth; x++) {
226                                transformInverse(outX+x, outY+y, out);
227                                srcX = (int)out[0];
228                                srcY = (int)out[1];
229                                // int casting rounds towards zero, so we check out[0] < 0, not srcX < 0
230                                if (out[0] < 0 || srcX >= srcWidth || out[1] < 0 || srcY >= srcHeight) {
231                                        int p;
232                                        switch (edgeAction) {
233                                        case ZERO:
234                                        default:
235                                                p = 0;
236                                                break;
237                                        case WRAP:
238                                                p = inPixels[(ImageMath.mod(srcY, srcHeight) * srcWidth) + ImageMath.mod(srcX, srcWidth)];
239                                                break;
240                                        case CLAMP:
241                                                p = inPixels[(ImageMath.clamp(srcY, 0, srcHeight-1) * srcWidth) + ImageMath.clamp(srcX, 0, srcWidth-1)];
242                                                break;
243                    case RGB_CLAMP:
244                                                p = inPixels[(ImageMath.clamp(srcY, 0, srcHeight-1) * srcWidth) + ImageMath.clamp(srcX, 0, srcWidth-1)] & 0x00ffffff;
245                                        }
246                                        outPixels[x] = p;
247                                } else {
248                                        int i = srcWidth*srcY + srcX;
249                                        rgb[0] = inPixels[i];
250                                        outPixels[x] = inPixels[i];
251                                }
252                        }
253                        setRGB( dst, 0, y, transformedSpace.width, 1, outPixels );
254                }
255                return dst;
256        }
257
258}
259