package org.esa.snap.rcp.colormanip;
import org.esa.snap.core.datamodel.*;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.ui.product.ProductSceneView;
import org.netbeans.api.annotations.common.NonNull;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
/**
* Created by jcoravu on 9/27/2016.
*/
class BrightnessContrastPanel extends JPanel {
private final ColorManipulationForm parentForm;
private SliderPanel brightnessPanel;
private SliderPanel contrastPanel;
private SliderPanel saturationPanel;
private PropertyChangeListener imageInfoChangeListener;
private Map<ProductSceneView, BrightnessContrastData> visibleProductScenes;
BrightnessContrastPanel(ColorManipulationForm parentForm) {
super(new BorderLayout());
this.parentForm = parentForm;
ChangeListener sliderChangeListener = event -> applySliderValues();
this.brightnessPanel = new SliderPanel("Brightness", sliderChangeListener);
this.contrastPanel = new SliderPanel("Contrast", sliderChangeListener);
this.saturationPanel = new SliderPanel("Saturation", sliderChangeListener);
int maximumPreferredWidth = Math.max(this.brightnessPanel.getTitlePreferredWidth(), this.contrastPanel.getTitlePreferredWidth());
maximumPreferredWidth = Math.max(maximumPreferredWidth, this.saturationPanel.getTitlePreferredWidth());
this.saturationPanel.setTitlePreferredWidth(maximumPreferredWidth);
this.contrastPanel.setTitlePreferredWidth(maximumPreferredWidth);
this.saturationPanel.setTitlePreferredWidth(maximumPreferredWidth);
this.visibleProductScenes = new HashMap<>();
this.imageInfoChangeListener = event -> {
ImageInfo modifiedImageInfo = (ImageInfo) event.getNewValue();
ProductSceneView selectedSceneView = (ProductSceneView)event.getSource();
sceneImageInfoChangedOutside(selectedSceneView, modifiedImageInfo);
};
JPanel colorsPanel = new JPanel(new GridLayout(3, 1, 0, 15));
colorsPanel.setBorder(new EmptyBorder(5, 5, 0, 0));
colorsPanel.add(this.brightnessPanel);
colorsPanel.add(this.contrastPanel);
colorsPanel.add(this.saturationPanel);
JButton resetButton = new JButton("Reset");
resetButton.setFocusable(false);
resetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
resetSliderValues();
}
});
JPanel componentsPanel = new JPanel();
componentsPanel.setLayout(new BoxLayout(componentsPanel, BoxLayout.Y_AXIS));
componentsPanel.add(colorsPanel);
componentsPanel.add(resetButton);
JScrollPane scrollPane = new JScrollPane(componentsPanel);
scrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
add(scrollPane, BorderLayout.NORTH);
}
public void productSceneViewSelected(@NonNull ProductSceneView selectedSceneView) {
selectedSceneView.addPropertyChangeListener(ProductSceneView.PROPERTY_NAME_IMAGE_INFO, this.imageInfoChangeListener);
BrightnessContrastData brightnessContrastData = this.visibleProductScenes.get(selectedSceneView);
if (brightnessContrastData == null) {
RasterDataNode[] rasterDataNodes = selectedSceneView.getSceneImage().getRasters();
ImageInfo initialImageInfo = selectedSceneView.getImageInfo().clone();
brightnessContrastData = new BrightnessContrastData(initialImageInfo);
for (int i=0; i<rasterDataNodes.length; i++) {
ImageInfo nodeImageInfo = rasterDataNodes[i].getImageInfo().clone();
brightnessContrastData.putImageInfo(rasterDataNodes[i], nodeImageInfo);
}
this.visibleProductScenes.put(selectedSceneView, brightnessContrastData);
}
RGBChannelDef initialRGBChannelDef = brightnessContrastData.getInitialImageInfo().getRgbChannelDef();
boolean enableSaturationPanel = (initialRGBChannelDef != null);
this.saturationPanel.setEnabled(enableSaturationPanel);
refreshSliderValues(brightnessContrastData);
}
public void productSceneViewDeselected(@NonNull ProductSceneView deselectedSceneView) {
deselectedSceneView.removePropertyChangeListener(ProductSceneView.PROPERTY_NAME_IMAGE_INFO, this.imageInfoChangeListener);
}
private void resetSliderValues() {
ProductSceneView selectedSceneView = getSelectedProductSceneView();
BrightnessContrastData brightnessContrastData = this.visibleProductScenes.get(selectedSceneView);
brightnessContrastData.setSliderValues(0, 0, 0);
refreshSliderValues(brightnessContrastData);
applySliderValues();
}
private void refreshSliderValues(BrightnessContrastData brightnessContrastData) {
this.brightnessPanel.setSliderValue(brightnessContrastData.getBrightnessSliderValue());
this.contrastPanel.setSliderValue(brightnessContrastData.getContrastSliderValue());
this.saturationPanel.setSliderValue(brightnessContrastData.getSaturationSliderValue());
}
private void sceneImageInfoChangedOutside(ProductSceneView selectedSceneView, ImageInfo modifiedImageInfo) {
BrightnessContrastData brightnessContrastData = this.visibleProductScenes.get(selectedSceneView);
ImageInfo initialImageInfo = modifiedImageInfo.clone();
brightnessContrastData.setInitialImageInfo(initialImageInfo);
brightnessContrastData.setSliderValues(0, 0, 0);
refreshSliderValues(brightnessContrastData);
}
private ProductSceneView getSelectedProductSceneView() {
return SnapApp.getDefault().getSelectedProductSceneView();
}
private void applySliderValues() {
ProductSceneView selectedSceneView = getSelectedProductSceneView();
int brightnessValue = this.brightnessPanel.getSliderValue();
int contrastValue = this.contrastPanel.getSliderValue();
int saturationValue = this.saturationPanel.getSliderValue();
BrightnessContrastData brightnessContrastData = this.visibleProductScenes.get(selectedSceneView);
brightnessContrastData.setSliderValues(brightnessValue, contrastValue, saturationValue);
// recompute the slider values before applying them to the colors
brightnessValue = computeSliderValueToApply(brightnessValue, this.brightnessPanel.getSliderMaximumValue(), 255);
contrastValue = computeSliderValueToApply(contrastValue, this.contrastPanel.getSliderMaximumValue(), 255);
saturationValue = computeSliderValueToApply(saturationValue, this.saturationPanel.getSliderMaximumValue(), 100);
ImageInfo sceneImageInfo = this.parentForm.getFormModel().getModifiedImageInfo();
RasterDataNode[] rasterDataNodes = selectedSceneView.getSceneImage().getRasters();
RGBChannelDef initialRGBChannelDef = brightnessContrastData.getInitialImageInfo().getRgbChannelDef();
if (initialRGBChannelDef == null) {
ColorPaletteDef colorPaletteDef = sceneImageInfo.getColorPaletteDef();
for (int k=0; k<rasterDataNodes.length; k++) {
RasterDataNode currentDataNode = rasterDataNodes[k];
ColorPaletteDef initialColorPaletteDef = brightnessContrastData.getInitialImageInfo(currentDataNode).getColorPaletteDef();
int pointCount = initialColorPaletteDef.getNumPoints();
for (int i=0; i<pointCount; i++) {
ColorPaletteDef.Point initialPoint = initialColorPaletteDef.getPointAt(i);
Color newColor = computeColor(initialPoint.getColor(), brightnessValue, contrastValue, saturationValue);
ColorPaletteDef.Point currentPoint = colorPaletteDef.getPointAt(i);
currentPoint.setColor(newColor);
}
}
} else {
RGBChannelDef rgbChannelDef = sceneImageInfo.getRgbChannelDef();
for (int i=0; i<rasterDataNodes.length; i++) {
RasterDataNode currentDataNode = rasterDataNodes[i];
if (currentDataNode instanceof Band) {
ColorPaletteDef initialColorPaletteDef = brightnessContrastData.getInitialImageInfo(currentDataNode).getColorPaletteDef();
Color initialFirstColor = initialColorPaletteDef.getFirstPoint().getColor();
Color newInitialFirstColor = computeColor(initialFirstColor, brightnessValue, contrastValue, saturationValue);
float firstPercent = computePercent(initialFirstColor, newInitialFirstColor);
double min = initialRGBChannelDef.getMinDisplaySample(i);
min = min + (min * firstPercent);
rgbChannelDef.setMinDisplaySample(i, min);
Color initialLastColor = initialColorPaletteDef.getLastPoint().getColor();
Color newInitialLastColor = computeColor(initialLastColor, brightnessValue, contrastValue, saturationValue);
float lastPercent = computePercent(initialLastColor, newInitialLastColor);
double max = initialRGBChannelDef.getMaxDisplaySample(i);
max = max + (max * lastPercent);
rgbChannelDef.setMaxDisplaySample(i, max);
}
}
}
selectedSceneView.removePropertyChangeListener(ProductSceneView.PROPERTY_NAME_IMAGE_INFO, this.imageInfoChangeListener);
try {
this.parentForm.applyChanges();
for (int i=0; i<rasterDataNodes.length; i++) {
RasterDataNode currentDataNode = rasterDataNodes[i];
currentDataNode.getProduct().setModified(true);
}
} finally {
selectedSceneView.addPropertyChangeListener(ProductSceneView.PROPERTY_NAME_IMAGE_INFO, this.imageInfoChangeListener);
}
}
private static Color computeColor(Color color, int brightnessValue, int contrastValue, int saturationValue) {
int newRgb = computePixelBrightness(color.getRGB(), brightnessValue);
newRgb = computePixelContrast(newRgb, contrastValue);
newRgb = computePixelSaturation(newRgb, saturationValue);
return new Color(newRgb);
}
private static int computeSliderValueToApply(int visibleSliderValue, int maximumVisibleSliderValue, int maximumAllowedValue) {
float visiblePercent = (float)visibleSliderValue / (float) maximumVisibleSliderValue;
float percent = Math.round(visiblePercent * maximumAllowedValue);
return (int)percent;
}
private static float computePercent(Color initialColor, Color currentColor) {
float initialRedPercent = (float)initialColor.getRed() / 255.0f;
float initialGreenPercent = (float)initialColor.getGreen() / 255.0f;
float initialBluePercent = (float)initialColor.getBlue() / 255.0f;
float currentRedPercent = (float)currentColor.getRed() / 255.0f;
float currentGreenPercent = (float)currentColor.getGreen() / 255.0f;
float currentBluePercent = (float)currentColor.getBlue() / 255.0f;
float redPercent = initialRedPercent - currentRedPercent;
float greenPercent = initialGreenPercent - currentGreenPercent;
float bluePercent = initialBluePercent - currentBluePercent;
return (redPercent + greenPercent + bluePercent) / 3.0f;
}
private static int checkRGBValue(int v) {
if (v > 255) {
return 255;
}
if (v < 0) {
return 0;
}
return v;
}
private static int computePixelBrightness(int pixel, int sliderValue) {
int red = ColorUtils.red(pixel) + sliderValue;
int green = ColorUtils.green(pixel) + sliderValue;
int blue = ColorUtils.blue(pixel) + sliderValue;
return ColorUtils.rgba(checkRGBValue(red), checkRGBValue(green), checkRGBValue(blue));
}
private static int computePixelSaturation(int pixel, int sliderValue) {
int red = ColorUtils.red(pixel);
int green = ColorUtils.green(pixel);
int blue = ColorUtils.blue(pixel);
float[] hsb = new float[3];
Color.RGBtoHSB(red, green, blue, hsb);
hsb[1] += (sliderValue * 0.01f);
if (hsb[1] > 1.0f) {
hsb[1] = 1.0f;
} else if (hsb[1] < 0.0f) {
hsb[1] = 0.0f;
}
return Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]);
}
private static int computePixelContrast(int pixel, int sliderValue) {
float factor = (259.0f * (sliderValue + 255.0f)) / (255.0f * (259.0f - sliderValue));
int red = ColorUtils.red(pixel);
int green = ColorUtils.green(pixel);
int blue = ColorUtils.blue(pixel);
int newRed = (int)(factor * (red - 128) + 128);
int newGreen = (int)(factor * (green - 128) + 128);
int newBlue = (int)(factor * (blue - 128) + 128);
return ColorUtils.rgba(checkRGBValue(newRed), checkRGBValue(newGreen), checkRGBValue(newBlue));
}
}