'How to can I edit RGB values of an image using JSlider?

editColors() code

The red, green, and blue values are in 0-255 format. I have a JSlider with a minimum of 0 and a maximum of 255. I want to use this JSlider to control the RGB values of a selected image. The current pixel RGB value is obtained from the originalImg which is an unedited version of the selected image. Then, I am applying new RGB values from JSlider to this image. The main problem is, that I do not know what math operation to use. I tried multiplying it and adding it, but nothing works. Do you have any experience with this?

public void editColors(double red, double green, double blue){

        for (int x = 0; x < originalImg.getWidth(); x++){

            for (int y = 0; y < originalImg.getHeight(); y++){

                int r = (originalImg.getRGB(x,y) >> 16) & 0xFF;
                int g = (originalImg.getRGB(x,y) >> 8) & 0xFF;
                int b = (originalImg.getRGB(x,y)) & 0xFF;

                int R = (int) (r + red); //Mainly, I do not know what operation to use here
                int G = (int) (g + green);
                int B = (int) (b + blue);

                int I = new Color(R, G, B).getRGB();

                image.setRGB(x, y, I);
            }
        }
        repaint();
    }

redSlider listener code

     redSlider.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                labelRed.setText("Red " + redSlider.getValue());
                int value = (redSlider.getValue());
                image.editColors(value, 1, 1);
            }
        });


Solution 1:[1]

Here's a crude (but tested) version using RescaleOp as mentioned in the comments. One slider for each R, G and B. On a reasonably fast machine (like my old 2016 MBP) and not too large image (< 1000 x 1000), the updates should be almost real-time.

public static void main(final String[] args) throws IOException {
    BufferedImage image = ImageIO.read(new File(args[0]));

    SwingUtilities.invokeLater(() -> {
        JLabel imageLabel = new JLabel(new ImageIcon(image));

        BoundedRangeModel red = new DefaultBoundedRangeModel(0, 1, -255, 255);
        BoundedRangeModel green = new DefaultBoundedRangeModel(0, 1, -255, 255);
        BoundedRangeModel blue = new DefaultBoundedRangeModel(0, 1, -255, 255);

        ChangeListener sliderListener = e -> new SwingWorker<BufferedImage, BufferedImage>() {
            final BufferedImageOp op = new RescaleOp(new float[] { 1, 1, 1 }, new float[] { red.getValue(), green.getValue(), blue.getValue() }, null);

            @Override protected BufferedImage doInBackground() {
                BufferedImage result = op.filter(image, null);
                publish(result);

                return result;
            }

            @Override protected void process(List<BufferedImage> chunks) {
                imageLabel.setIcon(new ImageIcon(chunks.get(0)));
            }
        }.execute();

        red.addChangeListener(sliderListener);
        green.addChangeListener(sliderListener);
        blue.addChangeListener(sliderListener);

        JSlider redSlider = new JSlider(red);
        JSlider greenSlider = new JSlider(green);
        JSlider blueSlider = new JSlider(blue);

        JPanel sliders = new JPanel();
        sliders.add(redSlider);
        sliders.add(greenSlider);
        sliders.add(blueSlider);

        JPanel main = new JPanel(new BorderLayout());
        main.add(imageLabel);
        main.add(sliders, BorderLayout.SOUTH);

        JFrame test = new JFrame("Test");
        test.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        test.getContentPane().add(main);
        test.pack();
        test.setLocationRelativeTo(null);

        test.setVisible(true);
    });
}

This code can/should probably be cleaned up and optimized, but it works fine as a proof of concept.

Screenshot of test application

See the RescaleOp API docs for more information.

Note: If your image is indexed (using palette) you need to convert to an RGB type first, like TYPE_INT_RGB or TYPE_3BYTE_RGB, for RescaleOp to work.

PS: Just for fun, I created my own BufferedImageOp which does the same as the RescaleOp example above, with a slightly modified version of your code and it also works fine, although a little slower:

final BufferedImageOp op = new BufferedImageOp() {
    @Override public BufferedImage filter(BufferedImage src, BufferedImage dest) {
        if (dest == null) {
            dest = createCompatibleDestImage(src, null);
        }

        // Get all the slider values at once, don't reset the others to 1!
        int rV = red.getValue();
        int gV = green.getValue();
        int bV = blue.getValue();

        int height = src.getHeight();
        int width = src.getWidth();
        int[] row = null;

        for (int y = 0; y < height; y++) {
            // I copy a full row of pixels at a time, for slightly better performance 
            row = src.getRGB(0, y, width, 1, row, 0, width);

            for (int x = 0; x < width; x++) {
                int A = row[x] & 0xFF000000; // Just copy the alpha as-is

                int r = (row[x] >> 16) & 0xFF;
                int g = (row[x] >> 8) & 0xFF;
                int b = (row[x]) & 0xFF;

                // As you can see, a simple addition will increase/decrease 
                // the channel values, just make sure you keep them in the 
                // [0...255] range. 
                int R = max(min(r + rV, 255), 0);
                int G = max(min(g + gV, 255), 0);
                int B = max(min(b + bV, 255), 0);

                row[x] = A | R << 16 | G << 8 | B;
            }

            // And copy the entire row back
            dest.setRGB(0, y, width, 1, row, 0, width);
        }

        return dest;
    }

    @Override public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
        ColorModel cm = destCM != null ? destCM : src.getColorModel();

        return new BufferedImage(cm, cm.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), cm.isAlphaPremultiplied(), null);
    }

    @Override public Rectangle2D getBounds2D(BufferedImage src) {
        return new Rectangle(src.getWidth(), src.getHeight());
    }

    @Override public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
        if (dstPt == null) {
            dstPt = new Point();
        }

        dstPt.setLocation(srcPt);

        return dstPt;
    }

    @Override public RenderingHints getRenderingHints() {
        return null;
    }
};

(min()/max() is just static imports of Math.min()/max()).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1