1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.tools;
|
---|
3 |
|
---|
4 | import java.awt.Color;
|
---|
5 | import java.awt.Dimension;
|
---|
6 | import java.awt.geom.Point2D;
|
---|
7 | import java.awt.geom.Rectangle2D;
|
---|
8 | import java.awt.image.BufferedImage;
|
---|
9 |
|
---|
10 | /**
|
---|
11 | * Image warping algorithm.
|
---|
12 | *
|
---|
13 | * Deforms an image geometrically according to a given transformation formula.
|
---|
14 | * @since 11858
|
---|
15 | */
|
---|
16 | public class ImageWarp {
|
---|
17 |
|
---|
18 | /**
|
---|
19 | * Transformation that translates the pixel coordinates.
|
---|
20 | */
|
---|
21 | public interface PointTransform {
|
---|
22 | Point2D transform(Point2D pt);
|
---|
23 | }
|
---|
24 |
|
---|
25 | /**
|
---|
26 | * Interpolation method.
|
---|
27 | */
|
---|
28 | public enum Interpolation {
|
---|
29 | /**
|
---|
30 | * Nearest neighbor.
|
---|
31 | *
|
---|
32 | * Simplest possible method. Faster, but not very good quality.
|
---|
33 | */
|
---|
34 | NEAREST_NEIGHBOR(1),
|
---|
35 | /**
|
---|
36 | * Bilinear.
|
---|
37 | *
|
---|
38 | * Decent quality.
|
---|
39 | */
|
---|
40 | BILINEAR(2);
|
---|
41 |
|
---|
42 | private final int margin;
|
---|
43 |
|
---|
44 | private Interpolation(int margin) {
|
---|
45 | this.margin = margin;
|
---|
46 | }
|
---|
47 |
|
---|
48 | /**
|
---|
49 | * Number of pixels to scan outside the source image.
|
---|
50 | * Used to get smoother borders.
|
---|
51 | * @return the margin
|
---|
52 | */
|
---|
53 | public int getMargin() {
|
---|
54 | return margin;
|
---|
55 | }
|
---|
56 | }
|
---|
57 |
|
---|
58 | /**
|
---|
59 | * Warp an image.
|
---|
60 | * @param srcImg the original image
|
---|
61 | * @param targetDim dimension of the target image
|
---|
62 | * @param invTransform inverse transformation (translates pixel coordinates
|
---|
63 | * of the target image to pixel coordinates of the original image)
|
---|
64 | * @param interpolation the interpolation method
|
---|
65 | * @return the warped image
|
---|
66 | */
|
---|
67 | public static BufferedImage warp(BufferedImage srcImg, Dimension targetDim, PointTransform invTransform, Interpolation interpolation) {
|
---|
68 | BufferedImage imgTarget = new BufferedImage(targetDim.width, targetDim.height, BufferedImage.TYPE_INT_ARGB);
|
---|
69 | Rectangle2D srcRect = new Rectangle2D.Double(0, 0, srcImg.getWidth(), srcImg.getHeight());
|
---|
70 | for (int j = 0; j < imgTarget.getHeight(); j++) {
|
---|
71 | for (int i = 0; i < imgTarget.getWidth(); i++) {
|
---|
72 | Point2D srcCoord = invTransform.transform(new Point2D.Double(i, j));
|
---|
73 | if (isInside(srcCoord, srcRect, interpolation.getMargin())) {
|
---|
74 | int rgb;
|
---|
75 | switch (interpolation) {
|
---|
76 | case NEAREST_NEIGHBOR:
|
---|
77 | rgb = getColor((int) Math.round(srcCoord.getX()), (int) Math.round(srcCoord.getY()), srcImg).getRGB();
|
---|
78 | break;
|
---|
79 | case BILINEAR:
|
---|
80 | int x0 = (int) Math.floor(srcCoord.getX());
|
---|
81 | double dx = srcCoord.getX() - x0;
|
---|
82 | int y0 = (int) Math.floor(srcCoord.getY());
|
---|
83 | double dy = srcCoord.getY() - y0;
|
---|
84 | Color c00 = getColor(x0, y0, srcImg);
|
---|
85 | Color c01 = getColor(x0, y0 + 1, srcImg);
|
---|
86 | Color c10 = getColor(x0 + 1, y0, srcImg);
|
---|
87 | Color c11 = getColor(x0 + 1, y0 + 1, srcImg);
|
---|
88 | int red = (int) Math.round(
|
---|
89 | (c00.getRed() * (1-dx) + c10.getRed() * dx) * (1-dy) +
|
---|
90 | (c01.getRed() * (1-dx) + c11.getRed() * dx) * dy);
|
---|
91 | int green = (int) Math.round(
|
---|
92 | (c00.getGreen()* (1-dx) + c10.getGreen() * dx) * (1-dy) +
|
---|
93 | (c01.getGreen() * (1-dx) + c11.getGreen() * dx) * dy);
|
---|
94 | int blue = (int) Math.round(
|
---|
95 | (c00.getBlue()* (1-dx) + c10.getBlue() * dx) * (1-dy) +
|
---|
96 | (c01.getBlue() * (1-dx) + c11.getBlue() * dx) * dy);
|
---|
97 | int alpha = (int) Math.round(
|
---|
98 | (c00.getAlpha()* (1-dx) + c10.getAlpha() * dx) * (1-dy) +
|
---|
99 | (c01.getAlpha() * (1-dx) + c11.getAlpha() * dx) * dy);
|
---|
100 | rgb = new Color(red, green, blue, alpha).getRGB();
|
---|
101 | break;
|
---|
102 | default:
|
---|
103 | throw new AssertionError();
|
---|
104 | }
|
---|
105 | imgTarget.setRGB(i, j, rgb);
|
---|
106 | }
|
---|
107 | }
|
---|
108 | }
|
---|
109 | return imgTarget;
|
---|
110 | }
|
---|
111 |
|
---|
112 | private static boolean isInside(Point2D p, Rectangle2D rect, double margin) {
|
---|
113 | return isInside(p.getX(), rect.getMinX(), rect.getMaxX(), margin) &&
|
---|
114 | isInside(p.getY(), rect.getMinY(), rect.getMaxY(), margin);
|
---|
115 | }
|
---|
116 |
|
---|
117 | private static boolean isInside(double x, double xMin, double xMax, double margin) {
|
---|
118 | return x + margin >= xMin && x - margin <= xMax;
|
---|
119 | }
|
---|
120 |
|
---|
121 | private static Color getColor(int x, int y, BufferedImage img) {
|
---|
122 | // border strategy: continue with the color of the outermost pixel,
|
---|
123 | // but change alpha component to fully translucent
|
---|
124 | boolean transparent = false;
|
---|
125 | if (x < 0) {
|
---|
126 | x = 0;
|
---|
127 | transparent = true;
|
---|
128 | } else if (x >= img.getWidth()) {
|
---|
129 | x = img.getWidth() - 1;
|
---|
130 | transparent = true;
|
---|
131 | }
|
---|
132 | if (y < 0) {
|
---|
133 | y = 0;
|
---|
134 | transparent = true;
|
---|
135 | } else if (y >= img.getHeight()) {
|
---|
136 | y = img.getHeight() - 1;
|
---|
137 | transparent = true;
|
---|
138 | }
|
---|
139 | Color clr = new Color(img.getRGB(x, y));
|
---|
140 | if (!transparent)
|
---|
141 | return clr;
|
---|
142 | // keep color components, but set transparency to 0
|
---|
143 | // (the idea is that border fades out and mixes with next tile)
|
---|
144 | return new Color(clr.getRed(), clr.getGreen(), clr.getBlue(), 0);
|
---|
145 | }
|
---|
146 | }
|
---|