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-2025 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009package imagingbook.common.image.access;
010
011import ij.process.ByteProcessor;
012import ij.process.FloatProcessor;
013import ij.process.ImageProcessor;
014import ij.process.ShortProcessor;
015import imagingbook.common.image.OutOfBoundsStrategy;
016import imagingbook.common.image.interpolation.InterpolationMethod;
017import imagingbook.common.image.interpolation.PixelInterpolator;
018
019/**
020 * The common (abstract) super-class for all image accessors to scalar-valued images. It inherits all methods from
021 * {@link ImageAccessor} but adds the methods {@link #getVal(int, int)}, {@link #getVal(double, double)} and
022 * {@link #setVal(int, int, float)} for reading and writing scalar-valued pixel data.
023 */
024public abstract class ScalarAccessor extends ImageAccessor {
025
026        private final PixelInterpolator interpolator; // performs interpolation
027
028        ScalarAccessor(ImageProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
029                super(ip, obs, ipm);
030                this.interpolator = PixelInterpolator.create(interpolationMethod);
031        }
032
033        /**
034         * Creates a new image accessor of general type {@link ScalarAccessor}. The concrete type of the returned instance
035         * depends on the specified image, i.e., {@link ByteAccessor} for {@link ByteProcessor}, {@link ShortAccessor} for
036         * {@link ShortProcessor}, {@link FloatAccessor} for {@link FloatProcessor}.
037         * An exception is thrown if the supplied image is not scalar-valued (i.e., a color image).
038         *
039         * @param ip the image to be accessed
040         * @param obs the out-of-bounds strategy to be used (use {@code null} for default settings)
041         * @param ipm the interpolation method to be used (use {@code null} for default settings)
042         * @return a new image accessor
043         */
044        public static ScalarAccessor create(ImageProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
045                if (ip instanceof ByteProcessor)
046                        return new ByteAccessor((ByteProcessor) ip, obs, ipm);
047                if (ip instanceof ShortProcessor)
048                        return new ShortAccessor((ShortProcessor) ip, obs, ipm);
049                if (ip instanceof FloatProcessor)
050                        return new FloatAccessor((FloatProcessor) ip, obs, ipm);
051                throw new IllegalArgumentException(
052                                "cannot create " + ScalarAccessor.class.getSimpleName() + " for " + ip.getClass().getSimpleName());
053        }
054
055        // /**
056        //  * Creates a new scalar accessor (subtype of {@link ScalarAccessor}) for the specified image.
057        //  *
058        //  * @param ip the image to be accessed
059        //  * @param obs the out-of-bounds strategy to be used (use {@code null} for default settings)
060        //  * @param ipm the interpolation method to be used (use {@code null} for default settings)
061        //  * @return a new {@link ByteAccessor}
062        //  */
063        // public static ByteAccessor create(ByteProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
064        //      return new ByteAccessor(ip, obs, ipm);
065        // }
066        //
067        // /**
068        //  * Creates a new scalar accessor (subtype of {@link ScalarAccessor}) for the specified image.
069        //  *
070        //  * @param ip the image to be accessed
071        //  * @param obs the out-of-bounds strategy to be used (use {@code null} for default settings)
072        //  * @param ipm the interpolation method to be used (use {@code null} for default settings)
073        //  * @return a new {@link ShortAccessor}
074        //  */
075        // public static ShortAccessor create(ShortProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
076        //      return new ShortAccessor(ip, obs, ipm);
077        // }
078        //
079        // /**
080        //  * Creates a new scalar accessor (subtype of {@link ScalarAccessor}) for the specified image.
081        //  *
082        //  * @param ip the image to be accessed
083        //  * @param obs the out-of-bounds strategy to be used (use {@code null} for default settings)
084        //  * @param ipm the interpolation method to be used (use {@code null} for default settings)
085        //  * @return a new {@link FloatAccessor}
086        //  */
087        // public static FloatAccessor create(FloatProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
088        //      return new FloatAccessor(ip, obs, ipm);
089        // }
090        
091        @Override
092        public int getDepth() {
093                return 1;
094        }
095        
096        @Override
097        public ScalarAccessor getComponentAccessor(int k) {
098                checkComponentIndex(k);
099                return this;
100        }
101
102        /**
103         * Reads and returns the scalar pixel value for the given image position. The value returned for coordinates outside
104         * the image boundaries depends on the {@link OutOfBoundsStrategy} specified for this {@link ImageAccessor}.
105         *
106         * @param u the x-coordinate
107         * @param v the y-coordinate
108         * @return the pixel value ({@code float})
109         */
110        public abstract float getVal(int u, int v); // returns pixel value at integer position (u, v)
111        
112        @Override
113        public float getVal(int u, int v, int k) {
114                checkComponentIndex(k);
115                return this.getVal(u, v);
116        }
117        
118        @Override
119        public float getVal(double x, double y, int k) {
120                checkComponentIndex(k);
121                return this.getVal(x, y);
122        }
123
124        /**
125         * Reads and returns the interpolated scalar pixel value for the given image position. The value returned for
126         * coordinates outside the image boundaries depends on the {@link OutOfBoundsStrategy} specified for this
127         * {@link ImageAccessor}.
128         *
129         * @param x the x-coordinate
130         * @param y the y-coordinate
131         * @return the pixel value ({@code float})
132         */
133        public float getVal(double x, double y) { // interpolating version
134                return interpolator.getInterpolatedValue(this, x, y);
135        }
136
137        /**
138         * Writes a scalar pixel value to the given image position. An exception is thrown if u, v coordinates are outside
139         * the image.
140         *
141         * @param u the x-coordinate
142         * @param v the y-coordinate
143         * @param val the new pixel value ({@code float})
144         */
145        public abstract void setVal(int u, int v, float val);
146        
147        public void setVal(int u, int v, int k, float val) {
148                if (k == 0) {
149                        this.setVal(u, v, val);
150                }
151                else {
152                        throw new IllegalArgumentException("invalid component index " + k);
153                }
154        }
155
156        @Override
157        public float[] getPix(int u, int v) {
158                return new float[] { this.getVal(u, v) };
159        }
160
161        @Override
162        public float[] getPix(double x, double y) {
163                return new float[] { this.getVal(x, y) };
164        }
165
166        @Override
167        public void setPix(int u, int v, float[] pix) {
168                this.setVal(u, v, pix[0]);
169        }
170        
171        // ---------------------------------------------------------------------
172        
173//      @Override
174//      public void setDefaultValue(float val) {
175//              this.defaultValue = val;
176//      }
177        
178//      public void setDefaultValue(float[] vals) {
179//              if (vals.length != 1) {
180//                      throw new IllegalArgumentException("default values must be of length " + 1);
181//              }
182//              this.setDefaultValue(vals[0]);
183//      }
184        
185        @Override
186        void checkComponentIndex(int k) {
187                if (k != 0) {
188                        throw new IllegalArgumentException("invalid component index " + k);
189                }
190        }
191}