001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 *  image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause 
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 
006 * Copyright (c) 2006-2016 Wilhelm Burger, Mark J. Burge. All rights reserved. 
007 * Visit http://imagingbook.com for additional details.
008 *******************************************************************************/
009
010package imagingbook.lib.image;
011
012import ij.process.ByteProcessor;
013import ij.process.ColorProcessor;
014import ij.process.FloatProcessor;
015import ij.process.ImageProcessor;
016import ij.process.ShortProcessor;
017import imagingbook.lib.interpolation.InterpolationMethod;
018import imagingbook.lib.interpolation.PixelInterpolator;
019
020
021/**
022 * This class provides unified image access to all 4 types of images available in ImageJ.
023 * Byte, Short, Float: get/set values are passed as type float using getVal() and setVal().
024 * Byte, Short, Float, Rgb: uses a float[] to pass in values using getPix() and setPix().
025 * 
026 * getVal() and getPix() interpolate for non-integer coordinates.
027 * 
028 * @author W. Burger
029 * @version 2015/12/20
030 */
031public abstract class ImageAccessor {
032        
033        static OutOfBoundsStrategy DefaultOutOfBoundsStrategy = OutOfBoundsStrategy.DefaultValue;
034        static InterpolationMethod DefaultInterpolationMethod = InterpolationMethod.Bilinear;
035                
036        protected final int width;
037        protected final int height;
038        protected final PixelIndexer indexer;                           // implements the specified OutOfBoundsStrategy
039        protected final OutOfBoundsStrategy outOfBoundsStrategy;
040        protected final InterpolationMethod interpolationMethod;
041        
042        /**
043         * Creates a new {@code ImageAccessor} instance for the given image,
044         * using the default out-of-bounds strategy and interpolation method.
045         * 
046         * @param ip the source image
047         * @return a new {@code ImageAccessor} instance
048         */
049        public static ImageAccessor create(ImageProcessor ip) {
050                return create(ip, DefaultOutOfBoundsStrategy, DefaultInterpolationMethod);
051        }
052        
053        /**
054         * Creates a new {@code ImageAccessor} instance for the given image,
055         * using the specified out-of-bounds strategy and interpolation method.
056         * 
057         * @param ip the source image
058         * @param obs the out-of-bounds strategy (use {@code null} for default settings)
059         * @param ipm the interpolation method (use {@code null} for default settings)
060         * @return a new {@code ImageAccessor} instance
061         */
062        public static ImageAccessor create(ImageProcessor ip,  OutOfBoundsStrategy obs, InterpolationMethod ipm) {
063                if (ip instanceof ColorProcessor) {
064                        return ImageAccessor.Rgb.create(ip, obs, ipm);
065                }
066                else {
067                        return ImageAccessor.Scalar.create(ip, obs, ipm);
068                }
069        }
070        
071        // private constructor (used by all subtypes)
072        private ImageAccessor(ImageProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
073                this.width  = ip.getWidth();
074                this.height = ip.getHeight();
075                this.outOfBoundsStrategy = (obs != null) ? obs : DefaultOutOfBoundsStrategy;
076                this.interpolationMethod = (ipm != null) ? ipm : DefaultInterpolationMethod;
077                this.indexer = PixelIndexer.create(width, height, outOfBoundsStrategy);
078        }
079        
080        public abstract ImageProcessor getProcessor();
081        
082        public OutOfBoundsStrategy getOutOfBoundsStrategy() {
083                return outOfBoundsStrategy;
084        }
085
086        public InterpolationMethod getInterpolationMethod() {
087                return interpolationMethod;
088        }
089        
090        // all ImageAccessor's can do this (Gray and Color, get/set complete pixels):
091        public abstract float[] getPix(int u, int v);                   // returns pixel value at integer position (u, v)
092        public abstract float[] getPix(double x, double y);             // returns interpolated pixel value at real position (x, y)
093        public abstract void setPix(int u, int v, float[] val);
094        
095        // ------------------------------------------------------------
096        
097        public static abstract class Scalar extends ImageAccessor {
098                
099                public static ImageAccessor.Scalar create(ImageProcessor ip,  OutOfBoundsStrategy obs, InterpolationMethod ipm) {
100                        if (ip instanceof ByteProcessor)
101                                return new ImageAccessor.Byte((ByteProcessor) ip, obs, ipm);
102                        if (ip instanceof ShortProcessor)
103                                return new ImageAccessor.Short((ShortProcessor) ip, obs, ipm);
104                        if (ip instanceof FloatProcessor)
105                                return new ImageAccessor.Float((FloatProcessor) ip, obs, ipm);
106                        throw new IllegalArgumentException("cannot create ImageAccessor.Gray for this processor");
107                }
108                
109                protected final ImageProcessor ip;
110                protected final float pixelDefaultValue = 0.0f;
111                
112                // only Gray accessors can do this (get/set scalar values):
113                public abstract float getVal(int u, int v);                             // returns pixel value at integer position (u, v)
114                public abstract void setVal(int u, int v, float val);
115                
116                protected final PixelInterpolator interpolator; // performs interpolation
117                        
118                private Scalar(ImageProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
119                        super(ip, obs, ipm);
120                        this.ip = ip;
121                        this.interpolator = PixelInterpolator.create(interpolationMethod);
122                }
123                
124                public float getVal(double x, double y) {       // interpolating version
125                        return interpolator.getInterpolatedValue(this, x, y);
126                }
127                
128                @Override
129                public float[] getPix(int u, int v) {
130                        return new float[] {this.getVal(u, v)};
131                }
132                
133                @Override
134                public float[] getPix(double x, double y) {
135                        return new float[] {this.getVal(x, y)};
136                }
137                
138                @Override
139                public void setPix(int u, int v, float[] pix) {
140                        this.setVal(u, v, pix[0]);
141                }
142        }
143        
144        // ------------------------------------------------------------
145        
146        public static class Byte extends ImageAccessor.Scalar {
147                private final ByteProcessor ip;
148                private final byte[] pixels;
149                
150                
151                public Byte(ByteProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
152                        super(ip, obs, ipm);
153                        this.ip = ip;
154                        this.pixels = (byte[]) this.ip.getPixels();
155                }
156                
157                @Override
158                public ByteProcessor getProcessor() {
159                        return ip;
160                }
161                
162                @Override
163                public float getVal(int u, int v) {
164                        final int i = indexer.getIndex(u, v);
165                        if (i < 0) 
166                                return pixelDefaultValue;
167                        else {
168                                return (0xff & pixels[i]);
169                        }
170                }
171                
172                @Override
173                public void setVal(int u, int v, float valf) {
174                        int val = Math.round(valf);
175                        if (val < 0)
176                                val = 0;
177                        if (val > 255)
178                                val = 255;
179                        if (u >= 0 && u < width && v >= 0 && v < height) {
180                                pixels[width * v + u] = (byte) (0xFF & val);
181                        }
182                }
183        }
184        
185        // ------------------------------------------------------------
186        
187        public static class Short extends ImageAccessor.Scalar {
188                private final ShortProcessor ip;
189                private final short[] pixels;
190                
191                public Short(ShortProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
192                        super(ip, obs, ipm);
193                        this.ip = ip;
194                        this.pixels = (short[]) this.ip.getPixels();
195                }
196                
197                @Override
198                public ShortProcessor getProcessor() {
199                        return ip;
200                }
201                
202                @Override
203                public float getVal(int u, int v) {
204                        int i = indexer.getIndex(u, v);
205                        if (i < 0) 
206                                return pixelDefaultValue;
207                        else
208                                return (float) pixels[i];
209                }
210                
211                @Override
212                public void setVal(int u, int v, float valf) {
213                        int val = Math.round(valf);
214                        if (val < 0) val = 0;
215            if (val > 65535) val = 65535;
216                        if (u >= 0 && u < width && v >= 0 && v < height) {
217                                pixels[width * v + u] = (short) (0xFFFF & val);
218                        }
219                }
220        }
221        
222        // ------------------------------------------------------------
223        
224        public static class Float extends ImageAccessor.Scalar {
225                private final FloatProcessor ip;
226                private final float[] pixels;
227                
228                public Float(FloatProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
229                        super(ip, obs, ipm);
230                        this.ip = ip;
231                        this.pixels = (float[]) ip.getPixels();
232                }
233                
234                @Override
235                public FloatProcessor getProcessor() {
236                        return ip;
237                }
238                
239                @Override
240                public float getVal(int u, int v) {
241                        int i = indexer.getIndex(u, v);
242                        if (i < 0) 
243                                return pixelDefaultValue;
244                        else
245                                return pixels[i];
246                }
247                
248                @Override
249                public void setVal(int u, int v, float val) {   
250                        if (u >= 0 && u < width && v >= 0 && v < height) {
251                                pixels[width * v + u] = val;
252                        }
253                }
254        }
255        
256        // ------------------------------------------------------------
257        
258        public static class Rgb extends ImageAccessor {
259                private final ColorProcessor ip;
260                private final int[] pixels;
261                private final float[] pixelDefaultValue = { 0, 0, 0 };
262                private final int[] rgb = new int[3];
263                
264                private final ImageAccessor.Byte rAcc, gAcc, bAcc;
265                
266                
267                public static ImageAccessor.Rgb create(ImageProcessor ip,  OutOfBoundsStrategy obs, InterpolationMethod ipm) {
268                        if (ip instanceof ColorProcessor)
269                                return new ImageAccessor.Rgb((ColorProcessor) ip, obs, ipm);
270                        throw new IllegalArgumentException("cannot create ImageAccessor.Rgb for this processor");
271                }
272                
273                public Rgb(ColorProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
274                        super(ip, obs, ipm);
275                        this.ip = ip;
276                        this.pixels = (int[]) this.ip.getPixels();
277                        
278                        int width  = ip.getWidth();
279                        int height = ip.getHeight();
280                        ByteProcessor rp = new ByteProcessor(width, height);
281                        ByteProcessor gp = new ByteProcessor(width, height);
282                        ByteProcessor bp = new ByteProcessor(width, height);
283                        byte[] rpix = (byte[]) rp.getPixels();
284                        byte[] gpix = (byte[]) gp.getPixels();
285                        byte[] bpix = (byte[]) bp.getPixels();
286                        ip.getRGB(rpix, gpix, bpix);    // fill byte arrays
287                        rAcc = new ImageAccessor.Byte(rp, obs, ipm);
288                        gAcc = new ImageAccessor.Byte(gp, obs, ipm);
289                        bAcc = new ImageAccessor.Byte(bp, obs, ipm);
290                }
291                
292                @Override
293                public ColorProcessor getProcessor() {
294                        return ip;
295                }
296                
297                @Override
298                public float[] getPix(int u, int v) {   // returns an RGB value packed into a float[]
299                        int i = indexer.getIndex(u, v);
300                        if (i < 0) {
301                                return pixelDefaultValue;
302                        }
303                        else {
304                                int c = pixels[i];
305                                int red = (c & 0xff0000) >> 16;
306                                int grn = (c & 0xff00) >> 8;
307                                int blu = (c & 0xff);
308                                return new float[] {red, grn, blu};
309                        }       
310                }
311        
312                @Override
313                public void setPix(int u, int v, float[] valf) {
314                        if (u >= 0 && u < width && v >= 0 && v < height) {
315                                if (valf.length == 3) {
316                                        rgb[0] = clamp(Math.round(valf[0]));
317                                        rgb[1] = clamp(Math.round(valf[1]));
318                                        rgb[2] = clamp(Math.round(valf[2]));
319                                }
320                                else {
321                                        rgb[0] = clamp(Math.round(valf[0]));
322                                        rgb[1] = rgb[0];
323                                        rgb[2] = rgb[0];
324                                }
325                                int val = ((rgb[0] & 0xff) << 16) | ((rgb[1] & 0xff) << 8) | rgb[2] & 0xff;
326                                pixels[width * v + u] = val;
327                        }
328                }
329
330                @Override
331                public float[] getPix(double x, double y) {
332//                      return interpolator.getInterpolatedValue(new Point2D.Double(x, y));
333                        float red = rAcc.getVal(x, y);
334                        float grn = gAcc.getVal(x, y);
335                        float blu = bAcc.getVal(x, y);
336                        return new float[] { red, grn, blu };
337                }
338
339        }
340        
341        private static final int clamp(int val) {
342                if (val < 0) return 0;
343                if (val > 255) return 255;
344                return val;
345        }
346        
347}