001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019
020package lucee.runtime.img;
021import java.awt.image.BufferedImage;
022import java.awt.image.ColorModel;
023
024import lucee.runtime.exp.ExpressionException;
025import lucee.runtime.img.interpolation.Bessel;
026import lucee.runtime.img.interpolation.Blackman;
027import lucee.runtime.img.interpolation.Hamming;
028import lucee.runtime.img.interpolation.Hanning;
029import lucee.runtime.img.interpolation.Hermite;
030import lucee.runtime.img.interpolation.Interpolation;
031import lucee.runtime.img.interpolation.Lanczos;
032import lucee.runtime.img.interpolation.Mitchell;
033import lucee.runtime.img.interpolation.Quadratic;
034import lucee.runtime.img.interpolation.Triangle;
035
036
037public class ImageResizer       {
038    
039    private static class ContributionInfo {
040        int pixel;
041        double weight;
042        
043        
044        public void setPixel(int pixel) {
045            this.pixel = pixel;
046        }
047        
048        public int getPixel() {
049            return pixel;
050        }
051        
052        public void setWeight(double weight) {
053            this.weight = weight;
054        }
055        
056        public double getWeight() {
057            return weight;
058        }
059    }
060    
061    private static int horizontal(BufferedImage source, BufferedImage destination, double xFactor,Interpolation ip, double blur, ContributionInfo[] contribution) {
062        if (source.getWidth() == destination.getWidth()
063            && source.getHeight() == destination.getHeight()) {
064            destination.setData(source.getData());
065            return destination.getWidth();
066        }
067        double scale = blur * Math.max(1.0 / xFactor, 1.0);
068        double support = Math.max(scale * ip.getSupport(), 0.5);
069        if (support <= 0.5) {
070            support = 0.500000000001;
071            scale = 1.0;
072        }
073        ColorModel cm = ColorModel.getRGBdefault();
074        for (int x = 0; x < destination.getWidth(); x++) {
075            double center = x / xFactor;
076            int start = (int) Math.max(center - support + 0.5, 0.0);
077            int end = (int) Math.min(center + support + 0.5,
078                                     source.getWidth());
079            int n = setContributionWeight(start, end, contribution, ip,
080                                          center, scale);
081            int sourceWidth = (contribution[n - 1].getPixel()
082                               - contribution[0].getPixel() + 1);
083            int[] sourcePixels = new int[sourceWidth * source.getHeight()];
084            int[] destPixels = new int[destination.getHeight()];
085            sourcePixels = source.getRGB(contribution[0].getPixel(), 0,
086                                         sourceWidth, source.getHeight(),
087                                         sourcePixels, 0, sourceWidth);
088            int destIndex = 0;
089            for (int y = 0; y < destination.getHeight(); y++) {
090                double blue = 0.0;
091                double green = 0.0;
092                double red = 0.0;
093                double opacity = 0.0;
094                int[] c = new int[4];
095                for (int i = 0; i < n; i++) {
096                    int j = (y * (contribution[n - 1].getPixel()
097                                  - contribution[0].getPixel() + 1)
098                             + (contribution[i].getPixel()
099                                - contribution[0].getPixel()));
100                    c = cm.getComponents(sourcePixels[j], c, 0);
101                    red += contribution[i].getWeight() * c[0];
102                    green += contribution[i].getWeight() * c[1];
103                    blue += contribution[i].getWeight() * c[2];
104                    opacity += contribution[i].getWeight() * c[3];
105                }
106                red = red < 0.0 ? 0.0 : red > 255.0 ? 255.0 : red + 0.5;
107                green
108                    = green < 0.0 ? 0.0 : green > 255.0 ? 255.0 : green + 0.5;
109                blue = blue < 0.0 ? 0.0 : blue > 255.0 ? 255.0 : blue + 0.5;
110                opacity = (opacity < 0.0 ? 0.0 : opacity > 255.0 ? 255.0
111                           : opacity + 0.5);
112                destPixels[destIndex]
113                    = cm.getDataElement(new int[] { (int) red, (int) green,
114                                                    (int) blue,
115                                                    (int) opacity },
116                                        0);
117                destIndex++;
118            }
119            destination.setRGB(x, 0, 1, destination.getHeight(), destPixels, 0,
120                               1);
121        }
122        return destination.getWidth();
123    }
124    
125    private static int setContributionWeight(int start, int end, ContributionInfo[] contribution, Interpolation interpolation, double center, double scale) {
126                int n = 0;
127                double density = 0.0;
128                for (int i = start; i < end; i++) {
129                    contribution[n].setPixel(i);
130                    contribution[n].setWeight(interpolation.f((i - center + 0.5D)
131                                                       / scale) / scale);
132                    density += contribution[n].getWeight();
133                    n++;
134                }
135                density = density == 0.0 ? 1.0 : 1.0 / density;
136                for (int i = 0; i < n; i++)
137                    contribution[i].setWeight(contribution[i].getWeight() * density);
138                return n;
139    }
140    
141    private static int vertical(BufferedImage source, BufferedImage destination, double y_factor, Interpolation ip, double blur, ContributionInfo[] contribution) {
142        /*if (source.getWidth() == destination.getWidth()
143            && source.getHeight() == destination.getHeight()) {
144            destination.setData(source.getData());
145            return destination.getWidth();
146        }*/
147        double scale = blur * Math.max(1.0 / y_factor, 1.0);
148        double support = Math.max(scale * ip.getSupport(), 0.5);
149        if (support <= 0.5) {
150            support = 0.500000000001;
151            scale = 1.0;
152        }
153        ColorModel cm = ColorModel.getRGBdefault();
154        for (int y = 0; y < destination.getHeight(); y++) {
155            double center =  y / y_factor;
156            int start = (int) Math.max(center - support + 0.5, 0.0);
157            int end = (int) Math.min(center + support + 0.5,
158                                     source.getHeight());
159            int n = setContributionWeight(start, end, contribution, ip,
160                                          center, scale);
161            int sourceHeight = (contribution[n - 1].getPixel()
162                                - contribution[0].getPixel() + 1);
163            int[] sourcePixels = new int[source.getWidth() * sourceHeight];
164            int[] destPixels = new int[destination.getWidth()];
165            sourcePixels = source.getRGB(0, contribution[0].getPixel(),
166                                         source.getWidth(), sourceHeight,
167                                         sourcePixels, 0, source.getWidth());
168            int destIndex = 0;
169            for (int x = 0; x < destination.getWidth(); x++) {
170                double blue = 0.0;
171                double green = 0.0;
172                double red = 0.0;
173                double opacity = 0.0;
174                int[] c = new int[4];
175                for (int i = 0; i < n; i++) {
176                    int j = ((contribution[i].getPixel()
177                              - contribution[0].getPixel()) * source.getWidth()
178                             + x);
179                    c = cm.getComponents(sourcePixels[j], c, 0);
180                    red += contribution[i].getWeight() * c[0];
181                    green += contribution[i].getWeight() * c[1];
182                    blue += contribution[i].getWeight() * c[2];
183                    opacity += contribution[i].getWeight() * c[3];
184                }
185                red = red < 0.0 ? 0.0 : red > 255.0 ? 255.0 : red + 0.5;
186                green
187                    = green < 0.0 ? 0.0 : green > 255.0 ? 255.0 : green + 0.5;
188                blue = blue < 0.0 ? 0.0 : blue > 255.0 ? 255.0 : blue + 0.5;
189                opacity = (opacity < 0.0 ? 0.0 : opacity > 255.0 ? 255.0
190                           : opacity + 0.5);
191                destPixels[destIndex]
192                    = cm.getDataElement(new int[] { (int) red, (int) green,
193                                                    (int) blue,
194                                                    (int) opacity },
195                                        0);
196                destIndex++;
197            }
198            destination.setRGB(0, y, destination.getWidth(), 1, destPixels, 0,
199                               destination.getWidth());
200        }
201        return destination.getHeight();
202    }
203    
204
205    
206    public static BufferedImage resize(BufferedImage image, int columns, int rows, int interpolation, double blur) throws ExpressionException {
207        if (columns == 0 || rows == 0)
208                throw new ExpressionException("invalid size for image");
209                
210        BufferedImage resizeImage = ImageUtil.createBufferedImage(image, columns, rows);
211                
212                Interpolation inter = getInterpolation(interpolation);
213                double xFactor = (double) columns / (double) image.getWidth();
214                double scale = blur * Math.max(1.0 / xFactor, 1.0);
215                double xSupport = Math.max(scale * inter.getSupport(), 0.5);
216                double yFactor = (double) rows / (double) image.getHeight();
217                scale = blur * Math.max(1.0 / yFactor, 1.0);
218                double ySupport = Math.max(scale * inter.getSupport(), 0.5);
219                double support = Math.max(xSupport, ySupport);
220                if (support < inter.getSupport())
221                    support = inter.getSupport();
222                ContributionInfo[] contribution = new ContributionInfo[(int) support * 2 + 3];
223                
224                for (int cloop = 0; cloop < (int) support * 2 + 3; cloop++)
225                    contribution[cloop] = new ContributionInfo();
226                
227                int status;
228                if (columns * (image.getHeight() + rows) < rows * (image.getWidth() + columns)) {
229                    BufferedImage sourceImage = ImageUtil.createBufferedImage(image, columns, image.getHeight());
230                    status = horizontal(image, sourceImage, xFactor, inter, blur, contribution);
231                    status |= vertical(sourceImage, resizeImage, yFactor,inter, blur, contribution);
232                } 
233                else {
234                    BufferedImage sourceImage = ImageUtil.createBufferedImage(image, image.getWidth(), rows);
235                    status = vertical(image, sourceImage, yFactor, inter, blur, contribution);
236                    status |= horizontal(sourceImage, resizeImage, xFactor, inter, blur, contribution);
237                }
238                
239                if (status == 0) throw new ExpressionException("can't resize image");
240                return resizeImage;
241    }
242    
243   
244    
245   
246    
247    private static Interpolation getInterpolation(int interpolation) throws ExpressionException {
248                switch (interpolation) {
249                
250                        case 0: return new Triangle();
251                        case Image.IP_HERMITE:  return new Hermite();
252                        case Image.IP_HANNING:  return new Hanning();
253                        case Image.IP_MEDIUMQUALITY:
254                        case Image.IP_HIGHPERFORMANCE:
255                        case Image.IP_HAMMING:  return new Hamming();
256                        case Image.IP_BLACKMAN: return new Blackman();
257                        case Image.IP_QUADRATIC:return new Quadratic();
258                        case Image.IP_HIGHQUALITY:
259                        case Image.IP_MEDIUMPERFORMANCE:
260                        case Image.IP_MITCHELL: return new Mitchell();
261                        case Image.IP_HIGHESTQUALITY:
262                        case Image.IP_LANCZOS:  return new Lanczos();
263                        case Image.IP_BESSEL:   return new Bessel();
264                        default:        throw new ExpressionException("invalid interpolation definition");
265                }
266    }
267    
268    
269}