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}