Java Draw on a image with jframe - java

So I have looked at these questions
Change the color of each pixel in an image java
How to convert a Drawable to a Bitmap?
How to oepn an image to draw on that
So I have a program that takes a screen shots in a while loop and then checks the color of the image per pixel, which works fine, but the problem is it detects all the color in the image. I used this answer: Java colour detection
But when for example taking this image
I only want to see the green square but not the loose pixels, and then draw something over it so I can see if my program understands that it only needs to target that square.
So what would be the best way to approach this problem?
pseudo code
Step 1: Detect the color
Step 2: Save the position of the pixel
Step 3: Then when ever the same pixel is found Look for the distance between ?
Step 4: if the distance is below certain amount look at the third up coming pixel locations
Step 5: if step 4 fails look at the last found pixel and repeat step 2
Step 6: if its a row of mutiple pixels in a row save the locations and start looking at the same X but different y to see if its true (But should also work for circles ;-;)
Is the best way to address this problem? Or is there an external java libary that can detect this for you?
Update:
So after doing some programming I came up with this what is prob not the best way to do this in anyway but its seems to be finding the yellow square
package src;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.*;
import java.awt.*;
import javax.swing.*;;
import java.util.List;
import java.util.Arrays;
public class ColorDectection
{
private static ArrayList<ArrayList<Integer>> yellowList = new ArrayList<ArrayList<Integer>>();
private static ArrayList<Integer> tmpYellowList = new ArrayList<Integer>();
private static int tmpYellowPixelX = -1;
/*
#params Image img
#params int w
#params int h
Confert notmal image to BufferedImage
Then set the width and height to image pixels cause image is always full screen
Loop all the pixels
*/
public void detectColorInImage(Image img, int w, int h) {
BufferedImage conImg = convertImage(img);
int[][] pixels = new int[w][h];
for( int i = 0; i < w; i++ )
for( int j = 0; j < h; j++ )
selectColor(conImg, i,j);
handleYellowList();
}
/*
#params Image img
#params int pixelX
#params int pixelY
get int rgb of pixel on location
transfer RGB to HSF
if color is found give pixel X and Y to the corresponding function
*/
private void selectColor(BufferedImage img, int pixelX, int pixelY){
int rgb = img.getRGB(pixelX,pixelY);
float hsb[] = new float[3];
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb ) & 0xFF;
Color.RGBtoHSB(r, g, b, hsb);
if (hsb[1] < 0.1 && hsb[2] > 0.9) whiteFound();
else if (hsb[2] < 0.1) blackFound();
else {
float deg = hsb[0]*360;
if (deg >= 30 && deg < 90) yellowFound(pixelX, pixelY);
else if (deg >= 90 && deg < 150) greenFound();
else if (deg >= 150 && deg < 210) cyanFound();
else if (deg >= 210 && deg < 270) blueFound();
else if (deg >= 270 && deg < 330) magentaFound();
else redFound();
}
}
private void handleYellowList(){
// System.out.println(yellowList);
// Step 1: Check if there is an item in the array
// Step 2: Save the position of the pixel
// Step 3: Then when ever the same pixel is found Look for the distance between ?
// Step 4: if the distance is below certain amount look at the third up coming pixel locations
// Step 5: if step 4 fails look at the last found pixel and repeat step 2
// Step 6: if its a row of mutiple pixels in a row save the locations and start looking at the same X but different y to see if its true (But should also work for circles ;-;)
}
private void blackFound(){
//
}
private void whiteFound(){
//
}
/*
#params int pixelX
#params int pixelY
if is this is NOT the first time in the loop && of the pixelX and tmp pixel are not the same
then set tmpYellowPixelX to pixelX
1. add Pixel y to the list!
2. and if the tempArray list is smaller then 20 dont add them to the current list cuz its prob alone pixel!
3. add the new pixelY!
4. create new arraylist cuz TempPixel and PixelX were not the same so we must have been on a new row!
else if Check if its the first iteration
1. just add set tmpYellowPixelX to pixelX
2. add pixelY to the tmpYellowList
else just add pixelY to tmpYellowList
if color is found give pixel X and Y to the corresponding function
*/
private void yellowFound(int pixelX, int pixelY){
if (tmpYellowPixelX != pixelX && tmpYellowPixelX != -1){
tmpYellowPixelX = pixelX;
tmpYellowList.add(pixelY);
if(tmpYellowList.size() > 50) {
yellowList.add(tmpYellowList);
System.out.println(pixelX);
System.out.println(tmpYellowList.size());
}
tmpYellowList = new ArrayList<Integer>();
} else if (tmpYellowPixelX != pixelX && tmpYellowPixelX == -1 ) {
tmpYellowPixelX = pixelX;
tmpYellowList.add(pixelY);
} else {
tmpYellowList.add(pixelY);
}
}
private void greenFound(){
//
}
private void cyanFound(){
//
}
private void blueFound(){
//
}
private void magentaFound(){
//
}
private void redFound(){
//
}
private static BufferedImage convertImage(Image img) {
if (img instanceof BufferedImage) return (BufferedImage) img;
BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(img, 0, 0, null);
bGr.dispose();
return bimage;
}
}
Still Need to make yellowFound function modulair so I can use it on all of them and more propper testing

Related

Algorithm to get all pixels between color border?

I have a long png file containing many sprites in a row, but their width/height changes by a little bit. However, all sprites have a fixed blue color 1px border around it.
However, after each sprite, the borders are connected to each other by 2px (just border after border that interacts) see this:
But at the bottom of the sprites, it misses one pixel point
Is there an existing algorithm that can get all pixels between a color border like this, including the border when giving the pixels?
Or any other ideas how to grab all sprites of one file like this and give them a fixed size?
I took your image and transformed it to match your description.
In plain text I went form left to right and identify lines that might indicate a start or end to an image and used a tracker variable to decide which is which.
I approached it like this in Java:
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.io.IOException;
public class PixelArtSizeFinder {
public static void main(String[] args) throws IOException {
File imageFile = new File("pixel_boat.png");
BufferedImage image = ImageIO.read(imageFile);
int w = image.getWidth();
int h = image.getHeight();
System.out.format("Size: %dx%d%n", w, h);
Raster data = image.getData();
int objectsFound = 0;
int startObjectWidth = 0;
int endObjectWidth = 0;
boolean scanningObject = false;
for (int x = 0; x < w; x++) {
boolean verticalLineContainsOnlyTransparentOrBorder = true;
for (int y = 0; y < h; y++) {
int[] pixel = data.getPixel(x, y, new int[4]);
if (isOther(pixel)) {
verticalLineContainsOnlyTransparentOrBorder = false;
}
}
if (verticalLineContainsOnlyTransparentOrBorder) {
if (scanningObject) {
endObjectWidth = x;
System.out.format("Object %d: %d-%d (%dpx)%n",
objectsFound,
startObjectWidth,
endObjectWidth,
endObjectWidth - startObjectWidth);
} else {
objectsFound++;
startObjectWidth = x;
}
scanningObject ^= true; //toggle
}
}
}
private static boolean isTransparent(int[] pixel) {
return pixel[3] == 0;
}
private static boolean isBorder(int[] pixel) {
return pixel[0] == 0 && pixel[1] == 187 && pixel[2] == 255 && pixel[3] == 255;
}
private static boolean isOther(int[] pixel) {
return !isTransparent(pixel) && !isBorder(pixel);
}
}
and the result was
Size: 171x72
Object 1: 0-27 (27px)
Object 2: 28-56 (28px)
Object 3: 57-85 (28px)
Object 4: 86-113 (27px)
Object 5: 114-142 (28px)
Object 6: 143-170 (27px)
I don't know if any algorithm or function already exists for this but what you can do is :
while the boats are all the same and you wanna get all the pixels between two blue pixels so you can use something like this :
for all i in vertical pixels
for all j in horizontal pixels
if pixel(i,j) == blue then
j = j+ 1
while pixel(i,j) != blue then
you save this pixel in an array for example
j = j+1
end while
end if
end for
end for
This is just an idea and for sure not the most optimal but you can you use it and perform it to make it better ;)

Handling of java.awt.Color while saving file (JPG) with ImageIO.write

Taking part in a Coursera course, I've been trying to use steganography to hide an image in another. This means I've tried to store the "main" picture's RGB values on 6 bits and the "second" picture's values on the last 2 bits.
I'm merging these two values to create a joint picture, and have also coded a class to parse the joint picture, and recover the original images.
Image recovery has not been successful, although it seems (from other examples provided within the course) that the parser is working fine. I suppose that saving the pictures after modification, using ImageIO.write somehow modifies the RGB values I have carefully set in the code. :D
public static BufferedImage mergeImage(BufferedImage original,
BufferedImage message, int hide) {
// hidden is the num of bits on which the second image is hidden
if (original != null) {
int width = original.getWidth();
int height = original.getHeight();
BufferedImage output = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
int pix_orig = original.getRGB(i, j);
int pix_msg = message.getRGB(i, j);
int pixel = setpixel(pix_orig, pix_msg, hide);
output.setRGB(i, j, pixel);
}
}
return output;
}
return null;
}
public static int setpixel(int pixel_orig, int pixel_msg, int hide) {
int bits = (int) Math.pow(2, hide);
Color orig = new Color(pixel_orig);
Color msg = new Color(pixel_msg);
int red = ((orig.getRed() / bits) * bits); //+ (msg.getRed() / (256/bits));
if (red % 4 != 0){
counter+=1;
}
int green = ((orig.getGreen() / bits) * bits) + (msg.getGreen() / (256/bits));
int blue = ((orig.getBlue() / bits) * bits) + (msg.getBlue() / (256/bits));
int pixel = new Color(red, green, blue).getRGB();
return pixel;
}
This is the code I use for setting the RGB values of the merged picture. As you can see, I have commented part of the code belonging to red to check whether the main picture can actually be saved on 6 bits, assuming I take int hide=2
Although if I make the same checks in the parsing part of the code:
public static BufferedImage parseImage(BufferedImage input, int hidden){
// hidden is the num of bits on which the second image is hidden
if (input != null){
int width = input.getWidth();
int height = input.getHeight();
BufferedImage output = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for(int i=0;i<width;i++){
for(int j=0;j<height;j++){
int pixel = input.getRGB(i, j);
pixel = setpixel(pixel,hidden);
output.setRGB(i, j, pixel);
}
}
return output;
}
return null;
}
public static int setpixel(int pixel, int hidden){
int bits = (int) Math.pow(2,hidden);
Color c = new Color(pixel);
if (c.getRed() % 4 != 0){
counter+=1;
}
int red = (c.getRed() - (c.getRed()/bits)*bits)*(256/bits);
int green = (c.getGreen() - (c.getGreen()/bits)*bits)*(256/bits);
int blue = (c.getBlue() - (c.getBlue()/bits)*bits)*(256/bits);
pixel = new Color(red,green,blue).getRGB();
return pixel;
}
I get ~100k pixels where the R value has a remainder if divided by four.
I suspect there' some problem with the function of ImageIO.write.
I know the question is going to be vague, but
1) Can someone confirm this
2) What can I do to get this code working?
Thanks a lot!
JPEG has lossy compression, which means some pixels will effectively be modified when reloading the image. This isn't a fault of ImageIO.write, it's how the format works. If you want to embed your data directly to pixel values, you want to save the image to a lossless format, such as BMP or PNG.

Terrain curve to array of points

In my 2D game I'm using graphic tools to create nice, smooth terrain represented by black color:
Simple algorithm written in java looks for black color every 15 pixels, creating following set of lines (gray):
As you can see, there's some places that are mapped very bad, some are pretty good. In other case it would be not necessary to sample every 15 pixels, eg. if terrain is flat.
What's the best way to covert this curve to set of points [lines], using as little points as possible?
Sampling every 15 pixels = 55 FPS, 10 pixels = 40 FPS
Following algorithm is doing that job, sampling from right to left, outputting pasteable into code array:
public void loadMapFile(String path) throws IOException {
File mapFile = new File(path);
image = ImageIO.read(mapFile);
boolean black;
System.out.print("{ ");
int[] lastPoint = {0, 0};
for (int x = image.getWidth()-1; x >= 0; x -= 15) {
for (int y = 0; y < image.getHeight(); y++) {
black = image.getRGB(x, y) == -16777216 ? true : false;
if (black) {
lastPoint[0] = x;
lastPoint[1] = y;
System.out.print("{" + (x) + ", " + (y) + "}, ");
break;
}
}
}
System.out.println("}");
}
Im developing on Android, using Java and AndEngine
This problem is nearly identical to the problem of digitization of a signal (such as sound), where the basic law is that the signal in the input that had the frequency too high for the sampling rate will not be reflected in the digitized output. So the concern is that if you check ever 30 pixels and then test the middle as bmorris591 suggests, you might miss that 7 pixel hole between the sampling points. This suggests that if there are 10 pixel features you cannot afford to miss, you need to do scanning every 5 pixels: your sample rate should be twice the highest frequency present in the signal.
One thing that can help improve your algorithm is a better y-dimension search. Currently you are searching for the intersection between sky and terrain linearly, but a binary search should be faster
int y = image.getHeight()/2; // Start searching from the middle of the image
int yIncr = y/2;
while (yIncr>0) {
if (image.getRGB(x, y) == -16777216) {
// We hit the terrain, to towards the sky
y-=yIncr;
} else {
// We hit the sky, go towards the terrain
y+=yIncr;
}
yIncr = yIncr/2;
}
// Make sure y is on the first terrain point: move y up or down a few pixels
// Only one of the following two loops will execute, and only one or two iterations max
while (image.getRGB(x, y) != -16777216) y++;
while (image.getRGB(x, y-1) == -16777216) y--;
Other optimizations are possible. If you know that your terrain has no cliffs, then you only need to search the window from lastY+maxDropoff to lastY-maxDropoff. Also, if your terrain can never be as tall as the entire bitmap, you don't need to search the top of the bitmap either. This should help to free some CPU cycles you can use for higher-resolution x-scanning of the terrain.
I propose to find border points which exists on the border between white and dark pixels. After that we can digitize those points. To do that, we should define DELTA which specify which point we should skip and which we should add to result list.
DELTA = 3, Number of points = 223
DELTA = 5, Number of points = 136
DELTA = 10, Number of points = 70
Below, I have put source code, which prints image and looking for points. I hope, you will be able to read it and find a way to solve your problem.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Program {
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File("/home/michal/Desktop/FkXG1.png"));
PathFinder pathFinder = new PathFinder(10);
List<Point> borderPoints = pathFinder.findBorderPoints(image);
System.out.println(Arrays.toString(borderPoints.toArray()));
System.out.println(borderPoints.size());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ImageBorderPanel(image, borderPoints));
frame.pack();
frame.setMinimumSize(new Dimension(image.getWidth(), image.getHeight()));
frame.setVisible(true);
}
}
class PathFinder {
private int maxDelta = 3;
public PathFinder(int delta) {
this.maxDelta = delta;
}
public List<Point> findBorderPoints(BufferedImage image) {
int width = image.getWidth();
int[][] imageInBytes = convertTo2DWithoutUsingGetRGB(image);
int[] borderPoints = findBorderPoints(width, imageInBytes);
List<Integer> indexes = dwindlePoints(width, borderPoints);
List<Point> points = new ArrayList<Point>(indexes.size());
for (Integer index : indexes) {
points.add(new Point(index, borderPoints[index]));
}
return points;
}
private List<Integer> dwindlePoints(int width, int[] borderPoints) {
List<Integer> indexes = new ArrayList<Integer>(width);
indexes.add(borderPoints[0]);
int delta = 0;
for (int index = 1; index < width; index++) {
delta += Math.abs(borderPoints[index - 1] - borderPoints[index]);
if (delta >= maxDelta) {
indexes.add(index);
delta = 0;
}
}
return indexes;
}
private int[] findBorderPoints(int width, int[][] imageInBytes) {
int[] borderPoints = new int[width];
int black = Color.BLACK.getRGB();
for (int y = 0; y < imageInBytes.length; y++) {
int maxX = imageInBytes[y].length;
for (int x = 0; x < maxX; x++) {
int color = imageInBytes[y][x];
if (color == black && borderPoints[x] == 0) {
borderPoints[x] = y;
}
}
}
return borderPoints;
}
private int[][] convertTo2DWithoutUsingGetRGB(BufferedImage image) {
final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
final int width = image.getWidth();
final int height = image.getHeight();
final boolean hasAlphaChannel = image.getAlphaRaster() != null;
int[][] result = new int[height][width];
if (hasAlphaChannel) {
final int pixelLength = 4;
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
int argb = 0;
argb += (((int) pixels[pixel] & 0xff) << 24); // alpha
argb += ((int) pixels[pixel + 1] & 0xff); // blue
argb += (((int) pixels[pixel + 2] & 0xff) << 8); // green
argb += (((int) pixels[pixel + 3] & 0xff) << 16); // red
result[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
} else {
final int pixelLength = 3;
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
int argb = 0;
argb += -16777216; // 255 alpha
argb += ((int) pixels[pixel] & 0xff); // blue
argb += (((int) pixels[pixel + 1] & 0xff) << 8); // green
argb += (((int) pixels[pixel + 2] & 0xff) << 16); // red
result[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
}
return result;
}
}
class ImageBorderPanel extends JPanel {
private static final long serialVersionUID = 1L;
private BufferedImage image;
private List<Point> borderPoints;
public ImageBorderPanel(BufferedImage image, List<Point> borderPoints) {
this.image = image;
this.borderPoints = borderPoints;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
Graphics2D graphics2d = (Graphics2D) g;
g.setColor(Color.YELLOW);
for (Point point : borderPoints) {
graphics2d.fillRect(point.x, point.y, 3, 3);
}
}
}
In my source code I have used example from this question:
Java - get pixel array from image
The most efficient solution (with respect to points required) would be to allow for variable spacing between points along the X axis. This way, a large flat part would require very few points/samples and complex terrains would use more.
In 3D mesh processing, there is a nice mesh simplification algorithm named "quadric edge collapse", which you can adapt to your problem.
Here is the idea, translated to your problem - it actually gets much simpler than the original 3D algorithm:
Represent your curve with way too many points.
For each point, measure the error (i.e. difference to the smooth terrain) if you remove it.
Remove the point that gives the smallest error.
Repeat until you have reduced the number of points far enough or errors get too large.
To be more precise regarding step 2: Given points P, Q, R, the error of Q is the difference between the approximation of your terrain by two straight lines, P->Q and Q->R, and the approximation of your terrain by just one line P->R.
Note that when a point is removed only its neighbors need an update of their error value.

Importing A Sprite from a sprite sheet

Well i have been watching a couple of videos of youtube on how take sprites from a spritesheet (8x8) and i really liked the tutorial by DesignsByZepher. However the method he uses results in him importing a sorite sheet and then changing the colors to in-code selected colours.
http://www.youtube.com/watch?v=6FMgQNDNMJc displaying the sheet
http://www.youtube.com/watch?v=7eotyB7oNHE for the color rendering
The code that i have made from watching his video is:
package exikle.learn.game.gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class SpriteSheet {
public String path;
public int width;
public int height;
public int[] pixels;
public SpriteSheet(String path) {
BufferedImage image = null;
try {
image = ImageIO.read(SpriteSheet.class.getResourceAsStream(path));
} catch (IOException e) {
e.printStackTrace();
}
if (image == null) { return; }
this.path = path;
this.width = image.getWidth();
this.height = image.getHeight();
pixels = image.getRGB(0, 0, width, height, null, 0, width);
for (int i = 0; i < pixels.length; i++) {
pixels[i] = (pixels[i] & 0xff) / 64;
}
}
}
^This is the code where an image gets imported
package exikle.learn.game.gfx;
public class Colours {
public static int get(int colour1, int colour2, int colour3, int colour4) {
return (get(colour4) << 24) + (get(colour3) << 16)
+ (get(colour2) << 8) + get(colour1);
}
private static int get(int colour) {
if (colour < 0)
return 255;
int r = colour / 100 % 10;
int g = colour / 10 % 10;
int b = colour % 10;
return r * 36 + g * 6 + b;
}
}
^ and the code which i think deals with all the colors but im kinda confused about this.
My question is how do i remove the color modifier and just import and display the sprite sheet as is, so with the color it already has?
So you're fiddling with the Minicraft source, I see. The thing about Notch's code is that he substantially limited himself technically in this game. What the engine is doing is basically saying every sprite/tile can have 4 colors (from the grey-scaled spritesheet), he generates his own color palette that he retrieves colors from and sets accordingly during rendering. I can't remember exactly how many bits per channel he set and such.
However, you obviously are very new to programming and imo there's nothing better than fiddling with and analyzing other people's code.. that is, if you actually can do so. The Screen class is where the rendering takes place and hence it's what uses the spritesheet and therefore gives color accordingly to whatever tile you tell it to get. Markus is quite clever, despite poorly written code (which is completely forgiven as he did have 48 hours to make the damned thing ;))
if you want to just display the spritesheet as is, you can either rewrite the render function or overload it to something like this... (in class Screen)
public void render() {
for(int y = 0; y < h; y++) {
if(y >= sheet.h) continue; //prevent going out of bounds on y-axis
for(int x = 0; x < w; x++) {
if(x >= sheet.w) continue; //prevent going out of bounds on x-axis
pixels[x + y * w] = sheet.pixels[x + y * sheet.w];
}
}
}
This will just put whatever of the sheet it can fit into the screen for rendering (it's a really simple piece of code, but should work), the next step will be copying the pixels over to the actual raster for display, which I'm sure you can handle. (If you have copy-pasted all of the minicraft source code or some other slightly modified source code, you might want to change some things about that as well.)
All the cheers!
This basics would be to replace the get(int) method...
private static int get(int colour) {
//if (colour < 0)
// return 255;
//int r = colour / 100 % 10;
//int g = colour / 10 % 10;
//int b = colour % 10;
//return r * 36 + g * 6 + b;
return colour;
}
I'd also get rid of
for (int i = 0; i < pixels.length; i++) {
pixels[i] = (pixels[i] & 0xff) / 64;
}
From the main method
But to be honest, wouldn't it be easier to simply use BufferedImage#getSubImage?

How to convert an image into a transparent image in java

How to convert a white background of an image into a transparent background? Can anyone tel me how to do this?
The first result from Google is this:
Make a color transparent
http://www.rgagnon.com/javadetails/java-0265.html
It makes the Blue part of an image transparent, but I'm sure you can adapt that to use White intstead
(hint: Pass Color.WHITE to the makeColorTransparent function, instead of Color.BLUE)
Found a more complete and modern answer here: How to make a color transparent in a BufferedImage and save as PNG
This method will make background transparent. You need to pass the image you want to modify, colour, and tolerance.
final int color = ret.getRGB(0, 0);
final Image imageWithTransparency = makeColorTransparent(ret, new Color(color), 10);
final BufferedImage transparentImage = imageToBufferedImage(imageWithTransparency);
private static BufferedImage imageToBufferedImage(final Image image) {
final BufferedImage bufferedImage =
new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
final Graphics2D g2 = bufferedImage.createGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
return bufferedImage;
}
private static Image makeColorTransparent(final BufferedImage im, final Color color, int tolerance) {
int temp = 0;
if (tolerance < 0 || tolerance > 100) {
System.err.println("The tolerance is a percentage, so the value has to be between 0 and 100.");
temp = 0;
} else {
temp = tolerance * (0xFF000000 | 0xFF000000) / 100;
}
final int toleranceRGB = Math.abs(temp);
final ImageFilter filter = new RGBImageFilter() {
// The color we are looking for (white)... Alpha bits are set to opaque
public int markerRGBFrom = (color.getRGB() | 0xFF000000) - toleranceRGB;
public int markerRGBTo = (color.getRGB() | 0xFF000000) + toleranceRGB;
public final int filterRGB(final int x, final int y, final int rgb) {
if ((rgb | 0xFF000000) >= markerRGBFrom && (rgb | 0xFF000000) <= markerRGBTo) {
// Mark the alpha bits as zero - transparent
return 0x00FFFFFF & rgb;
} else {
// Nothing to do
return rgb;
}
}
};
final ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(ip);
}
Here is my solution. This filter will remove the background from any image as long as the background image color is in the top left corner.
private static class BackgroundFilter extends RGBImageFilter{
boolean setUp = false;
int bgColor;
#Override
public int filterRGB(int x, int y, int rgb) {
int colorWOAlpha = rgb & 0xFFFFFF;
if( ! setUp && x == 0 && y == 0 ){
bgColor = colorWOAlpha;
setUp = true;
}
else if( colorWOAlpha == bgColor )
return colorWOAlpha;
return rgb;
}
}
Elsewhere...
ImageFilter bgFilter = new BackgroundFilter();
ImageProducer ip = new FilteredImageSource(image.getSource(), bgFilter);
image = Toolkit.getDefaultToolkit().createImage(ip);
I am aware that this question is over a decade old and that some answers have already been given. However, none of them is satisfactory if the pixels inside the image are the same color as the background. Let's take a practical example. Given these images:
both have a white background, but the white color is also inside the image to be cutout. In other words, the white pixels on the outside of the two pennants must become transparent, the ones on the inside must remain as they are. Add to this the complication that the white of the background is not perfectly white (due to jpeg compression), so a tolerance is needed. The issue can be made more complex by figures that are not only convex, but also concave.
I created an algorithm in Java that solves the problem very well, I tested it with the two figures shown here. The following code refers to the Java API of Codename One (https://www.codenameone.com/javadoc/), but can be repurposed to the Java SE API or implemented in other languages. The important thing is to understand the rationale.
/**
* Given an image with no transparency, it makes the white background
* transparent, provided that the entire image outline has a different color
* from the background; the internal pixels of the image, even if they have
* the same color as the background, are not changed.
*
* #param source image with a white background; the image must have an
* outline of a different color from background.
* #return a new image with a transparent background
*/
public static Image makeBackgroundTransparent(Image source) {
/*
* Algorithm
*
* Pixels must be iterated in the four possible directions: (1) left to
* right, for each row (top to bottom); (2) from right to left, for each
* row (from top to bottom); (3) from top to bottom, for each column
* (from left to right); (4) from bottom to top, for each column (from
* left to right).
*
* In each iteration, each white pixel is replaced with a transparent
* one. Each iteration ends when a pixel of color other than white (or
* a transparent pixel) is encountered.
*/
if (source == null) {
throw new IllegalArgumentException("ImageUtilities.makeBackgroundTransparent -> null source image");
}
if (source instanceof FontImage) {
source = ((FontImage) source).toImage();
}
int[] pixels = source.getRGB(); // array instance containing the ARGB data within this image
int width = source.getWidth();
int height = source.getHeight();
int tolerance = 1000000; // value chosen through several attempts
// check if the first pixel is transparent
if ((pixels[0] >> 24) == 0x00) {
return source; // nothing to do, the image already has a transparent background
}
Log.p("Converting white background to transparent...", Log.DEBUG);
// 1. Left to right, for each row (top to bottom)
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int color = pixels[y * width + x];
if ((color >> 24) != 0x00 && color >= ColorUtil.WHITE - tolerance && color <= ColorUtil.WHITE + tolerance) { // means white with tolerance and no transparency
pixels[y * width + x] = 0x00; // means full transparency
} else {
break;
}
}
}
// 2. Right to left, for each row (top to bottom)
for (int y = 0; y < height; y++) {
for (int x = width - 1; x >= 0; x--) {
int color = pixels[y * width + x];
if ((color >> 24) != 0x00 && color >= ColorUtil.WHITE - tolerance && color <= ColorUtil.WHITE + tolerance) { // means white with tolerance and no transparency
pixels[y * width + x] = 0x00; // means full transparency
} else {
break;
}
}
}
// 3. Top to bottom, for each column (from left to right)
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int color = pixels[y * width + x];
if ((color >> 24) != 0x00 && color >= ColorUtil.WHITE - tolerance && color <= ColorUtil.WHITE + tolerance) { // means white with tolerance and no transparency
pixels[y * width + x] = 0x00; // means full transparency
} else {
break;
}
}
}
// 4. Bottom to top, for each column (from left to right)
for (int x = 0; x < width; x++) {
for (int y = height - 1; y >= 0; y--) {
int color = pixels[y * width + x];
if ((color >> 24) != 0x00 && color >= ColorUtil.WHITE - tolerance && color <= ColorUtil.WHITE + tolerance) { // means white with tolerance and no transparency
pixels[y * width + x] = 0x00; // means full transparency
} else {
break;
}
}
}
return EncodedImage.createFromRGB(pixels, width, height, false);
}

Categories

Resources