001    /*
002     * Crop
003     *
004     * Copyright (c) 2001, 2002, 2003 Marco Schmidt.
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     * Copies a rectangular area of one image to another image that is exactly as large
018     * as that rectangular area.
019     * Works with all image data classes implementing {@link net.sourceforge.jiu.data.IntegerImage}.
020     * <em>Make sure to use zero-based parameters when defining the bounds with
021     * {@link #setBounds}!</em>
022     * <h3>Usage example</h3>
023     * In this example we assume that the input image is larger than 20 pixels in both directions.
024     * Ten pixels will be removed from any of its four borders.
025     * <pre>
026     * PixelImage image = ...; // something implementing IntegerImage
027     * Crop crop = new Crop();
028     * crop.setInputImage(image);
029     * crop.setBounds(10, 10, image.getWidth() - 9, image.getHeight() - 9);
030     * crop.process();
031     * PixelImage croppedImage = crop.getOutputImage();
032     * </pre>
033     * @author Marco Schmidt
034     */
035    public class Crop extends ImageToImageOperation
036    {
037            private int x1;
038            private int y1;
039            private int x2;
040            private int y2;
041    
042            private void checkBounds() throws WrongParameterException
043            {
044                    PixelImage in = getInputImage();
045                    if (x1 >= in.getWidth())
046                    {
047                            throw new WrongParameterException("x1 must be smaller than input image width.");
048                    }
049                    if (x2 >= in.getWidth())
050                    {
051                            throw new WrongParameterException("x2 must be smaller than input image width.");
052                    }
053                    if (y1 >= in.getHeight())
054                    {
055                            throw new WrongParameterException("y1 must be smaller than input image height.");
056                    }
057                    if (y2 >= in.getHeight())
058                    {
059                            throw new WrongParameterException("y2 must be smaller than input image height.");
060                    }
061            }
062    
063            private void process(IntegerImage in, IntegerImage out)
064            {
065                    final int OUT_WIDTH = x2 - x1 + 1;
066                    final int OUT_HEIGHT = y2 - y1 + 1;
067                    if (out == null)
068                    {
069                            out = (IntegerImage)in.createCompatibleImage(OUT_WIDTH, OUT_HEIGHT);
070                            setOutputImage(out);
071                    }
072                    int totalItems = in.getNumChannels() * OUT_HEIGHT;
073                    int processedItems = 0;
074                    for (int c = 0; c < in.getNumChannels(); c++)
075                    {
076                            for (int yfrom = y1, yto = 0; yto < OUT_HEIGHT; yfrom++, yto++)
077                            {
078                                    for (int xfrom = x1, xto = 0; xto < OUT_WIDTH; xfrom++, xto++)
079                                    {
080                                            out.putSample(c, xto, yto, in.getSample(c, xfrom, yfrom));
081                                    }
082                                    setProgress(processedItems++, totalItems);
083                            }
084                    }
085            }
086    
087            public void process() throws
088                    MissingParameterException,
089                    WrongParameterException
090            {
091                    ensureInputImageIsAvailable();
092                    checkBounds();
093                    ensureOutputImageResolution(x2 - x1 + 1, y2 - y1 + 1);
094                    if (getInputImage() instanceof IntegerImage)
095                    {
096                            process((IntegerImage)getInputImage(), (IntegerImage)getOutputImage());
097                    }
098                    else
099                    {
100                            throw new WrongParameterException("Input image must implement IntegerImage.");
101                    }
102            }
103    
104            /**
105             * Specify the rectangular section of the original image that is to be
106             * copied to the output image by this operation.
107             * Note that the arguments are not checked directly against any input image that may have
108             * been provided to this Crop object, that checking is done later in {@link #process()}.
109             * If any of the arguments provided here are outside of the input image's resolution
110             * (e.g. x1 == 100 although the input image's width is only 60), a 
111             * {@link net.sourceforge.jiu.ops.WrongParameterException} will be thrown from
112             * within {@link #process()}.
113             * <p>
114             * Note that the arguments to this method are zero-based, so the first column and row
115             * are 0, the second 1, the third 2, and so on.
116             * If you have a image that is 200 pixels wide and 100 pixels high,
117             * values from 0 to 199 are valid for the x arguments, and values from 0 to 99 are valid
118             * for the vertical direction.
119             * @param x1 horizontal position of upper left corner of the rectangle
120             * @param y1 vertical position of upper left corner of the rectangle
121             * @param x2 horizontal position of lower right corner of the rectangle
122             * @param y2 vertical position of lower right corner of the rectangle
123             * @throws IllegalArgumentException if any of the arguments is negative or x1 larger than x2 or y1 larger than y2
124             */
125            public void setBounds(int x1, int y1, int x2, int y2) throws IllegalArgumentException
126            {
127                    if (x1 < 0)
128                    {
129                            throw new IllegalArgumentException("x1 must not be negative.");
130                    }
131                    if (y1 < 0)
132                    {
133                            throw new IllegalArgumentException("y1 must not be negative.");
134                    }
135                    if (x2 < 0)
136                    {
137                            throw new IllegalArgumentException("x2 must not be negative.");
138                    }
139                    if (y2 < 0)
140                    {
141                            throw new IllegalArgumentException("y2 must not be negative.");
142                    }
143                    if (x1 > x2)
144                    {
145                            throw new IllegalArgumentException("x1 must not be larger than x2.");
146                    }
147                    if (y1 > y2)
148                    {
149                            throw new IllegalArgumentException("y1 must not be larger than y2.");
150                    }
151                    this.x1 = x1;
152                    this.y1 = y1;
153                    this.x2 = x2;
154                    this.y2 = y2;
155            }
156    }