001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer.imagery; 003 004import java.awt.Rectangle; 005import java.awt.RenderingHints; 006import java.awt.geom.Point2D; 007import java.awt.geom.Rectangle2D; 008import java.awt.image.BufferedImage; 009import java.awt.image.BufferedImageOp; 010import java.awt.image.ColorModel; 011import java.awt.image.DataBuffer; 012import java.awt.image.DataBufferByte; 013 014import org.openstreetmap.josm.Main; 015import org.openstreetmap.josm.gui.layer.ImageProcessor; 016 017/** 018 * Adds or removes the colorfulness of the image. 019 * 020 * @author Michael Zangl 021 * @since 10547 022 */ 023public class ColorfulImageProcessor implements ImageProcessor { 024 private ColorfulFilter op; 025 private double colorfulness = 1; 026 027 /** 028 * Gets the colorfulness value. 029 * @return The value 030 */ 031 public double getColorfulness() { 032 return colorfulness; 033 } 034 035 /** 036 * Sets the colorfulness value. Clamps it to 0+ 037 * @param colorfulness The value 038 */ 039 public void setColorfulness(double colorfulness) { 040 if (colorfulness < 0) { 041 this.colorfulness = 0; 042 } else { 043 this.colorfulness = colorfulness; 044 } 045 046 if (this.colorfulness < .95 || this.colorfulness > 1.05) { 047 op = new ColorfulFilter(this.colorfulness); 048 } else { 049 op = null; 050 } 051 } 052 053 @Override 054 public BufferedImage process(BufferedImage image) { 055 if (op != null) { 056 return op.filter(image, null); 057 } else { 058 return image; 059 } 060 } 061 062 @Override 063 public String toString() { 064 return "ColorfulImageProcessor [colorfulness=" + colorfulness + ']'; 065 } 066 067 static class ColorfulFilter implements BufferedImageOp { 068 private final double colorfulness; 069 070 /** 071 * Create a new colorful filter. 072 * @param colorfulness The colorfulness as defined in the {@link ColorfulImageProcessor} class. 073 */ 074 ColorfulFilter(double colorfulness) { 075 this.colorfulness = colorfulness; 076 } 077 078 @Override 079 public BufferedImage filter(BufferedImage src, BufferedImage dst) { 080 if (src.getWidth() == 0 || src.getHeight() == 0) { 081 return src; 082 } 083 084 BufferedImage dest = dst; 085 if (dest == null) { 086 dest = createCompatibleDestImage(src, null); 087 } 088 DataBuffer srcBuffer = src.getRaster().getDataBuffer(); 089 DataBuffer destBuffer = dest.getRaster().getDataBuffer(); 090 if (!(srcBuffer instanceof DataBufferByte) || !(destBuffer instanceof DataBufferByte)) { 091 Main.trace("Cannot apply color filter: Images do not use DataBufferByte."); 092 return src; 093 } 094 095 int type = src.getType(); 096 if (type != dest.getType()) { 097 Main.trace("Cannot apply color filter: Src / Dest differ in type (" + type + '/' + dest.getType() + ')'); 098 return src; 099 } 100 int redOffset; 101 int greenOffset; 102 int blueOffset; 103 int alphaOffset = 0; 104 switch (type) { 105 case BufferedImage.TYPE_3BYTE_BGR: 106 blueOffset = 0; 107 greenOffset = 1; 108 redOffset = 2; 109 break; 110 case BufferedImage.TYPE_4BYTE_ABGR: 111 case BufferedImage.TYPE_4BYTE_ABGR_PRE: 112 blueOffset = 1; 113 greenOffset = 2; 114 redOffset = 3; 115 break; 116 case BufferedImage.TYPE_INT_ARGB: 117 case BufferedImage.TYPE_INT_ARGB_PRE: 118 redOffset = 0; 119 greenOffset = 1; 120 blueOffset = 2; 121 alphaOffset = 3; 122 break; 123 default: 124 Main.trace("Cannot apply color filter: Source image is of wrong type (" + type + ")."); 125 return src; 126 } 127 doFilter((DataBufferByte) srcBuffer, (DataBufferByte) destBuffer, redOffset, greenOffset, blueOffset, 128 alphaOffset, src.getAlphaRaster() != null); 129 return dest; 130 } 131 132 private void doFilter(DataBufferByte src, DataBufferByte dest, int redOffset, int greenOffset, int blueOffset, 133 int alphaOffset, boolean hasAlpha) { 134 byte[] srcPixels = src.getData(); 135 byte[] destPixels = dest.getData(); 136 if (srcPixels.length != destPixels.length) { 137 Main.trace("Cannot apply color filter: Source/Dest lengths differ."); 138 return; 139 } 140 int entries = hasAlpha ? 4 : 3; 141 for (int i = 0; i < srcPixels.length; i += entries) { 142 int r = srcPixels[i + redOffset] & 0xff; 143 int g = srcPixels[i + greenOffset] & 0xff; 144 int b = srcPixels[i + blueOffset] & 0xff; 145 double luminosity = r * .21d + g * .72d + b * .07d; 146 destPixels[i + redOffset] = mix(r, luminosity); 147 destPixels[i + greenOffset] = mix(g, luminosity); 148 destPixels[i + blueOffset] = mix(b, luminosity); 149 if (hasAlpha) { 150 destPixels[i + alphaOffset] = srcPixels[i + alphaOffset]; 151 } 152 } 153 } 154 155 private byte mix(int color, double luminosity) { 156 int val = (int) (colorfulness * color + (1 - colorfulness) * luminosity); 157 if (val < 0) { 158 return 0; 159 } else if (val > 0xff) { 160 return (byte) 0xff; 161 } else { 162 return (byte) val; 163 } 164 } 165 166 @Override 167 public Rectangle2D getBounds2D(BufferedImage src) { 168 return new Rectangle(src.getWidth(), src.getHeight()); 169 } 170 171 @Override 172 public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) { 173 return new BufferedImage(src.getWidth(), src.getHeight(), src.getType()); 174 } 175 176 @Override 177 public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { 178 return (Point2D) srcPt.clone(); 179 } 180 181 @Override 182 public RenderingHints getRenderingHints() { 183 return null; 184 } 185 } 186}