Displaying a histogram of image data
Asked Answered
R

2

9

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.

Ragg answered 14/2, 2015 at 19:25 Comment(0)
R
15

The example below uses several techniques to create an RGB histogram of an arbitrary image:

  • The Raster method getSamples() extracts the values of each color band from the BufferedImage.

  • The HistogramDataset method addSeries() adds each band's counts to the dataset.

  • A StandardXYBarPainter replaces the ChartFactory 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 using ChartMouseListener is shown here.

color histogram

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();
        });
    }
}
Ragg answered 14/2, 2015 at 19:25 Comment(1)
For amusement, here's a histogram of the (infamous) blue black white gold dress.Ragg
R
3

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 the getRGB() method to extract the value of each pixel; the corresponding counts are used to construct the dataset.

Chart2DHistogram

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();
            }
        });
    }
}
Ragg answered 14/2, 2015 at 19:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.