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-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009 010package imagingbook.common.image; 011 012/** 013 * <p> 014 * Instances of this class perform the transformation between 2D image coordinates and indexes into the associated 1D 015 * pixel array and vice versa. As usual images are assumed to be stored in row-major order. Instances of this class do 016 * not hold any image data themselves, they just perform the indexing task. This is used, for example, by class 017 * {@link PixelPack}, which provides a universal image data container which uses {@link GridIndexer2D} internally. 018 * </p> 019 * <p> 020 * The (abstract) method {@link #getIndex(int, int)} returns the 1D array index for a pair of 2D image coordinates. It 021 * is implemented by the inner subclasses {@link ZeroValueIndexer}, {@link MirrorImageIndexer} and 022 * {@link NearestBorderIndexer}. They exhibit different behaviors when accessing out-of-image coordinates (see 023 * {@link OutOfBoundsStrategy}). 024 * </p> 025 * 026 * @author WB 027 * @version 2022/09/17 028 * @see OutOfBoundsStrategy 029 */ 030public abstract class GridIndexer2D implements Cloneable { 031 032 public static final OutOfBoundsStrategy DefaultOutOfBoundsStrategy = OutOfBoundsStrategy.NearestBorder; 033 034 /** 035 * Creates and returns a new {@link GridIndexer2D} with the specified size and {@link OutOfBoundsStrategy}. 036 * 037 * @param width grid size (horizontal) 038 * @param height grid size (vertical) 039 * @param obs out-of-bounds strategy 040 * @return a new {@link GridIndexer2D} 041 */ 042 public static GridIndexer2D create(int width, int height, OutOfBoundsStrategy obs) { 043 if (obs == null) { 044 obs = DefaultOutOfBoundsStrategy; 045 } 046 switch (obs) { 047 case ZeroValues : return new ZeroValueIndexer(width, height); 048 case NearestBorder : return new NearestBorderIndexer(width, height); 049 case MirrorImage : return new MirrorImageIndexer(width, height); 050 case ThrowException : return new ExceptionIndexer(width, height); 051 } 052 return null; 053 } 054 055 final int width; 056 final int height; 057 final OutOfBoundsStrategy obs; 058 059 private GridIndexer2D(int width, int height, OutOfBoundsStrategy obs) { 060 this.width = width; 061 this.height = height; 062 this.obs = obs; 063 } 064 065 /** 066 * Returns the 1D array index for a given pair of image coordinates. For u, v coordinates outside the image, the 067 * returned index depends on the concrete sub-class of {@link GridIndexer2D}. As a general rule, this method either 068 * returns a valid 1D array index or throws an exception. 069 * Subclasses implement (override) this method. 070 * 071 * @param u x-coordinate 072 * @param v y-coordinate 073 * @return 1D array index 074 */ 075 public abstract int getIndex(int u, int v); 076 077 /** 078 * Returns the 1D array index assuming that the specified coordinates are inside the image. 079 * 080 * @param u x-coordinate 081 * @param v y-coordinate 082 * @return the associated 1D index 083 */ 084 private int getWithinBoundsIndex(int u, int v) { 085 return width * v + u; 086 } 087 088 /** 089 * Returns the width of the associated image. 090 * @return the image width 091 */ 092 public int getWidth() { 093 return this.width; 094 } 095 096 /** 097 * Returns the height of the associated image. 098 * @return the image height 099 */ 100 public int getHeight() { 101 return this.height; 102 } 103 104 /** 105 * Returns the out-of-bounds strategy (see {qlink OutOfBoundsStrategy} used by this indexer. 106 * 107 * @return the out-of-bounds strategy 108 */ 109 public OutOfBoundsStrategy getOutOfBoundsStrategy() { 110 return this.obs; 111 } 112 113 // --------------------------------------------------------- 114 115 /** 116 * This indexer returns the closest border pixel for coordinates outside the image bounds. This is the most common 117 * method. There is no public constructor. To instantiate use method 118 * {@link GridIndexer2D#create(int, int, OutOfBoundsStrategy)} with {@link OutOfBoundsStrategy#NearestBorder}. 119 */ 120 public static class NearestBorderIndexer extends GridIndexer2D { 121 122 NearestBorderIndexer(int width, int height) { 123 super(width, height, OutOfBoundsStrategy.NearestBorder); 124 } 125 126 @Override 127 public int getIndex(int u, int v) { 128 if (u < 0) { 129 u = 0; 130 } 131 else if (u >= width) { 132 u = width - 1; 133 } 134 if (v < 0) { 135 v = 0; 136 } 137 else if (v >= height) { 138 v = height - 1; 139 } 140 return super.getWithinBoundsIndex(u, v); 141 } 142 } 143 144 /** 145 * This indexer returns mirrored image values for coordinates outside the image bounds. There is no public 146 * constructor. To instantiate use method {@link GridIndexer2D#create(int, int, OutOfBoundsStrategy)} with 147 * {@link OutOfBoundsStrategy#MirrorImage}. 148 */ 149 public static class MirrorImageIndexer extends GridIndexer2D { 150 151 MirrorImageIndexer(int width, int height) { 152 super(width, height, OutOfBoundsStrategy.MirrorImage); 153 } 154 155 @Override 156 public int getIndex(int u, int v) { 157 // fast modulo operation for positive divisors only 158 u = u % width; 159 if (u < 0) { 160 u = u + width; 161 } 162 v = v % height; 163 if (v < 0) { 164 v = v + height; 165 } 166 return super.getWithinBoundsIndex(u, v); 167 } 168 } 169 170 /** 171 * This indexer returns -1 for coordinates outside the image bounds, indicating that a (predefined) default value 172 * should be used. There is no public constructor. To instantiate use method 173 * {@link GridIndexer2D#create(int, int, OutOfBoundsStrategy)} with {@link OutOfBoundsStrategy#ZeroValues}. 174 */ 175 public static class ZeroValueIndexer extends GridIndexer2D { 176 177 ZeroValueIndexer(int width, int height) { 178 super(width, height, OutOfBoundsStrategy.ZeroValues); 179 } 180 181 @Override 182 public int getIndex(int u, int v) { 183 if (u < 0 || u >= width || v < 0 || v >= height) { 184 return -1; 185 } 186 else { 187 return super.getWithinBoundsIndex(u, v); 188 } 189 } 190 } 191 192 /** 193 * This indexer throws an exception if coordinates outside image bounds are accessed. There is no public 194 * constructor. To instantiate use method {@link GridIndexer2D#create(int, int, OutOfBoundsStrategy)} with 195 * {@link OutOfBoundsStrategy#ThrowException}. 196 */ 197 public static class ExceptionIndexer extends GridIndexer2D { 198 199 ExceptionIndexer(int width, int height) { 200 super(width, height, OutOfBoundsStrategy.ThrowException); 201 } 202 203 @Override 204 public int getIndex(int u, int v) throws OutOfImageException { 205 if (u < 0 || u >= width || v < 0 || v >= height) { 206 throw new OutOfImageException( 207 String.format("out-of-image position [%d,%d]", u, v)); 208 } 209 else 210 return super.getWithinBoundsIndex(u, v); 211 } 212 } 213 214 // ----------------------------------------------------------- 215 216 /** 217 * Exception to be thrown by {@link ExceptionIndexer}. 218 */ 219 public static class OutOfImageException extends RuntimeException { 220 private static final long serialVersionUID = 1L; 221 222 public OutOfImageException(String message) { 223 super(message); 224 } 225 } 226 227} 228 229