I sometimes need to display a representation of image data in the form of a histogram. I'm especially interested in ways to access the image data. I'm familiar with JFreeChart
, which includes histogram support, but I'd consider other approaches.
The example below uses several techniques to create an RGB histogram of an arbitrary image:
The
Raster
methodgetSamples()
extracts the values of each color band from theBufferedImage
.The
HistogramDataset
methodaddSeries()
adds each band's counts to thedataset
.A
StandardXYBarPainter
replaces theChartFactory
default, as shown here.A custom
DefaultDrawingSupplier
supplies the color required for each series; it contains translucent colors.A variation of
VisibleAction
, discussed here, is used to control the visibility of each band; a complementary approach usingChartMouseListener
is shown here.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.DefaultDrawingSupplier;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.statistics.HistogramDataset;
/**
* @see https://mcmap.net/q/1021794/-change-jfreechart-histogram-colors-dynamically/230513
* @see https://mcmap.net/q/429255/-combineddomainxyplot-not-rescaling-domain-axis/230513
* @see https://mcmap.net/q/1021780/-displaying-a-histogram-of-image-data
*/
public class Histogram {
private static final int BINS = 256;
private final BufferedImage image = getImage();
private HistogramDataset dataset;
private XYBarRenderer renderer;
private BufferedImage getImage() {
try {
return ImageIO.read(new URL(
"https://i.imgur.com/kxXhIH1.jpg"));
} catch (IOException e) {
e.printStackTrace(System.err);
}
return null;
}
private ChartPanel createChartPanel() {
// dataset
dataset = new HistogramDataset();
Raster raster = image.getRaster();
final int w = image.getWidth();
final int h = image.getHeight();
double[] r = new double[w * h];
r = raster.getSamples(0, 0, w, h, 0, r);
dataset.addSeries("Red", r, BINS);
r = raster.getSamples(0, 0, w, h, 1, r);
dataset.addSeries("Green", r, BINS);
r = raster.getSamples(0, 0, w, h, 2, r);
dataset.addSeries("Blue", r, BINS);
// chart
JFreeChart chart = ChartFactory.createHistogram("Histogram", "Value",
"Count", dataset, PlotOrientation.VERTICAL, true, true, false);
XYPlot plot = (XYPlot) chart.getPlot();
renderer = (XYBarRenderer) plot.getRenderer();
renderer.setBarPainter(new StandardXYBarPainter());
// translucent red, green & blue
Paint[] paintArray = {
new Color(0x80ff0000, true),
new Color(0x8000ff00, true),
new Color(0x800000ff, true)
};
plot.setDrawingSupplier(new DefaultDrawingSupplier(
paintArray,
DefaultDrawingSupplier.DEFAULT_FILL_PAINT_SEQUENCE,
DefaultDrawingSupplier.DEFAULT_OUTLINE_PAINT_SEQUENCE,
DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE,
DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE));
ChartPanel panel = new ChartPanel(chart);
panel.setMouseWheelEnabled(true);
return panel;
}
private JPanel createControlPanel() {
JPanel panel = new JPanel();
panel.add(new JCheckBox(new VisibleAction(0)));
panel.add(new JCheckBox(new VisibleAction(1)));
panel.add(new JCheckBox(new VisibleAction(2)));
return panel;
}
private class VisibleAction extends AbstractAction {
private final int i;
public VisibleAction(int i) {
this.i = i;
this.putValue(NAME, (String) dataset.getSeriesKey(i));
this.putValue(SELECTED_KEY, true);
renderer.setSeriesVisible(i, true);
}
@Override
public void actionPerformed(ActionEvent e) {
renderer.setSeriesVisible(i, !renderer.getSeriesVisible(i));
}
}
private void display() {
JFrame f = new JFrame("Histogram");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(createChartPanel());
f.add(createControlPanel(), BorderLayout.SOUTH);
f.add(new JLabel(new ImageIcon(image)), BorderLayout.WEST);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
new Histogram().display();
});
}
}
Using the Chart2D
library, the example below illustrates some alternative approaches.
A
ColorConvertOp
is used to convert the sample image to grayscale, as shown here and here.Nested loops iterate over the pixels of the
BufferedImage
, invoking thegetRGB()
method to extract the value of each pixel; the corresponding counts are used to construct thedataset
.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import net.sourceforge.chart2d.Chart2DProperties;
import net.sourceforge.chart2d.Dataset;
import net.sourceforge.chart2d.GraphChart2DProperties;
import net.sourceforge.chart2d.GraphProperties;
import net.sourceforge.chart2d.LBChart2D;
import net.sourceforge.chart2d.LegendProperties;
import net.sourceforge.chart2d.MultiColorsProperties;
import net.sourceforge.chart2d.Object2DProperties;
/** @see https://mcmap.net/q/1021797/-histogram-using-chart2d/230513 */
public class Histogram extends JPanel {
private BufferedImage image = getImage("OptionPane.warningIcon");
private BufferedImage gray = getGray(image);
public Histogram() {
JPanel panel = new JPanel(new GridLayout(0, 1));
panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
panel.add(new JLabel(new ImageIcon(image)));
panel.add(new JLabel(new ImageIcon(gray)));
this.setLayout(new BorderLayout());
this.add(panel, BorderLayout.WEST);
this.add(createChart(gray, 20), BorderLayout.CENTER);
}
private BufferedImage getImage(String name) {
Icon icon = UIManager.getIcon(name);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
this.setPreferredSize(new Dimension(w, h));
BufferedImage i = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) i.getGraphics();
g2d.setPaint(new GradientPaint(
0, 0, Color.blue, w, h, Color.green, true));
g2d.fillRect(0, 0, w, h);
icon.paintIcon(null, g2d, 0, 0);
g2d.dispose();
return i;
}
private BufferedImage getGray(BufferedImage image) {
BufferedImage g = new BufferedImage(
image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
ColorConvertOp op = new ColorConvertOp(
image.getColorModel().getColorSpace(),
g.getColorModel().getColorSpace(), null);
op.filter(image, g);
return g;
}
private LBChart2D createChart(BufferedImage gray, int buckets) {
// Chart2D configuration
Object2DProperties object2DProps = new Object2DProperties();
object2DProps.setObjectTitleText("Gray Histogram");
Chart2DProperties chart2DProps = new Chart2DProperties();
chart2DProps.setChartDataLabelsPrecision(-1);
LegendProperties legendProps = new LegendProperties();
String[] legendLabels = {"Gray"};
legendProps.setLegendLabelsTexts(legendLabels);
GraphChart2DProperties graphChart2DProps = new GraphChart2DProperties();
graphChart2DProps.setLabelsAxisTitleText("Gray");
graphChart2DProps.setNumbersAxisTitleText("Count");
// Dataset
String[] labelsAxisLabels = new String[buckets];
for (int i = 0; i < labelsAxisLabels.length; i++) {
labelsAxisLabels[i] = String.valueOf(i * 256 / buckets);
}
graphChart2DProps.setLabelsAxisLabelsTexts(labelsAxisLabels);
int[] counts = new int[buckets];
for (int r = 0; r < gray.getHeight(); r++) {
for (int c = 0; c < gray.getWidth(); c++) {
int v = (gray.getRGB(c, r) & 0xff) * buckets / 256;
counts[v]++;
}
}
Dataset dataset = new Dataset(1, counts.length, 1);
for (int i = 0; i < counts.length; i++) {
dataset.set(0, i, 0, counts[i]);
}
GraphProperties graphProps = new GraphProperties();
MultiColorsProperties multiColorsProps = new MultiColorsProperties();
LBChart2D chart2D = new LBChart2D();
chart2D.setObject2DProperties(object2DProps);
chart2D.setChart2DProperties(chart2DProps);
chart2D.setLegendProperties(legendProps);
chart2D.setGraphChart2DProperties(graphChart2DProps);
chart2D.addGraphProperties(graphProps);
chart2D.addDataset(dataset);
chart2D.addMultiColorsProperties(multiColorsProps);
//Optional validation: Prints debug messages if invalid only.
if (!chart2D.validate(false)) {
chart2D.validate(true);
}
return chart2D;
}
private void display() {
JFrame f = new JFrame("Histogram");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setSize(640, 480);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new Histogram().display();
}
});
}
}
© 2022 - 2024 — McMap. All rights reserved.