source: josm/trunk/src/org/openstreetmap/josm/tools/ImageWarp.java@ 15547

Last change on this file since 15547 was 14816, checked in by Don-vip, 6 years ago

see #17387 - add more details when NegativeArraySizeException occurs to be sure it's caused by https://bugs.openjdk.java.net/browse/JDK-4690476

  • Property svn:eol-style set to native
File size: 7.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import java.awt.Dimension;
5import java.awt.geom.Point2D;
6import java.awt.geom.Rectangle2D;
7import java.awt.image.BufferedImage;
8import java.util.HashMap;
9import java.util.HashSet;
10import java.util.Map;
11import java.util.Objects;
12import java.util.Set;
13
14/**
15 * Image warping algorithm.
16 *
17 * Deforms an image geometrically according to a given transformation formula.
18 * @since 11858
19 */
20public final class ImageWarp {
21
22 private ImageWarp() {
23 // Hide default constructor
24 }
25
26 /**
27 * Transformation that translates the pixel coordinates.
28 */
29 public interface PointTransform {
30 /**
31 * Translates pixel coordinates.
32 * @param pt pixel coordinates
33 * @return transformed pixel coordinates
34 */
35 Point2D transform(Point2D pt);
36 }
37
38 /**
39 * Wrapper that optimizes a given {@link ImageWarp.PointTransform}.
40 *
41 * It does so by spanning a grid with certain step size. It will invoke the
42 * potentially expensive master transform only at those grid points and use
43 * bilinear interpolation to approximate transformed values in between.
44 * <p>
45 * For memory optimization, this class assumes that rows are more or less scanned
46 * one-by-one as is done in {@link ImageWarp#warp}. I.e. this transform is <em>not</em>
47 * random access in the y coordinate.
48 */
49 public static class GridTransform implements ImageWarp.PointTransform {
50
51 private final double stride;
52 private final ImageWarp.PointTransform trfm;
53
54 private final Map<Integer, Map<Integer, Point2D>> cache;
55
56 private final boolean consistencyTest;
57 private final Set<Integer> deletedRows;
58
59 /**
60 * Create a new GridTransform.
61 * @param trfm the master transform, that needs to be optimized
62 * @param stride step size
63 */
64 public GridTransform(ImageWarp.PointTransform trfm, double stride) {
65 this.trfm = trfm;
66 this.stride = stride;
67 this.cache = new HashMap<>();
68 this.consistencyTest = Logging.isDebugEnabled();
69 if (consistencyTest) {
70 deletedRows = new HashSet<>();
71 } else {
72 deletedRows = null;
73 }
74 }
75
76 @Override
77 public Point2D transform(Point2D pt) {
78 int xIdx = (int) Math.floor(pt.getX() / stride);
79 int yIdx = (int) Math.floor(pt.getY() / stride);
80 double dx = pt.getX() / stride - xIdx;
81 double dy = pt.getY() / stride - yIdx;
82 Point2D value00 = getValue(xIdx, yIdx);
83 Point2D value01 = getValue(xIdx, yIdx + 1);
84 Point2D value10 = getValue(xIdx + 1, yIdx);
85 Point2D value11 = getValue(xIdx + 1, yIdx + 1);
86 double valueX = (value00.getX() * (1-dx) + value10.getX() * dx) * (1-dy) +
87 (value01.getX() * (1-dx) + value11.getX() * dx) * dy;
88 double valueY = (value00.getY() * (1-dx) + value10.getY() * dx) * (1-dy) +
89 (value01.getY() * (1-dx) + value11.getY() * dx) * dy;
90 return new Point2D.Double(valueX, valueY);
91 }
92
93 private Point2D getValue(int xIdx, int yIdx) {
94 return getRow(yIdx).computeIfAbsent(xIdx, k -> trfm.transform(new Point2D.Double(xIdx * stride, yIdx * stride)));
95 }
96
97 private Map<Integer, Point2D> getRow(int yIdx) {
98 cleanUp(yIdx - 3);
99 Map<Integer, Point2D> row = cache.get(yIdx);
100 if (row == null) {
101 row = new HashMap<>();
102 cache.put(yIdx, row);
103 if (consistencyTest) {
104 // should not create a row that has been deleted before
105 if (deletedRows.contains(yIdx)) throw new AssertionError();
106 // only ever cache 3 rows at once
107 if (cache.size() > 3) throw new AssertionError();
108 }
109 }
110 return row;
111 }
112
113 // remove rows from cache that will no longer be used
114 private void cleanUp(int yIdx) {
115 Map<Integer, Point2D> del = cache.remove(yIdx);
116 if (consistencyTest && del != null) {
117 // should delete each row only once
118 if (deletedRows.contains(yIdx)) throw new AssertionError();
119 deletedRows.add(yIdx);
120 }
121 }
122 }
123
124 /**
125 * Interpolation method.
126 */
127 public enum Interpolation {
128 /**
129 * Nearest neighbor.
130 *
131 * Simplest possible method. Faster, but not very good quality.
132 */
133 NEAREST_NEIGHBOR,
134
135 /**
136 * Bilinear.
137 *
138 * Decent quality.
139 */
140 BILINEAR;
141 }
142
143 /**
144 * Warp an image.
145 * @param srcImg the original image
146 * @param targetDim dimension of the target image
147 * @param invTransform inverse transformation (translates pixel coordinates
148 * of the target image to pixel coordinates of the original image)
149 * @param interpolation the interpolation method
150 * @return the warped image
151 */
152 public static BufferedImage warp(BufferedImage srcImg, Dimension targetDim, PointTransform invTransform, Interpolation interpolation) {
153 BufferedImage imgTarget = new BufferedImage(targetDim.width, targetDim.height, BufferedImage.TYPE_INT_ARGB);
154 Rectangle2D srcRect = new Rectangle2D.Double(0, 0, srcImg.getWidth(), srcImg.getHeight());
155 for (int j = 0; j < imgTarget.getHeight(); j++) {
156 for (int i = 0; i < imgTarget.getWidth(); i++) {
157 Point2D srcCoord = invTransform.transform(new Point2D.Double(i, j));
158 if (srcRect.contains(srcCoord)) {
159 int rgba;
160 switch (interpolation) {
161 case NEAREST_NEIGHBOR:
162 rgba = getColor((int) Math.round(srcCoord.getX()), (int) Math.round(srcCoord.getY()), srcImg);
163 break;
164 case BILINEAR:
165 int x0 = (int) Math.floor(srcCoord.getX());
166 double dx = srcCoord.getX() - x0;
167 int y0 = (int) Math.floor(srcCoord.getY());
168 double dy = srcCoord.getY() - y0;
169 int c00 = getColor(x0, y0, srcImg);
170 int c01 = getColor(x0, y0 + 1, srcImg);
171 int c10 = getColor(x0 + 1, y0, srcImg);
172 int c11 = getColor(x0 + 1, y0 + 1, srcImg);
173 rgba = 0;
174 // loop over color components: blue, green, red, alpha
175 for (int ch = 0; ch <= 3; ch++) {
176 int shift = 8 * ch;
177 int chVal = (int) Math.round(
178 (((c00 >> shift) & 0xff) * (1-dx) + ((c10 >> shift) & 0xff) * dx) * (1-dy) +
179 (((c01 >> shift) & 0xff) * (1-dx) + ((c11 >> shift) & 0xff) * dx) * dy);
180 rgba |= chVal << shift;
181 }
182 break;
183 default:
184 throw new AssertionError(Objects.toString(interpolation));
185 }
186 imgTarget.setRGB(i, j, rgba);
187 }
188 }
189 }
190 return imgTarget;
191 }
192
193 private static int getColor(int x, int y, BufferedImage img) {
194 // border strategy: continue with the color of the outermost pixel,
195 return img.getRGB(
196 Utils.clamp(x, 0, img.getWidth() - 1),
197 Utils.clamp(y, 0, img.getHeight() - 1));
198 }
199}
Note: See TracBrowser for help on using the repository browser.