001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.processing.resize;
031
032import org.openimaj.image.FImage;
033import org.openimaj.image.processing.resize.ResizeProcessor.PixelContribution;
034import org.openimaj.image.processing.resize.ResizeProcessor.PixelContributions;
035import org.openimaj.image.processing.resize.filters.TriangleFilter;
036import org.openimaj.image.processor.SinglebandImageProcessor;
037
038
039/**
040 * A copy of the {@link ResizeProcessor} which speeds up the resize operation
041 * between images of a given size to another fixed size by caching the contribution
042 * calculations
043 * 
044 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
045 */
046public class FixedResizeProcessor implements SinglebandImageProcessor<Float, FImage> {
047        
048        private float newX;
049        private float newY;
050        private ResizeFilterFunction filterFunction;
051        private float srcX;
052        private float srcY;
053        private ImageContributions ic;
054        final float[] work;
055        
056        /**
057         * The default {@link TriangleFilter} (bilinear-interpolation filter) used
058         * by instances of {@link ResizeProcessor}, unless otherwise specified.
059         */
060        public static final ResizeFilterFunction DEFAULT_FILTER = TriangleFilter.INSTANCE;
061        
062        /**
063         * Construct a fixed resize processor that will rescale the image to the given
064         * width and height with the given filter function. By default, this method
065         * will retain the image's aspect ratio.
066         * @param srcX 
067         *                      The expected width of input images
068         * @param srcY 
069         *                      The expected with of output images
070         * @param newX
071         *            The new width of the image.
072         * @param newY
073         *            The new height of the image.
074         * @param ff
075         *            The filter function to use.
076         */
077        public FixedResizeProcessor(float srcX, float srcY, float newX, float newY, ResizeFilterFunction ff) {
078                this.srcX = srcX;
079                this.srcY = srcY;
080                this.newX = newX;
081                this.newY = newY;
082                this.filterFunction = ff;
083                prepareResample(true);
084                this.work = new float[(int)newY];
085        }
086
087        /**
088         * Construct a fixed resize processor that will rescale the image to the given
089         * width and height with the default filter function. By default, this
090         * method will retain the image's aspect ratio which means that the
091         * resulting image may have dimensions less than those specified here.
092         * @param srcX 
093         *                        The expected width of input images
094         * @param srcY
095         *                        The expected height of input images
096         * @param newX
097         *            The new width of the image.
098         * @param newY
099         *            The new height of the image.
100         */
101        public FixedResizeProcessor(float srcX, float srcY, float newX, float newY) {
102                this(srcX,srcY,newX, newY, DEFAULT_FILTER);
103        }
104        
105        /**
106         * @param image
107         *                      The expected width and height of input images
108         * @param newX
109         *                      The new width of the image
110         * @param newY
111         *                      The new height of the image
112         */
113        public FixedResizeProcessor(FImage image, int newX, int newY) {
114                this(image.width,image.height,newX,newY);
115        }
116
117        private void prepareResample(boolean aspect) {
118                // Work out the size of the resampled image
119                // if the aspect ratio is set to true
120                int nx = (int)newX;
121                int ny = (int)newY;
122                if (aspect) {
123                        if (ny > nx)
124                                nx = (int) Math.round((this.srcX * ny) / (double) this.srcY);
125                        else
126                                ny = (int) Math.round((this.srcY * nx) / (double) this.srcX);
127                }
128                this.newX = nx;
129                this.newY = ny;
130
131                this.ic = FixedResizeProcessor.prepareZoom((int)srcX,(int)srcY,(int)newX,(int)newY,this.filterFunction);
132        }
133        static class ImageContributions{
134                PixelContributions[] xContributions;
135                PixelContributions[] yContributions;
136        }
137        private static ImageContributions prepareZoom(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ResizeFilterFunction filterf) {
138                final double xscale = (double) dstWidth / (double) srcWidth;
139                final double yscale = (double) dstHeight / (double) srcHeight;
140                
141                final PixelContributions[] contribY = new PixelContributions[dstHeight];
142                for (int i = 0; i < contribY.length; i++) {
143                        contribY[i] = new PixelContributions();
144                }
145                
146                final PixelContributions[] contribX = new PixelContributions[dstWidth];
147                for (int i = 0; i < contribX.length; i++) {
148                        contribX[i] = new PixelContributions();
149                }
150                
151                final double fwidth = filterf.getSupport();
152                if (yscale < 1.0) {
153                        double width = fwidth / yscale;
154                        double fscale = 1.0 / yscale;
155
156                        if (width <= .5) {
157                                // Reduce to point sampling.
158                                width = .5 + 1.0e-6;
159                                fscale = 1.0;
160                        }
161
162                        for (int i = 0; i < dstHeight; i++) {
163                                contribY[i].contributions = new PixelContribution[(int) (width * 2.0 + 1)];
164                                contribY[i].numberOfContributors = 0;
165
166                                final double center = i / yscale;
167                                final int left = (int) Math.ceil(center - width);
168                                final int right = (int) Math.floor(center + width);
169
170                                double density = 0.0;
171
172                                for (int j = left; j <= right; j++) {
173                                        double weight = center - j;
174                                        weight = filterf.filter(weight / fscale) / fscale;
175                                        int n;
176                                        if (j < 0) {
177                                                n = -j;
178                                        }
179                                        else if (j >= srcHeight) {
180                                                n = (srcHeight - j) + srcHeight - 1;
181                                        }
182                                        else {
183                                                n = j;
184                                        }
185
186                                        /**/
187                                        if (n >= srcHeight) {
188                                                n = n % srcHeight;
189                                        }
190                                        else if (n < 0) {
191                                                n = srcHeight - 1;
192                                        }
193                                        /**/
194
195                                        final int k = contribY[i].numberOfContributors++;
196                                        contribY[i].contributions[k] = new PixelContribution();
197                                        contribY[i].contributions[k].pixel = n;
198                                        contribY[i].contributions[k].weight = weight;
199
200                                        density += weight;
201                                }
202
203                                if ((density != 0.0) && (density != 1.0)) {
204                                        // Normalize.
205                                        density = 1.0 / density;
206                                        for (int k = 0; k < contribY[i].numberOfContributors; k++) {
207                                                contribY[i].contributions[k].weight *= density;
208                                        }
209                                }
210                        }
211                }
212                else {
213                        for (int i = 0; i < dstHeight; ++i) {
214                                contribY[i].contributions = new PixelContribution[(int) (fwidth * 2 + 1)];
215                                contribY[i].numberOfContributors = 0;
216
217                                final double center = i / yscale;
218                                final double left = Math.ceil(center - fwidth);
219                                final double right = Math.floor(center + fwidth);
220                                for (int j = (int) left; j <= right; ++j) {
221                                        double weight = center - j;
222                                        weight = filterf.filter(weight);
223                                        int n;
224                                        if (j < 0) {
225                                                n = -j;
226                                        }
227                                        else if (j >= srcHeight) {
228                                                n = (srcHeight - j) + srcHeight - 1;
229                                        }
230                                        else {
231                                                n = j;
232                                        }
233
234                                        /**/
235                                        if (n >= srcHeight) {
236                                                n = n % srcHeight;
237                                        }
238                                        else if (n < 0) {
239                                                n = srcHeight - 1;
240                                        }
241                                        /**/
242
243                                        final int k = contribY[i].numberOfContributors++;
244                                        contribY[i].contributions[k] = new PixelContribution();
245                                        contribY[i].contributions[k].pixel = n;
246                                        contribY[i].contributions[k].weight = weight;
247                                }
248                        }
249                }
250                
251                if (xscale < 1.0) {
252                        for (int i = 0; i < dstWidth; ++i) {
253                                /* Shrinking image */
254                                double width = fwidth / xscale;
255                                double fscale = 1.0 / xscale;
256        
257                                if (width <= .5) {
258                                        // Reduce to point sampling.
259                                        width = .5 + 1.0e-6;
260                                        fscale = 1.0;
261                                }
262        
263                                contribX[i].numberOfContributors = 0;
264                                contribX[i].contributions = new PixelContribution[(int) (width * 2.0 + 1.0)];
265        
266                                double center = i / xscale;
267                                final int left = (int) Math.ceil(center - width);// Note: Assumes
268                                                                                                                                        // width <= .5
269                                final int right = (int) Math.floor(center + width);
270        
271                                double density = 0.0;
272        
273                                for (int j = left; j <= right; j++) {
274                                        double weight = center - j;
275                                        weight = filterf.filter(weight / fscale) / fscale;
276                                        int n;
277                                        if (j < 0) {
278                                                n = -j;
279                                        }
280                                        else if (j >= srcWidth) {
281                                                n = (srcWidth - j) + srcWidth - 1;
282                                        }
283                                        else {
284                                                n = j;
285                                        }
286        
287                                        /**/
288                                        if (n >= srcWidth) {
289                                                n = n % srcWidth;
290                                        }
291                                        else if (n < 0) {
292                                                n = srcWidth - 1;
293                                        }
294                                        /**/
295        
296                                        final int k = contribX[i].numberOfContributors++;
297                                        contribX[i].contributions[k] = new PixelContribution();
298                                        contribX[i].contributions[k].pixel = n;
299                                        contribX[i].contributions[k].weight = weight;
300        
301                                        density += weight;
302        
303                                }
304        
305                                if ((density != 0.0) && (density != 1.0)) {
306                                        // Normalize.
307                                        density = 1.0 / density;
308                                        for (int k = 0; k < contribX[i].numberOfContributors; k++) {
309                                                contribX[i].contributions[k].weight *= density;
310                                        }
311                                }
312                        }
313                }
314                else {
315                        for (int i = 0; i < dstWidth; ++i) {
316                                /* Expanding image */
317                                contribX[i].numberOfContributors = 0;
318                                contribX[i].contributions = new PixelContribution[(int) (fwidth * 2.0 + 1.0)];
319        
320                                double center = i / xscale;
321                                final int left = (int) Math.ceil(center - fwidth);
322                                final int right = (int) Math.floor(center + fwidth);
323        
324                                for (int j = left; j <= right; j++) {
325                                        double weight = center - j;
326                                        weight = filterf.filter(weight);
327        
328                                        int n;
329                                        if (j < 0) {
330                                                n = -j;
331                                        }
332                                        else if (j >= srcWidth) {
333                                                n = (srcWidth - j) + srcWidth - 1;
334                                        }
335                                        else {
336                                                n = j;
337                                        }
338        
339                                        /**/
340                                        if (n >= srcWidth) {
341                                                n = n % srcWidth;
342                                        }
343                                        else if (n < 0) {
344                                                n = srcWidth - 1;
345                                        }
346                                        /**/
347        
348                                        final int k = contribX[i].numberOfContributors++;
349                                        contribX[i].contributions[k] = new PixelContribution();
350                                        contribX[i].contributions[k].pixel = n;
351                                        contribX[i].contributions[k].weight = weight;
352                                }
353                        }
354                }
355                
356                ImageContributions ic = new ImageContributions();
357                ic.xContributions = contribX;
358                ic.yContributions = contribY;
359                
360                return ic;
361        }
362
363        /**
364         * {@inheritDoc}
365         * 
366         * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj.image.Image)
367         */
368        @Override
369        public void processImage(FImage in) {
370                if(in.width != this.srcX || in.height != srcY){
371                        throw new RuntimeException("Incompatible image type used with FixedResizeProcessor, try the normal ResizeProcessor");
372                }
373                /* create intermediate column to hold horizontal dst column zoom */
374                
375                FImage dst = new FImage((int)this.newX,(int)this.newY);
376                final float maxValue = in.max();
377                for (int xx = 0; xx < dst.width; xx++) {
378                        final PixelContributions contribX = this.ic.xContributions[xx];
379
380                        /* Apply horiz filter to make dst column in tmp. */
381                        for (int k = 0; k < in.height; k++) {
382                                double weight = 0.0;
383                                boolean bPelDelta = false;
384                                // TODO: This line throws index out of bounds, if the image
385                                // is smaller than filter.support()
386                                final double pel = in.pixels[k][contribX.contributions[0].pixel];
387                                for (int j = 0; j < contribX.numberOfContributors; j++) {
388                                        final double pel2 = j == 0 ? pel : in.pixels[k][contribX.contributions[j].pixel];
389                                        if (pel2 != pel) {
390                                                bPelDelta = true;
391                                        }
392                                        weight += pel2 * contribX.contributions[j].weight;
393                                }
394                                weight = bPelDelta ? Math.round(weight * 255) / 255f : pel;
395
396                                if (weight < 0) {
397                                        weight = 0;
398                                }
399                                else if (weight > maxValue) {
400                                        weight = maxValue;
401                                }
402
403                                work[k] = (float) weight;
404                        }/* next row in temp column */
405
406                        /*
407                         * The temp column has been built. Now stretch it vertically into
408                         * dst column.
409                         */
410                        for (int i = 0; i < dst.height; i++) {
411                                double weight = 0.0;
412                                boolean bPelDelta = false;
413                                final double pel = work[ic.yContributions[i].contributions[0].pixel];
414
415                                for (int j = 0; j < ic.yContributions[i].numberOfContributors; j++) {
416                                        // TODO: This line throws index out of bounds, if the
417                                        // image is smaller than filter.support()
418                                        final double pel2 = j == 0 ? pel : work[ic.yContributions[i].contributions[j].pixel];
419                                        if (pel2 != pel) {
420                                                bPelDelta = true;
421                                        }
422                                        weight += pel2 * ic.yContributions[i].contributions[j].weight;
423                                }
424                                weight = bPelDelta ? Math.round(weight * 255) / 255f : pel;
425
426                                if (weight < 0) {
427                                        weight = 0;
428                                }
429                                else if (weight > maxValue) {
430                                        weight = maxValue;
431                                }
432
433                                dst.pixels[i][xx] = (float) weight;
434                        } /* next dst row */
435                } /* next dst column */
436                
437                in.internalAssign(dst);
438        }
439
440}