001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer.imagery; 003 004import java.awt.image.BufferedImage; 005import java.awt.image.ConvolveOp; 006import java.awt.image.Kernel; 007import java.util.Collections; 008import java.util.Map; 009 010import org.openstreetmap.josm.io.session.SessionAwareReadApply; 011import org.openstreetmap.josm.tools.ImageProcessor; 012import org.openstreetmap.josm.tools.Logging; 013import org.openstreetmap.josm.tools.Utils; 014 015/** 016 * Sharpens or blurs the image, depending on the sharpen value. 017 * <p> 018 * A positive sharpen level means that we sharpen the image. 019 * <p> 020 * A negative sharpen level let's us blur the image. -1 is the most useful value there. 021 * 022 * @author Michael Zangl 023 * @since 10547 024 */ 025public class SharpenImageProcessor implements ImageProcessor, SessionAwareReadApply { 026 private float sharpenLevel = 1.0f; 027 private ConvolveOp op; 028 029 private static final float[] KERNEL_IDENTITY = new float[] { 030 0, 0, 0, 031 0, 1, 0, 032 0, 0, 0 033 }; 034 035 private static final float[] KERNEL_BLUR = new float[] { 036 1f / 16, 2f / 16, 1f / 16, 037 2f / 16, 4f / 16, 2f / 16, 038 1f / 16, 2f / 16, 1f / 16 039 }; 040 041 private static final float[] KERNEL_SHARPEN = new float[] { 042 -.5f, -1f, -.5f, 043 -1f, 7, -1f, 044 -.5f, -1f, -.5f 045 }; 046 047 /** 048 * Gets the current sharpen level. 049 * @return The level. 050 */ 051 public float getSharpenLevel() { 052 return sharpenLevel; 053 } 054 055 /** 056 * Sets the sharpening level. 057 * @param sharpenLevel The level. Clamped to be positive or 0. 058 */ 059 public void setSharpenLevel(float sharpenLevel) { 060 if (sharpenLevel < 0) { 061 this.sharpenLevel = 0; 062 } else { 063 this.sharpenLevel = sharpenLevel; 064 } 065 066 if (this.sharpenLevel < 0.95) { 067 op = generateMixed(this.sharpenLevel, KERNEL_IDENTITY, KERNEL_BLUR); 068 } else if (this.sharpenLevel > 1.05) { 069 op = generateMixed(this.sharpenLevel - 1, KERNEL_SHARPEN, KERNEL_IDENTITY); 070 } else { 071 op = null; 072 } 073 } 074 075 private static ConvolveOp generateMixed(float aFactor, float[] a, float[] b) { 076 if (a.length != 9 || b.length != 9) { 077 throw new IllegalArgumentException("Illegal kernel array length."); 078 } 079 float[] values = new float[9]; 080 for (int i = 0; i < values.length; i++) { 081 values[i] = aFactor * a[i] + (1 - aFactor) * b[i]; 082 } 083 return new ConvolveOp(new Kernel(3, 3, values), ConvolveOp.EDGE_NO_OP, null); 084 } 085 086 @Override 087 public BufferedImage process(BufferedImage image) { 088 if (op != null) { 089 return op.filter(image, null); 090 } else { 091 return image; 092 } 093 } 094 095 @Override 096 public void applyFromPropertiesMap(Map<String, String> properties) { 097 String vStr = properties.get("sharpenlevel"); 098 if (vStr != null) { 099 try { 100 setSharpenLevel(Float.parseFloat(vStr)); 101 } catch (NumberFormatException e) { 102 Logging.trace(e); 103 } 104 } 105 } 106 107 @Override 108 public Map<String, String> toPropertiesMap() { 109 if (Utils.equalsEpsilon(sharpenLevel, 1.0)) 110 return Collections.emptyMap(); 111 else 112 return Collections.singletonMap("sharpenlevel", Float.toString(sharpenLevel)); 113 } 114 115 @Override 116 public String toString() { 117 return "SharpenImageProcessor [sharpenLevel=" + sharpenLevel + ']'; 118 } 119}