I'm writing a method that will attempt to find a color in a bufferedImage. At the moment, the method works by taking a screencap, and then scanning the image for a specific color. Now I'd like to add some RGB tolerence, so if the user is trying to find color (1, 3, 5) with tolerance 1, any color +-1 R, B or G will return true.
I could solve this by first generating a arrayList of RGB values that work, and then for each pixel I could go through the array and check with each value. The problem is that would probably get VERY slow for high tolerances on large images.
Is there a more efficient or possibly a built in way I can do this? Here is my method as it stands right now. Thank you!
public static Point findColor(Box searchArea, int color){
System.out.println("Test");
BufferedImage image = generateScreenCap(searchArea);
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
if((image.getRGB(i, j)*-1)==color){
return new Point(i + searchArea.x1, j + searchArea.y1);
}
}
}
return new Point(-1, -1);
}
Edit: I'm using the int RGB values for all comparisons, so instead of Color[1, 1, 1], I use Color.getRGB() which returns a negative int which I convert to positive for end user simplicity.
You need to compare RGB values and not the "whole" color if you want to have a custom tolerance. Here is the code, it is not tested, but you get the idea :
public static Point findColor(Box searchArea, int r, int g, int b, int tolerance) {
// Pre-calc RGB "tolerance" values out of the loop (min is 0 and max is 255)
int minR = Math.max(r - tolerance, 0);
int minG = Math.max(g - tolerance, 0);
int minB = Math.max(b - tolerance, 0);
int maxR = Math.min(r + tolerance, 255);
int maxG = Math.min(g + tolerance, 255);
int maxB = Math.min(b + tolerance, 255);
BufferedImage image = generateScreenCap(searchArea);
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
// get single RGB pixel
int color = image.getRGB(i, j);
// get individual RGB values of that pixel
// (could use Java's Color class but this is probably a little faster)
int red = (color >> 16) & 0x000000FF;
int green = (color >> 8) & 0x000000FF;
int blue = (color) & 0x000000FF;
if ( (red >= minR && red <= maxR) &&
(green >= minG && green <= maxG) &&
(blue >= minB && blue <= maxB) )
return new Point(i + searchArea.x1, j + searchArea.y1);
}
}
return new Point(-1, -1);
}
Related
I have a homework task where I have to write a class responsible for contour detection. It is essentially an image processing operation, using the definition of euclidean distance between 2 points in the 3-dimensional space. Formula given to us to use is:
Math.sqrt(Math.pow(pix1.red - pix2.red,2) + Math.pow(pix1.green- pix2.green,2) + Math.pow(pix1.blue- pix2.blue,2));
We need to consider each entry of the two dimensional array storing the colors of the pixels of an image, and if some pixel, pix, the color distance between p and any of its neighbors is more than 70, change the color of the pixel to black, else change it to white.
We are given a seperate class as well responsible for choosing an image, and selecting an output, for which method operationContouring is applied to. Java syntax and convention is very new to me having started with python. Conceptually, I'm struggling to understand what the difference between pix1 and pix2 is, and how to define them. This is my code so far.
Given:
import java.awt.Color;
/* Interface for ensuring all image operations invoked in same manner */
public interface operationImage {
public Color[][] operationDo(Color[][] imageArray);
}
My code:
import java.awt.Color;
public class operationContouring implements operationImage {
public Color[][] operationDo(Color[][] imageArray) {
int numberOfRows = imageArray.length;
int numberOfColumns = imageArray[0].length;
Color[][] results = new Color[numberOfRows][numberOfColumns];
for (int i = 0; i < numberOfRows; i++)
for (int j = 0; j < numberOfColumns; j++) {
int red = imageArray[i][j].getRed();
int green = imageArray[i][j].getGreen();
int blue = imageArray[i][j].getBlue();
double DistanceColor = Math.sqrt(Math.pow(pix1.red - pix2.red,2) + Math.pow(pix1.green- pix2.green,2) + Math.pow(pix1.blue- pix2.blue,2));
int LIMIT = 70;
if (DistanceColor> LIMIT ) {
results[i][j] = new Color((red=0), (green=0), (blue=0));
}
else {
results[i][j] = new Color((red=255), (green=255), (blue=255));
}
}
return results;
}
}
This is a solution I wrote that uses BufferedImages. I tested it and it should work. Try changing it such that it uses your data format (Color[][]) and it should work for you too. Note that "pix1" is nothing more than a description of the color of some pixel, and "pix2" is the description of the color of the pixel you are comparing it to (determining whether the color distance > 70).
public static boolean tooDifferent(Color c1, Color c2) {
return Math.sqrt(Math.pow(c1.getRed() - c2.getRed(),2) + Math.pow(c1.getGreen()- c2.getGreen(),2) + Math.pow(c1.getBlue()- c2.getBlue(),2)) > 70;
}
public static Color getColor(int x, int y, BufferedImage img) {
return new Color(img.getRGB(x, y));
}
public static BufferedImage operationDo(BufferedImage img) {
int numberOfRows = img.getHeight();
int numberOfColumns = img.getWidth();
BufferedImage results = new BufferedImage(numberOfColumns, numberOfRows, BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < numberOfRows; y++) {
for (int x = 0; x < numberOfColumns; x++) {
Color color = new Color(img.getRGB(x, y));
boolean aboveExists = y > 0;
boolean belowExists = y < numberOfRows - 1;
boolean leftExists = x > 0;
boolean rightExists = x < numberOfColumns - 1;
if ((aboveExists && tooDifferent(color, getColor(x, y - 1, img))) ||
(belowExists && tooDifferent(color, getColor(x, y + 1, img))) ||
(leftExists && tooDifferent(color, getColor(x - 1, y, img))) ||
(rightExists && tooDifferent(color, getColor(x + 1, y, img)))) {
results.setRGB(x, y, Color.black.getRGB());
} else {
results.setRGB(x, y, Color.white.getRGB());
}
}
}
return results;
}
I got given some code for calculating R, G and B values from an rgb value. They look like this:
public static int getR(int rgb) {
return (rgb >> 16) & 0xff;
}
public static int getG(int rgb) {
return (rgb >> 8) & 0xff;
}
public static int getB(int rgb) {
return rgb & 0xff;
}
now I have to do the following excercise. I am changing the correct part of the image, but everything is colored black.
/**
* Converts a picture by dividing it in three equal parts along the X axis.
* In the first (left) part, only the red component is drawn. In the second
* (middle) part, only the green component is drawn. In the third (right) part,
* only the blue component is drawn.
*
* #param pixels The input pixels.
* #return The output pixels.
*/
public static int[][] andyWarhol(int[][] pixels) {
int i, j;
//Convert red part:
for (i = 0; i < pixels.length / 3; i++) {
for (j = 0; j < pixels[0].length; j++) {
pixels[i][j] = Colors.getR(pixels[i][j]);
}
}
//Convert yellow part:
for (i = pixels.length / 3; i < pixels.length * 2 / 3; i++) {
for (j = 0; j < pixels[0].length; j++) {
pixels[i][j] = Colors.getG(pixels[i][j]);
}
}
//Convert blue part:
for (i = pixels.length * 2 / 3; i < pixels.length; i++) {
for (j = 0; j < pixels[0].length; j++) {
pixels[i][j] = Colors.getB(pixels[i][j]);
}
}
return pixels;
}
I suspect your alpha is 0 (transparent).
I don't know how it's used, but colors in java are often 'argb'.
Try with (color | 0xff_00_00_00)
Solved.
Okay yea as I suspected I had to turn every value for R, B and G into an rbg value again. Makes sense
I want to layer two images together. A background and foreground. The foreground is stitched together as a grid of smaller images (3x3). I have been able to make all white pixels transparent as a workaround, however the inside of the shapes are white and I only want pixels outside the shapes transparent.
Say for example the grid of images contained a circle or square in each grid location. Is there a way I can iterate over each pixel and create two arrays of pixel locations - those outside the images making them transparent, and those inside the images where I can set the colour?
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
// Stitches a grid of images together, scales a background image to fit and layers them.
public class Layer {
public static void layerImages() {
// Grid layout of images to stitch.
int rows = 3;
int cols = 3;
int chunks = rows * cols;
int chunckWidth, chunkHeight;
// Image files to stitch
File[] imgFiles = new File[chunks];
for(int i = 0; i < chunks; i++) {
imgFiles[i] = new File("ocarina_sprite" + (i + 1) + ".png");
}
// Read images into array.
try {
BufferedImage[] buffImages = new BufferedImage[chunks];
for (int i = 0; i < chunks; i++) {
buffImages[i] = ImageIO.read(imgFiles[i]);
}
chunckWidth = buffImages[0].getWidth();
chunkHeight = buffImages[0].getHeight();
BufferedImage finalImage = new BufferedImage(chunckWidth * cols, chunkHeight*rows, BufferedImage.TYPE_INT_ARGB);
// Calculate background width and height to cover stitched image.
int bwidth = 0;
int bheight = 0;
for(int i = 0; i < rows; i++) {
bwidth += buffImages[i].getWidth();
}
for(int i = 0; i < cols; i++) {
bheight += buffImages[i].getHeight();
}
// Background image
File dory = new File("dory.png");
BufferedImage original = ImageIO.read(dory);
// Scale background image.
BufferedImage background = scale(original, bwidth, bheight);
// Prepare final image by drawing background first.
Graphics2D g = finalImage.createGraphics();
g.drawImage(background, 0, 0, null);
// Prepare foreground image.
BufferedImage foreground = new BufferedImage(chunckWidth * cols, chunkHeight*rows, BufferedImage.TYPE_INT_ARGB);
// Stitch foreground images together
int num = 0;
for(int i = 0; i < rows; i++) {
for(int j = 0; j < rows; j++) {
foreground.createGraphics().drawImage(buffImages[num],chunckWidth * j, chunkHeight * i, null);
num++;
}
}
// Set white pixels to transparent.
for (int y = 0; y < foreground.getHeight(); ++y) {
for (int x = 0; x < foreground.getWidth(); ++x) {
int argb = foreground.getRGB(x, y);
if ((argb & 0xFFFFFF) > 0xFFFFEE) {
foreground.setRGB(x, y, 0x00FFFFFF);
}
}
}
// Draw foreground image to final image.
Graphics2D g3 = finalImage.createGraphics();
g3.drawImage(foreground, 0, 0, null);
// Output final image
ImageIO.write(finalImage, "png", new File("finalImage.png"));
}
catch (Exception e) {
System.out.println(e);
}
}
// Scale image
public static BufferedImage scale(BufferedImage imageToScale, int dWidth, int dHeight) {
BufferedImage scaledImage = null;
if (imageToScale != null) {
scaledImage = new BufferedImage(dWidth, dHeight, imageToScale.getType());
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
graphics2D.dispose();
}
return scaledImage;
}
}
The floodfill solution mentioned in the comment was what I needed to solve the problem, however the recursion over a million+ pixels didn't work out so I implemented the forest fire algorithm which is floodfill using queues instead of recursion.
public static void forestFire(int width, int height, int x, int y) {
// Check if already set
int argb = foreground.getRGB(x, y);
if (((argb >> 24) & 0xFF) == 0) {
return;
}
coords.add(new Point(x, y));
// Set transparent pixel
foreground.setRGB(x, y, 0x00FFFFFF);
Point currentCoord = new Point();
while(!coords.isEmpty()) {
currentCoord.setLocation(coords.poll());
// Get current coordinates
x = (int)currentCoord.getX();
y = (int)currentCoord.getY();
// North
if(y != 0) {
int north = foreground.getRGB(x, y - 1);
// Check if transparent (already set) and check target colour (white)
if (((north >> 24) & 0xFF) > 0 && (north & 0xFFFFFF) > 0x111100) {
// Set transparent pixel
foreground.setRGB(x, y - 1, 0x00FFFFFF);
coords.add(new Point(x, y - 1));
}
}
// East
if(x != width - 1) {
int east = foreground.getRGB(x + 1, y);
if (((east >> 24) & 0xFF) > 0 && (east & 0xFFFFFF) > 0x111100) {
foreground.setRGB(x + 1, y, 0x00FFFFFF);
coords.add(new Point(x + 1, y));
}
}
// South
if(y != height - 1) {
int south = foreground.getRGB(x, y + 1);
if (((south >> 24) & 0xFF) > 0 && (south & 0xFFFFFF) > 0x111100) {
foreground.setRGB(x, y + 1, 0x00FFFFFF);
coords.add(new Point(x, y + 1));
}
}
// West
if(x != 0) {
int west = foreground.getRGB(x - 1, y);
if (((west >> 24) & 0xFF) > 0 && (west & 0xFFFFFF) > 0x111100) {
foreground.setRGB(x - 1, y, 0x00FFFFFF);
coords.add(new Point(x - 1, y));
}
}
}
What's the easiest way to auto crop the white border out of an image in java? Thanks in advance...
Here's a way to crop all 4 sides, using the color from the very top-left pixel as the baseline, and allow for a tolerance of color variation so that noise in the image won't make the crop useless
public BufferedImage getCroppedImage(BufferedImage source, double tolerance) {
// Get our top-left pixel color as our "baseline" for cropping
int baseColor = source.getRGB(0, 0);
int width = source.getWidth();
int height = source.getHeight();
int topY = Integer.MAX_VALUE, topX = Integer.MAX_VALUE;
int bottomY = -1, bottomX = -1;
for(int y=0; y<height; y++) {
for(int x=0; x<width; x++) {
if (colorWithinTolerance(baseColor, source.getRGB(x, y), tolerance)) {
if (x < topX) topX = x;
if (y < topY) topY = y;
if (x > bottomX) bottomX = x;
if (y > bottomY) bottomY = y;
}
}
}
BufferedImage destination = new BufferedImage( (bottomX-topX+1),
(bottomY-topY+1), BufferedImage.TYPE_INT_ARGB);
destination.getGraphics().drawImage(source, 0, 0,
destination.getWidth(), destination.getHeight(),
topX, topY, bottomX, bottomY, null);
return destination;
}
private boolean colorWithinTolerance(int a, int b, double tolerance) {
int aAlpha = (int)((a & 0xFF000000) >>> 24); // Alpha level
int aRed = (int)((a & 0x00FF0000) >>> 16); // Red level
int aGreen = (int)((a & 0x0000FF00) >>> 8); // Green level
int aBlue = (int)(a & 0x000000FF); // Blue level
int bAlpha = (int)((b & 0xFF000000) >>> 24); // Alpha level
int bRed = (int)((b & 0x00FF0000) >>> 16); // Red level
int bGreen = (int)((b & 0x0000FF00) >>> 8); // Green level
int bBlue = (int)(b & 0x000000FF); // Blue level
double distance = Math.sqrt((aAlpha-bAlpha)*(aAlpha-bAlpha) +
(aRed-bRed)*(aRed-bRed) +
(aGreen-bGreen)*(aGreen-bGreen) +
(aBlue-bBlue)*(aBlue-bBlue));
// 510.0 is the maximum distance between two colors
// (0,0,0,0 -> 255,255,255,255)
double percentAway = distance / 510.0d;
return (percentAway > tolerance);
}
If you want the white parts to be invisible, best way is to use image filters and make white pixels transparent, it is discussed here by #PhiLho with some good samples,
if you want to resize your image so it's borders won't have white colors, you can do it with four simple loops,
this little method that I've write for you does the trick, note that it just crop upper part of image, you can write the rest,
private Image getCroppedImage(String address) throws IOException{
BufferedImage source = ImageIO.read(new File(address)) ;
boolean flag = false ;
int upperBorder = -1 ;
do{
upperBorder ++ ;
for (int c1 =0 ; c1 < source.getWidth() ; c1++){
if(source.getRGB(c1, upperBorder) != Color.white.getRGB() ){
flag = true;
break ;
}
}
if (upperBorder >= source.getHeight())
flag = true ;
}while(!flag) ;
BufferedImage destination = new BufferedImage(source.getWidth(), source.getHeight() - upperBorder, BufferedImage.TYPE_INT_ARGB) ;
destination.getGraphics().drawImage(source, 0, upperBorder*-1, null) ;
return destination ;
}
And here just another Example
private static BufferedImage autoCrop(BufferedImage sourceImage) {
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
boolean firstFind = true;
for (int x = 0; x < sourceImage.getWidth(); x++) {
for (int y = 0; y < sourceImage.getWidth(); y++) {
// pixel is not empty
if (sourceImage.getRGB(x, y) != 0) {
// we walk from left to right, thus x can be applied as left on first finding
if (firstFind) {
left = x;
}
// update right on each finding, because x can grow only
right = x;
// on first find apply y as top
if (firstFind) {
top = y;
} else {
// on each further find apply y to top only if a lower has been found
top = Math.min(top, y);
}
// on first find apply y as bottom
if (bottom == 0) {
bottom = y;
} else {
// on each further find apply y to bottom only if a higher has been found
bottom = Math.max(bottom, y);
}
firstFind = false;
}
}
}
return sourceImage.getSubimage(left, top, right - left, bottom - top);
}
img is original image source
BufferedImage subImg = img.getSubimage(0, 0, img.getWidth() - 1, img.getHeight() - 1);
I'm using the following piece of code to iterate over all pixels in an image and draw a red 1x1 square over the pixels that are within a certain RGB-tolerance. I guess there is a more efficient way to do this? Any ideas appreciated. (bi is a BufferedImage and g2 is a Graphics2D with its color set to Color.RED).
Color targetColor = new Color(selectedRGB);
for (int x = 0; x < bi.getWidth(); x++) {
for (int y = 0; y < bi.getHeight(); y++) {
Color pixelColor = new Color(bi.getRGB(x, y));
if (withinTolerance(pixelColor, targetColor)) {
g2.drawRect(x, y, 1, 1);
}
}
}
private boolean withinTolerance(Color pixelColor, Color targetColor) {
int pixelRed = pixelColor.getRed();
int pixelGreen = pixelColor.getGreen();
int pixelBlue = pixelColor.getBlue();
int targetRed = targetColor.getRed();
int targetGreen = targetColor.getGreen();
int targetBlue = targetColor.getBlue();
return (((pixelRed >= targetRed - tolRed) && (pixelRed <= targetRed + tolRed)) &&
((pixelGreen >= targetGreen - tolGreen) && (pixelGreen <= targetGreen + tolGreen)) &&
((pixelBlue >= targetBlue - tolBlue) && (pixelBlue <= targetBlue + tolBlue)));
}
if (withinTolerance(pixelColor, targetColor)) {
bi.setRGB( x, y, 0xFFFF0000 )
}
BufferedImage's setRGB method's third parameter, as explained in the Javadoc, takes a pixel of the TYPE_INT_ARGB form.
8 bits for the alpha (FF here, fully opaque)
8 bits for the red component (FF here, fully flashy red)
8 bits for the green component (0, no green)
8 bits for the blue component.