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 = {
030        0, 0, 0,
031        0, 1, 0,
032        0, 0, 0
033    };
034
035    private static final float[] KERNEL_BLUR = {
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 = {
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}