source: josm/trunk/src/org/openstreetmap/josm/gui/layer/imagery/ColorfulFilter.java@ 13397

Last change on this file since 13397 was 13397, checked in by michael2402, 6 years ago

Fix #15878: Implement colorfullness filter for indexed images.

  • Property svn:eol-style set to native
File size: 7.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.imagery;
3
4import java.awt.Rectangle;
5import java.awt.RenderingHints;
6import java.awt.geom.Point2D;
7import java.awt.geom.Rectangle2D;
8import java.awt.image.BufferedImage;
9import java.awt.image.BufferedImageOp;
10import java.awt.image.ColorModel;
11import java.awt.image.DataBuffer;
12import java.awt.image.DataBufferByte;
13import java.awt.image.IndexColorModel;
14import java.util.Objects;
15import java.util.Optional;
16import java.util.function.Consumer;
17
18import org.openstreetmap.josm.tools.Logging;
19
20/**
21 * Colorful filter.
22 * @since 11914 (extracted from ColorfulImageProcessor)
23 */
24public class ColorfulFilter implements BufferedImageOp {
25 private static final double LUMINOSITY_RED = .21d;
26 private static final double LUMINOSITY_GREEN = .72d;
27 private static final double LUMINOSITY_BLUE = .07d;
28 private final double colorfulness;
29
30 /**
31 * Create a new colorful filter.
32 * @param colorfulness The colorfulness as defined in the {@link ColorfulImageProcessor} class.
33 */
34 ColorfulFilter(double colorfulness) {
35 this.colorfulness = colorfulness;
36 }
37
38 @Override
39 public BufferedImage filter(BufferedImage src, BufferedImage dst) {
40 if (src.getWidth() == 0 || src.getHeight() == 0 || src.getType() == BufferedImage.TYPE_CUSTOM) {
41 return src;
42 }
43
44 BufferedImage dest = Optional.ofNullable(dst).orElseGet(() -> createCompatibleDestImage(src, null));
45 int type = src.getType();
46
47 if (type == BufferedImage.TYPE_BYTE_INDEXED) {
48 return filterIndexed(src, dest);
49 }
50
51 DataBuffer srcBuffer = src.getRaster().getDataBuffer();
52 DataBuffer destBuffer = dest.getRaster().getDataBuffer();
53 if (!(srcBuffer instanceof DataBufferByte) || !(destBuffer instanceof DataBufferByte)) {
54 Logging.trace("Cannot apply color filter: Images do not use DataBufferByte.");
55 return src;
56 }
57
58 if (type != dest.getType()) {
59 Logging.trace("Cannot apply color filter: Src / Dest differ in type (" + type + '/' + dest.getType() + ')');
60 return src;
61 }
62 int redOffset;
63 int greenOffset;
64 int blueOffset;
65 int alphaOffset = 0;
66 switch (type) {
67 case BufferedImage.TYPE_3BYTE_BGR:
68 blueOffset = 0;
69 greenOffset = 1;
70 redOffset = 2;
71 break;
72 case BufferedImage.TYPE_4BYTE_ABGR:
73 case BufferedImage.TYPE_4BYTE_ABGR_PRE:
74 blueOffset = 1;
75 greenOffset = 2;
76 redOffset = 3;
77 break;
78 case BufferedImage.TYPE_INT_ARGB:
79 case BufferedImage.TYPE_INT_ARGB_PRE:
80 redOffset = 0;
81 greenOffset = 1;
82 blueOffset = 2;
83 alphaOffset = 3;
84 break;
85 default:
86 Logging.trace("Cannot apply color filter: Source image is of wrong type (" + type + ").");
87 return src;
88 }
89 doFilter((DataBufferByte) srcBuffer, (DataBufferByte) destBuffer, redOffset, greenOffset, blueOffset,
90 alphaOffset, src.getAlphaRaster() != null);
91 return dest;
92 }
93
94 /**
95 * Fast alternative for indexed images: We can change the palette here.
96 * @param src The source image
97 * @param dest The image to copy the source to
98 * @return The image.
99 */
100 private BufferedImage filterIndexed(BufferedImage src, BufferedImage dest) {
101 Objects.requireNonNull(dest, "dst needs to be non null");
102 if (src.getType() != BufferedImage.TYPE_BYTE_INDEXED) {
103 throw new IllegalArgumentException("Source must be of type TYPE_BYTE_INDEXED");
104 }
105 if (dest.getType() != BufferedImage.TYPE_BYTE_INDEXED) {
106 throw new IllegalArgumentException("Destination must be of type TYPE_BYTE_INDEXED");
107 }
108 if (!(src.getColorModel() instanceof IndexColorModel)) {
109 throw new IllegalArgumentException("Expecting an IndexColorModel for a image of type TYPE_BYTE_INDEXED");
110 }
111 src.copyData(dest.getRaster());
112
113 IndexColorModel model = (IndexColorModel) src.getColorModel();
114 int size = model.getMapSize();
115 byte[] red = getIndexColorModelData(size, model::getReds);
116 byte[] green = getIndexColorModelData(size, model::getGreens);
117 byte[] blue = getIndexColorModelData(size, model::getBlues);
118 byte[] alphas = getIndexColorModelData(size, model::getAlphas);
119
120 for (int i = 0; i < size; i++) {
121 int r = red[i] & 0xff;
122 int g = green[i] & 0xff;
123 int b = blue[i] & 0xff;
124 double luminosity = r * LUMINOSITY_RED + g * LUMINOSITY_GREEN + b * LUMINOSITY_BLUE;
125 red[i] = mix(r, luminosity);
126 green[i] = mix(g, luminosity);
127 blue[i] = mix(b, luminosity);
128 }
129
130 IndexColorModel dstModel = new IndexColorModel(model.getPixelSize(), model.getMapSize(), red, green, blue, alphas);
131 return new BufferedImage(dstModel, dest.getRaster(), dest.isAlphaPremultiplied(), null);
132 }
133
134 private static byte[] getIndexColorModelData(int size, Consumer<byte[]> consumer) {
135 byte[] data = new byte[size];
136 consumer.accept(data);
137 return data;
138 }
139
140 private void doFilter(DataBufferByte src, DataBufferByte dest, int redOffset, int greenOffset, int blueOffset,
141 int alphaOffset, boolean hasAlpha) {
142 byte[] srcPixels = src.getData();
143 byte[] destPixels = dest.getData();
144 if (srcPixels.length != destPixels.length) {
145 Logging.trace("Cannot apply color filter: Source/Dest lengths differ.");
146 return;
147 }
148 int entries = hasAlpha ? 4 : 3;
149 for (int i = 0; i < srcPixels.length; i += entries) {
150 int r = srcPixels[i + redOffset] & 0xff;
151 int g = srcPixels[i + greenOffset] & 0xff;
152 int b = srcPixels[i + blueOffset] & 0xff;
153 double luminosity = r * LUMINOSITY_RED + g * LUMINOSITY_GREEN + b * LUMINOSITY_BLUE;
154 destPixels[i + redOffset] = mix(r, luminosity);
155 destPixels[i + greenOffset] = mix(g, luminosity);
156 destPixels[i + blueOffset] = mix(b, luminosity);
157 if (hasAlpha) {
158 destPixels[i + alphaOffset] = srcPixels[i + alphaOffset];
159 }
160 }
161 }
162
163 private byte mix(int color, double luminosity) {
164 int val = (int) (colorfulness * color + (1 - colorfulness) * luminosity);
165 if (val < 0) {
166 return 0;
167 } else if (val > 0xff) {
168 return (byte) 0xff;
169 } else {
170 return (byte) val;
171 }
172 }
173
174 @Override
175 public Rectangle2D getBounds2D(BufferedImage src) {
176 return new Rectangle(src.getWidth(), src.getHeight());
177 }
178
179 @Override
180 public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
181 return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
182 }
183
184 @Override
185 public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
186 return (Point2D) srcPt.clone();
187 }
188
189 @Override
190 public RenderingHints getRenderingHints() {
191 return null;
192 }
193}
Note: See TracBrowser for help on using the repository browser.