Ticket #12752: patch-image-sharpness.patch

File patch-image-sharpness.patch, 33.2 KB (added by michael2402, 9 years ago)
  • src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java

    diff --git a/images/dialogs/layerlist/colorfulness.png b/images/dialogs/layerlist/colorfulness.png
    new file mode 100644
    index 0000000..490f35a
    Binary files /dev/null and b/images/dialogs/layerlist/colorfulness.png differ
    diff --git a/images/dialogs/layerlist/colorfulness.xcf b/images/dialogs/layerlist/colorfulness.xcf
    new file mode 100644
    index 0000000..031cfae
    Binary files /dev/null and b/images/dialogs/layerlist/colorfulness.xcf differ
    diff --git a/images/dialogs/layerlist/gamma.png b/images/dialogs/layerlist/gamma.png
    index f67ada4..3afee11 100644
    Binary files a/images/dialogs/layerlist/gamma.png and b/images/dialogs/layerlist/gamma.png differ
    diff --git a/images/dialogs/layerlist/gamma.xcf b/images/dialogs/layerlist/gamma.xcf
    new file mode 100644
    index 0000000..8d8e59f
    Binary files /dev/null and b/images/dialogs/layerlist/gamma.xcf differ
    diff --git a/images/dialogs/layerlist/sharpness.png b/images/dialogs/layerlist/sharpness.png
    new file mode 100644
    index 0000000..b381935
    Binary files /dev/null and b/images/dialogs/layerlist/sharpness.png differ
    diff --git a/images/dialogs/layerlist/sharpness.xcf b/images/dialogs/layerlist/sharpness.xcf
    new file mode 100644
    index 0000000..3e50dd5
    Binary files /dev/null and b/images/dialogs/layerlist/sharpness.xcf differ
    diff --git a/images/dialogs/layerlist/transparency.png b/images/dialogs/layerlist/transparency.png
    index 3149c9d..0498ca1 100644
    Binary files a/images/dialogs/layerlist/transparency.png and b/images/dialogs/layerlist/transparency.png differ
    diff --git a/images/dialogs/layerlist/transparency.xcf b/images/dialogs/layerlist/transparency.xcf
    new file mode 100644
    index 0000000..906a920
    Binary files /dev/null and b/images/dialogs/layerlist/transparency.xcf differ
    diff --git a/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java b/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
    index 41cad51..82901e9 100644
    a b public class LayerListDialog extends ToggleDialog {  
    531531    public static final class LayerVisibilityAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
    532532        protected static final int SLIDER_STEPS = 100;
    533533        private static final double MAX_GAMMA_FACTOR = 2;
     534        private static final double MAX_SHARPNESS_FACTOR = 2;
     535        private static final double MAX_COLORFUL_FACTOR = 2;
    534536        private final LayerListModel model;
    535537        private final JPopupMenu popup;
    536         private JSlider opacitySlider;
    537         private JSlider gammaSlider;
    538538        private SideButton sideButton;
    539539        private JCheckBox visibilityCheckbox;
     540        final OpacitySlider opacitySlider = new OpacitySlider();
     541        private final ArrayList<FilterSlider<?>> sliders = new ArrayList<>();
    540542
    541543        /**
    542544         * Creates a new {@link LayerVisibilityAction}
    public class LayerListDialog extends ToggleDialog {  
    559561            visibilityCheckbox.addChangeListener(new ChangeListener() {
    560562                @Override
    561563                public void stateChanged(ChangeEvent e) {
    562                     setVisible(visibilityCheckbox.isSelected());
     564                    setVisibleFlag(visibilityCheckbox.isSelected());
    563565                }
    564566            });
    565567            content.add(visibilityCheckbox, GBC.eop());
    566568
    567             content.add(new JLabel(ImageProvider.get("dialogs/layerlist", "transparency")), GBC.std().span(1, 2).insets(0, 0, 5, 0));
    568             content.add(new JLabel(tr("Opacity")), GBC.eol());
    569             opacitySlider = new JSlider(JSlider.HORIZONTAL);
    570             opacitySlider.setMaximum(SLIDER_STEPS);
    571             opacitySlider.addChangeListener(new ChangeListener() {
    572                 @Override
    573                 public void stateChanged(ChangeEvent e) {
    574                     setOpacityValue(readOpacityValue(), opacitySlider.getValueIsAdjusting());
    575                 }
    576             });
    577             opacitySlider.setToolTipText(tr("Adjust opacity of the layer."));
    578             content.add(opacitySlider, GBC.eop());
    579 
    580             content.add(new JLabel(ImageProvider.get("dialogs/layerlist", "gamma")), GBC.std().span(1, 2).insets(0, 0, 5, 0));
    581             content.add(new JLabel(tr("Gamma")), GBC.eol());
    582             gammaSlider = new JSlider(JSlider.HORIZONTAL);
    583             gammaSlider.setMaximum(SLIDER_STEPS);
    584             gammaSlider.addChangeListener(new ChangeListener() {
    585                 @Override
    586                 public void stateChanged(ChangeEvent e) {
    587                     setGammaValue(readGammaValue());
    588                 }
    589             });
    590             gammaSlider.setToolTipText(tr("Adjust gamma value of the layer."));
    591             content.add(gammaSlider, GBC.eol());
    592         }
    593 
    594         protected double readOpacityValue() {
    595             return (double) opacitySlider.getValue() / SLIDER_STEPS;
     569            addSlider(content, opacitySlider);
     570            addSlider(content, new ColorfulnessSlider());
     571            addSlider(content, new GammaFilterSlider());
     572            addSlider(content, new SharpnessSlider());
    596573        }
    597574
    598         protected double readGammaValue() {
    599             return (double) gammaSlider.getValue() / SLIDER_STEPS * MAX_GAMMA_FACTOR;
     575        private void addSlider(JPanel content, FilterSlider<?> slider) {
     576            content.add(new JLabel(slider.getIcon()), GBC.std().span(1, 2).insets(0, 0, 5, 0));
     577            content.add(new JLabel(slider.getLabel()), GBC.eol());
     578            content.add(slider, GBC.eop());
     579            sliders.add(slider);
    600580        }
    601581
    602         protected void setVisible(boolean visible) {
     582        protected void setVisibleFlag(boolean visible) {
    603583            for (Layer l : model.getSelectedLayers()) {
    604584                l.setVisible(visible);
    605585            }
    606586            updateValues();
    607587        }
    608588
    609         protected void setOpacityValue(double value, boolean adjusting) {
    610             if (value <= 0 && !adjusting) {
    611                 setVisible(false);
    612             } else {
    613                 for (Layer l : model.getSelectedLayers()) {
    614                     l.setOpacity(value);
    615                 }
    616             }
    617         }
    618 
    619         protected void setGammaValue(double value) {
    620             for (ImageryLayer imageryLayer : Utils.filteredCollection(model.getSelectedLayers(), ImageryLayer.class)) {
    621                 imageryLayer.setGamma(value);
    622             }
    623         }
    624 
    625589        @Override
    626590        public void actionPerformed(ActionEvent e) {
    627591            updateValues();
    public class LayerListDialog extends ToggleDialog {  
    647611            // TODO: Indicate tristate.
    648612            visibilityCheckbox.setSelected(allVisible && !allHidden);
    649613
    650             updateOpacitySlider(layers, allHidden);
    651 
    652             updateGammaSlider(layers, allHidden);
    653         }
    654 
    655         private void updateGammaSlider(List<Layer> layers, boolean allHidden) {
    656             Collection<ImageryLayer> gammaLayers = Utils.filteredCollection(layers, ImageryLayer.class);
    657             if (gammaLayers.isEmpty() || allHidden) {
    658                 gammaSlider.setEnabled(false);
    659             } else {
    660                 gammaSlider.setEnabled(true);
    661                 double gamma = gammaLayers.iterator().next().getGamma();
    662                 gammaSlider.setValue((int) (gamma * SLIDER_STEPS / MAX_GAMMA_FACTOR));
    663             }
    664         }
    665 
    666         private void updateOpacitySlider(List<Layer> layers, boolean allHidden) {
    667             if (layers.isEmpty() || allHidden) {
    668                 opacitySlider.setEnabled(false);
    669             } else {
    670                 opacitySlider.setEnabled(true);
    671                 double opacity = 0;
    672                 for (Layer l : layers) {
    673                     opacity += l.getOpacity();
    674                 }
    675                 opacity /= layers.size();
    676                 if (opacity == 0) {
    677                     opacity = 1;
    678                     setOpacityValue(opacity, false);
    679                 }
    680                 opacitySlider.setValue((int) (opacity * SLIDER_STEPS));
     614            for (FilterSlider<?> slider : sliders) {
     615                slider.updateSlider(layers, allHidden);
    681616            }
    682617        }
    683618
    public class LayerListDialog extends ToggleDialog {  
    703638        void setCorrespondingSideButton(SideButton sideButton) {
    704639            this.sideButton = sideButton;
    705640        }
     641
     642        /**
     643         * This is a slider for a filter value.
     644         * @author Michael Zangl
     645         *
     646         * @param <T> The layer type.
     647         */
     648        private abstract class FilterSlider<T extends Layer> extends JSlider {
     649            private final double minValue;
     650            private final double maxValue;
     651            private final Class<T> layerClassFilter;
     652
     653            /**
     654             * Create a new filter slider.
     655             * @param minValue The minimum value to map to the left side.
     656             * @param maxValue The maximum value to map to the right side.
     657             * @param layerClassFilter The type of layer influenced by this filter.
     658             */
     659            FilterSlider(double minValue, double maxValue, Class<T> layerClassFilter) {
     660                super(JSlider.HORIZONTAL);
     661                this.minValue = minValue;
     662                this.maxValue = maxValue;
     663                this.layerClassFilter = layerClassFilter;
     664                setMaximum(SLIDER_STEPS);
     665                int tick = convertFromRealValue(1);
     666                setMinorTickSpacing(tick);
     667                setMajorTickSpacing(tick);
     668                setPaintTicks(true);
     669
     670                addChangeListener(new ChangeListener() {
     671                    @Override
     672                    public void stateChanged(ChangeEvent e) {
     673                        onStateChanged();
     674                    }
     675                });
     676            }
     677
     678            /**
     679             * Called whenever the state of the slider was changed.
     680             * @see #getValueIsAdjusting()
     681             * @see #getRealValue()
     682             */
     683            protected void onStateChanged() {
     684                Collection<T> layers = filterLayers(model.getSelectedLayers());
     685                for (T layer : layers) {
     686                    applyValueToLayer(layer);
     687                }
     688            }
     689
     690            protected void applyValueToLayer(T layer) {
     691            }
     692
     693            protected double getRealValue() {
     694                return convertToRealValue(getValue());
     695            }
     696
     697            protected double convertToRealValue(int value) {
     698                double s = (double) value / SLIDER_STEPS;
     699                return s * maxValue + (1-s) * minValue;
     700            }
     701
     702            protected void setRealValue(double value) {
     703                setValue(convertFromRealValue(value));
     704            }
     705
     706            protected int convertFromRealValue(double value) {
     707                int i = (int) ((value - minValue) / (maxValue - minValue) * SLIDER_STEPS + .5);
     708                if (i < getMinimum()) {
     709                    return getMinimum();
     710                } else if (i > getMaximum()) {
     711                    return getMaximum();
     712                } else {
     713                    return i;
     714                }
     715            }
     716
     717            public abstract ImageIcon getIcon();
     718
     719            public abstract String getLabel();
     720
     721            public void updateSlider(List<Layer> layers, boolean allHidden) {
     722                Collection<? extends Layer> usedLayers = filterLayers(layers);
     723                if (usedLayers.isEmpty() || allHidden) {
     724                    setEnabled(false);
     725                } else {
     726                    setEnabled(true);
     727                    updateSliderWhileEnabled(usedLayers, allHidden);
     728                }
     729            }
     730
     731            protected Collection<T> filterLayers(List<Layer> layers) {
     732                return Utils.filteredCollection(layers, layerClassFilter);
     733            }
     734
     735            protected abstract void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden);
     736        }
     737
     738        /**
     739         * This slider allows you to change the opacity of a layer.
     740         *
     741         * @author Michael Zangl
     742         * @see Layer#setOpacity(double)
     743         */
     744        class OpacitySlider extends FilterSlider<Layer> {
     745            /**
     746             * Creaate a new {@link OpacitySlider}.
     747             */
     748            OpacitySlider() {
     749                super(0, 1, Layer.class);
     750                setToolTipText(tr("Adjust opacity of the layer."));
     751
     752            }
     753
     754            @Override
     755            protected void onStateChanged() {
     756                if (getRealValue() <= 0.001 && !getValueIsAdjusting()) {
     757                    setVisibleFlag(false);
     758                } else {
     759                    super.onStateChanged();
     760                }
     761            }
     762
     763            @Override
     764            protected void applyValueToLayer(Layer layer) {
     765                layer.setOpacity(getRealValue());
     766            }
     767
     768            @Override
     769            protected void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden) {
     770                double opacity = 0;
     771                for (Layer l : usedLayers) {
     772                    opacity += l.getOpacity();
     773                }
     774                opacity /= usedLayers.size();
     775                if (opacity == 0) {
     776                    opacity = 1;
     777                    setVisibleFlag(true);
     778                }
     779                setRealValue(opacity);
     780            }
     781
     782            @Override
     783            public String getLabel() {
     784                return tr("Opacity");
     785            }
     786
     787            @Override
     788            public ImageIcon getIcon() {
     789                return ImageProvider.get("dialogs/layerlist", "transparency");
     790            }
     791
     792            @Override
     793            public String toString() {
     794                return "OpacitySlider [getRealValue()=" + getRealValue() + "]";
     795            }
     796        }
     797
     798        /**
     799         * This slider allows you to change the gamma value of a layer.
     800         *
     801         * @author Michael Zangl
     802         * @see ImageryLayer#setGamma(double)
     803         */
     804        private class GammaFilterSlider extends FilterSlider<ImageryLayer> {
     805
     806            /**
     807             * Create a new {@link GammaFilterSlider}
     808             */
     809            GammaFilterSlider() {
     810                super(0, MAX_GAMMA_FACTOR, ImageryLayer.class);
     811                setToolTipText(tr("Adjust gamma value of the layer."));
     812            }
     813
     814            @Override
     815            protected void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden) {
     816                double gamma = ((ImageryLayer) usedLayers.iterator().next()).getGamma();
     817                setRealValue(gamma);
     818            }
     819
     820            @Override
     821            protected void applyValueToLayer(ImageryLayer layer) {
     822                layer.setGamma(getRealValue());
     823            }
     824
     825            @Override
     826            public ImageIcon getIcon() {
     827               return ImageProvider.get("dialogs/layerlist", "gamma");
     828            }
     829
     830            @Override
     831            public String getLabel() {
     832                return tr("Gamma");
     833            }
     834        }
     835
     836        /**
     837         * This slider allows you to change the sharpness of a layer.
     838         *
     839         * @author Michael Zangl
     840         * @see ImageryLayer#setSharpenLevel(double)
     841         */
     842        private class SharpnessSlider extends FilterSlider<ImageryLayer> {
     843
     844            /**
     845             * Creates a new {@link SharpnessSlider}
     846             */
     847            SharpnessSlider() {
     848                super(0, MAX_SHARPNESS_FACTOR, ImageryLayer.class);
     849                setToolTipText(tr("Adjust sharpness/blur value of the layer."));
     850            }
     851
     852            @Override
     853            protected void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden) {
     854                setRealValue(((ImageryLayer) usedLayers.iterator().next()).getSharpenLevel());
     855            }
     856
     857            @Override
     858            protected void applyValueToLayer(ImageryLayer layer) {
     859                layer.setSharpenLevel(getRealValue());
     860            }
     861
     862            @Override
     863            public ImageIcon getIcon() {
     864               return ImageProvider.get("dialogs/layerlist", "sharpness");
     865            }
     866
     867            @Override
     868            public String getLabel() {
     869                return tr("Sharpness");
     870            }
     871        }
     872
     873        /**
     874         * This slider allows you to change the colorfulness of a layer.
     875         *
     876         * @author Michael Zangl
     877         * @see ImageryLayer#setColorfulness(double)
     878         */
     879        private class ColorfulnessSlider extends FilterSlider<ImageryLayer> {
     880
     881            /**
     882             * Create a new {@link ColorfulnessSlider}
     883             */
     884            ColorfulnessSlider() {
     885                super(0, MAX_COLORFUL_FACTOR, ImageryLayer.class);
     886                setToolTipText(tr("Adjust colorfulness of the layer."));
     887            }
     888
     889            @Override
     890            protected void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden) {
     891                setRealValue(((ImageryLayer) usedLayers.iterator().next()).getColorfulness());
     892            }
     893
     894            @Override
     895            protected void applyValueToLayer(ImageryLayer layer) {
     896                layer.setColorfulness(getRealValue());
     897            }
     898
     899            @Override
     900            public ImageIcon getIcon() {
     901               return ImageProvider.get("dialogs/layerlist", "colorfulness");
     902            }
     903
     904            @Override
     905            public String getLabel() {
     906                return tr("Colorfulness");
     907            }
     908        }
    706909    }
    707910
    708911    /**
  • src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
    index b52d9df..6467fe7 100644
    a b implements ImageObserver, TileLoaderListener, ZoomChangeListener {  
    258258        redraw();
    259259    }
    260260
     261    @Override
     262    public void setSharpenLevel(double sharpenLevel) {
     263        super.setSharpenLevel(sharpenLevel);
     264        redraw();
     265    }
     266
     267    @Override
     268    public void setColorfulness(double colorfulness) {
     269        super.setColorfulness(colorfulness);
     270        redraw();
     271    }
     272
    261273    /**
    262274     * Marks layer as needing redraw on offset change
    263275     */
  • src/org/openstreetmap/josm/gui/layer/ImageryLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java b/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java
    index d1eb563..5b3f7a7 100644
    a b import static org.openstreetmap.josm.tools.I18n.trc;  
    88import java.awt.Color;
    99import java.awt.Component;
    1010import java.awt.GridBagLayout;
     11import java.awt.Rectangle;
     12import java.awt.RenderingHints;
    1113import java.awt.Transparency;
    1214import java.awt.event.ActionEvent;
     15import java.awt.geom.Point2D;
     16import java.awt.geom.Rectangle2D;
    1317import java.awt.image.BufferedImage;
    1418import java.awt.image.BufferedImageOp;
     19import java.awt.image.ColorModel;
    1520import java.awt.image.ConvolveOp;
     21import java.awt.image.DataBuffer;
     22import java.awt.image.DataBufferByte;
    1623import java.awt.image.Kernel;
    1724import java.awt.image.LookupOp;
    1825import java.awt.image.ShortLookupTable;
    public abstract class ImageryLayer extends Layer {  
    6976    protected double dy;
    7077
    7178    protected GammaImageProcessor gammaImageProcessor = new GammaImageProcessor();
     79    protected SharpenImageProcessor sharpenImageProcessor = new SharpenImageProcessor();
     80    protected ColorfulImageProcessor collorfulnessImageProcessor = new ColorfulImageProcessor();
    7281
    7382    private final ImageryAdjustAction adjustAction = new ImageryAdjustAction(this);
    7483
    public abstract class ImageryLayer extends Layer {  
    8695        if (icon == null) {
    8796            icon = ImageProvider.get("imagery_small");
    8897        }
    89         addImageProcessor(createSharpener(PROP_SHARPEN_LEVEL.get()));
     98        addImageProcessor(collorfulnessImageProcessor);
    9099        addImageProcessor(gammaImageProcessor);
     100        addImageProcessor(sharpenImageProcessor);
     101        sharpenImageProcessor.setSharpenLevel(1 + PROP_SHARPEN_LEVEL.get() / 2f);
    91102    }
    92103
    93104    public double getPPD() {
    public abstract class ImageryLayer extends Layer {  
    232243        return hasBookmarks ? subMenu : adjustMenuItem;
    233244    }
    234245
     246    // Can soon be removed. Replaced by sharpen image processor.
     247    @Deprecated
    235248    public ImageProcessor createSharpener(int sharpenLevel) {
    236249        final Kernel kernel;
    237250        if (sharpenLevel == 1) {
    public abstract class ImageryLayer extends Layer {  
    300313    }
    301314
    302315    /**
     316     * Sharpens or blurs the image, depending on the sharpen value.
     317     * <p>
     318     * A positive sharpen level means that we sharpen the image.
     319     * <p>
     320     * A negative sharpen level let's us blur the image. -1 is the most useful value there.
     321     *
     322     * @author Michael Zangl
     323     */
     324    public static class SharpenImageProcessor implements ImageProcessor {
     325        private float sharpenLevel = 0;
     326        private ConvolveOp op;
     327
     328        private static float[] KERNEL_IDENTITY = new float[] {
     329            0, 0, 0,
     330            0, 1, 0,
     331            0, 0, 0
     332        };
     333
     334        private static float[] KERNEL_BLUR = new float[] {
     335            1f / 16, 2f / 16, 1f / 16,
     336            2f / 16, 4f / 16, 2f / 16,
     337            1f / 16, 2f / 16, 1f / 16
     338        };
     339
     340        private static float[] KERNEL_SHARPEN = new float[] {
     341            -.5f, -1f, -.5f,
     342             -1f,  7,  -1f,
     343            -.5f, -1f, -.5f
     344        };
     345
     346        /**
     347         * Gets the current sharpen level.
     348         * @return The level.
     349         */
     350        public float getSharpenLevel() {
     351            return sharpenLevel;
     352        }
     353
     354        /**
     355         * Sets the sharpening level.
     356         * @param sharpenLevel The level. Clamped to be positive or 0.
     357         */
     358        public void setSharpenLevel(float sharpenLevel) {
     359            if (sharpenLevel < 0) {
     360                this.sharpenLevel = 0;
     361            } else {
     362                this.sharpenLevel = sharpenLevel;
     363            }
     364
     365            if (this.sharpenLevel < 0.95) {
     366                op = generateMixed(this.sharpenLevel, KERNEL_IDENTITY, KERNEL_BLUR);
     367            } else if (this.sharpenLevel > 1.05) {
     368                op = generateMixed(this.sharpenLevel - 1, KERNEL_SHARPEN, KERNEL_IDENTITY);
     369            } else {
     370                op = null;
     371            }
     372        }
     373
     374        private ConvolveOp generateMixed(float aFactor, float[] a, float[] b) {
     375            if (a.length != 9 || b.length != 9) {
     376                throw new IllegalArgumentException("Illegal kernel array length.");
     377            }
     378            float[] values = new float[9];
     379            for (int i = 0; i < values.length; i++) {
     380                values[i] = aFactor * a[i] + (1 - aFactor) * b[i];
     381            }
     382            return new ConvolveOp(new Kernel(3, 3, values), ConvolveOp.EDGE_NO_OP, null);
     383        }
     384
     385        @Override
     386        public BufferedImage process(BufferedImage image) {
     387            if (op != null) {
     388                return op.filter(image, null);
     389            } else {
     390                return image;
     391            }
     392        }
     393
     394        @Override
     395        public String toString() {
     396            return "SharpenImageProcessor [sharpenLevel=" + sharpenLevel + "]";
     397        }
     398    }
     399
     400    /**
     401     * Adds or removes the colorfulness of the image.
     402     *
     403     * @author Michael Zangl
     404     */
     405    public static class ColorfulImageProcessor implements ImageProcessor {
     406        private ColorfulFilter op = null;
     407        private double colorfulness = 1;
     408
     409        /**
     410         * Gets the colorfulness value.
     411         * @return The value
     412         */
     413        public double getColorfulness() {
     414            return colorfulness;
     415        }
     416
     417        /**
     418         * Sets the colorfulness value. Clamps it to 0+
     419         * @param colorfulness The value
     420         */
     421        public void setColorfulness(double colorfulness) {
     422            if (colorfulness < 0) {
     423                this.colorfulness = 0;
     424            } else {
     425                this.colorfulness = colorfulness;
     426            }
     427
     428            if (this.colorfulness < .95 || this.colorfulness > 1.05) {
     429                op = new ColorfulFilter(this.colorfulness);
     430            } else {
     431                op = null;
     432            }
     433        }
     434
     435        @Override
     436        public BufferedImage process(BufferedImage image) {
     437            if (op != null) {
     438                return op.filter(image, null);
     439            } else {
     440                return image;
     441            }
     442        }
     443
     444        @Override
     445        public String toString() {
     446            return "ColorfulImageProcessor [colorfulness=" + colorfulness + "]";
     447        }
     448    }
     449
     450    private static class ColorfulFilter implements BufferedImageOp {
     451        private final double colorfulness;
     452
     453        /**
     454         * Create a new colorful filter.
     455         * @param colorfulness The colorfulness as defined in the {@link ColorfulImageProcessor} class.
     456         */
     457        ColorfulFilter(double colorfulness) {
     458            this.colorfulness = colorfulness;
     459        }
     460
     461        @Override
     462        public BufferedImage filter(BufferedImage src, BufferedImage dest) {
     463            if (src.getWidth() == 0 || src.getHeight() == 0) {
     464                return src;
     465            }
     466
     467            if (dest == null) {
     468                dest = createCompatibleDestImage(src, null);
     469            }
     470            DataBuffer srcBuffer = src.getRaster().getDataBuffer();
     471            DataBuffer destBuffer = dest.getRaster().getDataBuffer();
     472            if (!(srcBuffer instanceof DataBufferByte) || !(destBuffer instanceof DataBufferByte)) {
     473                Main.trace("Cannot apply color filter: Images do not use DataBufferByte.");
     474                return src;
     475            }
     476
     477            int type = src.getType();
     478            if (type != dest.getType()) {
     479                Main.trace("Cannot apply color filter: Src / Dest differ in type (" + type + "/" + dest.getType() + ")");
     480                return src;
     481            }
     482            int redOffset, greenOffset, blueOffset, alphaOffset = 0;
     483            switch (type) {
     484            case BufferedImage.TYPE_3BYTE_BGR:
     485                blueOffset = 0;
     486                greenOffset = 1;
     487                redOffset = 2;
     488                break;
     489            case BufferedImage.TYPE_4BYTE_ABGR:
     490            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
     491                blueOffset = 1;
     492                greenOffset = 2;
     493                redOffset = 3;
     494                break;
     495            case BufferedImage.TYPE_INT_ARGB:
     496            case BufferedImage.TYPE_INT_ARGB_PRE:
     497                redOffset = 0;
     498                greenOffset = 1;
     499                blueOffset = 2;
     500                alphaOffset = 3;
     501                break;
     502            default:
     503                Main.trace("Cannot apply color filter: Source image is of wrong type (" + type + ").");
     504                return src;
     505            }
     506            doFilter((DataBufferByte) srcBuffer, (DataBufferByte) destBuffer, redOffset, greenOffset, blueOffset,
     507                    alphaOffset, src.getAlphaRaster() != null);
     508            return dest;
     509        }
     510
     511        private void doFilter(DataBufferByte src, DataBufferByte dest, int redOffset, int greenOffset, int blueOffset,
     512                int alphaOffset, boolean hasAlpha) {
     513            byte[] srcPixels = src.getData();
     514            byte[] destPixels = dest.getData();
     515            if (srcPixels.length != destPixels.length) {
     516                Main.trace("Cannot apply color filter: Source/Dest lengths differ.");
     517                return;
     518            }
     519            int entries = hasAlpha ? 4 : 3;
     520            for (int i = 0; i < srcPixels.length; i += entries) {
     521                int r = srcPixels[i + redOffset] & 0xff;
     522                int g = srcPixels[i + greenOffset] & 0xff;
     523                int b = srcPixels[i + blueOffset] & 0xff;
     524                float luminosity = r * .21f + g * .72f + b * .07f;
     525                destPixels[i + redOffset] = mix(r, luminosity);
     526                destPixels[i + greenOffset] = mix(g, luminosity);
     527                destPixels[i + blueOffset] = mix(b, luminosity);
     528                if (hasAlpha) {
     529                    destPixels[i + alphaOffset] = srcPixels[i + alphaOffset];
     530                }
     531            }
     532        }
     533
     534        private byte mix(int color, float luminosity) {
     535            int val = (int) (colorfulness * color +  (1 - colorfulness) * luminosity);
     536            if (val < 0) {
     537                return 0;
     538            } else if (val > 0xff) {
     539                return (byte) 0xff;
     540            } else {
     541                return (byte) val;
     542            }
     543        }
     544
     545        @Override
     546        public Rectangle2D getBounds2D(BufferedImage src) {
     547            return new Rectangle(src.getWidth(), src.getHeight());
     548        }
     549
     550        @Override
     551        public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
     552            return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
     553        }
     554
     555        @Override
     556        public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
     557            return (Point2D) srcPt.clone();
     558        }
     559
     560        @Override
     561        public RenderingHints getRenderingHints() {
     562            return null;
     563        }
     564
     565    }
     566
     567    /**
    303568     * Returns the currently set gamma value.
    304569     * @return the currently set gamma value
    305570     */
    public abstract class ImageryLayer extends Layer {  
    316581    }
    317582
    318583    /**
     584     * Gets the current sharpen level.
     585     * @return The sharpen level.
     586     */
     587    public double getSharpenLevel() {
     588        return sharpenImageProcessor.getSharpenLevel();
     589    }
     590
     591    /**
     592     * Sets the sharpen level for the layer.
     593     * <code>1</code> means no change in sharpness.
     594     * Values in range 0..1 blur the image.
     595     * Values above 1 are used to sharpen the image.
     596     * @param sharpenLevel The sharpen level.
     597     */
     598    public void setSharpenLevel(double sharpenLevel) {
     599        sharpenImageProcessor.setSharpenLevel((float) sharpenLevel);
     600    }
     601
     602    /**
     603     * Gets the colorfulness of this image.
     604     * @return The colorfulness
     605     */
     606    public double getColorfulness() {
     607        return collorfulnessImageProcessor.getColorfulness();
     608    }
     609
     610    /**
     611     * Sets the colorfulness of this image.
     612     * 0 means grayscale.
     613     * 1 means normal colorfulness.
     614     * Values greater than 1 are allowed.
     615     * @param colorfulness The colorfulness.
     616     */
     617    public void setColorfulness(double colorfulness) {
     618        collorfulnessImageProcessor.setColorfulness(colorfulness);
     619    }
     620
     621    /**
    319622     * This method adds the {@link ImageProcessor} to this Layer if it is not {@code null}.
    320623     *
    321624     * @param processor that processes the image
  • test/unit/org/openstreetmap/josm/gui/dialogs/LayerListDialogTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/LayerListDialogTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/LayerListDialogTest.java
    index afede87..9157742 100644
    a b public class LayerListDialogTest {  
    4646
    4747            // now check values
    4848            action.updateValues();
    49             assertEquals(1.0, action.readOpacityValue(), 1e-15);
    50             assertEquals(1.0, action.readGammaValue(), 1e-15);
     49            assertEquals(1.0, action.opacitySlider.getRealValue(), 1e-15);
    5150
    52             action.setOpacityValue(.5, false);
    53             action.setGammaValue(1.5);
     51            action.opacitySlider.setRealValue(.5);
    5452            action.updateValues();
    5553
    56             assertEquals(0.5, action.readOpacityValue(), 1e-15);
    57             assertEquals(1.5, action.readGammaValue(), 1e-15);
     54            assertEquals(0.5, action.opacitySlider.getRealValue(), 1e-15);
    5855
    59             action.setVisible(false);
     56            action.setVisibleFlag(false);
    6057            action.updateValues();
    6158            assertFalse(layer.isVisible());
    6259
    63             action.setVisible(true);
     60            action.setVisibleFlag(true);
    6461            action.updateValues();
    6562            assertTrue(layer.isVisible());
    6663
    6764            // layer stays visible during adjust
    68             action.setOpacityValue(0, true);
     65            action.opacitySlider.setValueIsAdjusting(true);
     66            action.opacitySlider.setRealValue(0);
    6967            assertEquals(0, layer.getOpacity(), 1e-15);
    7068            layer.setOpacity(.1); // to make layer.isVisible work
    7169            assertTrue(layer.isVisible());
    7270            layer.setOpacity(0);
    7371
    74             action.setOpacityValue(0, false);
     72            action.opacitySlider.setValueIsAdjusting(false);
     73            action.opacitySlider.setRealValue(0);
    7574            assertEquals(0, layer.getOpacity(), 1e-15);
    7675            layer.setOpacity(.1); // to make layer.isVisible work
    7776            assertFalse(layer.isVisible());
    public class LayerListDialogTest {  
    7978            action.updateValues();
    8079
    8180            // Opacity reset when it was 0 and user set layer to visible.
    82             action.setVisible(true);
     81            action.setVisibleFlag(true);
    8382            action.updateValues();
    84             assertEquals(1.0, action.readOpacityValue(), 1e-15);
     83            assertEquals(1.0, action.opacitySlider.getRealValue(), 1e-15);
    8584            assertEquals(1.0, layer.getOpacity(), 1e-15);
    8685
    8786        } finally {