Java/Image: How to make adjacent background pixels transparent?
There are a lot of questions about how to make the background-color of an image transparent, but all the anwers seem to use an RgbImageFilter
to make every occurrence of a specific color transparent.
My question is, how would I implement this "background removal" in Java, so that it floods transparency from a fixed point (as per the "bucket" operation in Paint, or the RMagick functi开发者_StackOverflow中文版on Image#matte_floodfill)?
As is the way with the Internet, I wound up on this page after a bit of searching trying to find some code that did something similar.
Here's my knocked-together solution. It's not perfect but it's perhaps a starting point for someone else trying to do it.
This works by choosing the four corners of the image, averaging them, and using that as the anchor colour. I use a Pixel class for what seemed like convenience initially and ended up wasting my time! Hah. As is the way.
public class Pixel implements Comparable{
int x,y;
public Pixel(int x, int y){
this.x = x;
this.y = y;
}
@Override
public int compareTo(Object arg0) {
Pixel p = (Pixel) arg0;
if(p.x == x && p.y == y)
return 0;
return -1;
}
}
And here's the beef:
public class ImageGrab {
private static int pixelSimilarityLimit = 20;
public static void main(String[] args){
BufferedImage image = null;
try {
URL url = new URL("http://animal-photography.com/thumbs/russian_blue_cat_side_view_on_~AP-0PR4DL-TH.jpg");
image = ImageIO.read(url);
} catch (IOException e) {
e.printStackTrace();
}
Color[] corners = new Color[]{new Color(image.getRGB(0, 0)),
new Color(image.getRGB(image.getWidth()-1, 0)),
new Color(image.getRGB(0, image.getHeight()-1)),
new Color(image.getRGB(image.getWidth()-1, image.getHeight()-1))};
int avr = 0, avb=0, avg=0, ava=0;
for(Color c : corners){
avr += c.getRed();
avb += c.getBlue();
avg += c.getGreen();
ava += c.getAlpha();
}
System.out.println(avr/4+","+avg/4+","+avb/4+","+ava/4);
for(Color c : corners){
if(Math.abs(c.getRed() - avr/4) < pixelSimilarityLimit &&
Math.abs(c.getBlue() - avb/4) < pixelSimilarityLimit &&
Math.abs(c.getGreen() - avg/4) < pixelSimilarityLimit &&
Math.abs(c.getAlpha() - ava/4) < pixelSimilarityLimit){
}
else{
return;
}
}
Color master = new Color(avr/4, avg/4, avb/4, ava/4);
System.out.println("Image sufficiently bordered.");
LinkedList<Pixel> open = new LinkedList<Pixel>();
LinkedList<Pixel> closed = new LinkedList<Pixel>();
open.add(new Pixel(0,0));
open.add(new Pixel(0,image.getHeight()-1));
open.add(new Pixel(image.getWidth()-1,0));
open.add(new Pixel(image.getWidth()-1,image.getHeight()-1));
while(open.size() > 0){
Pixel p = open.removeFirst();
closed.add(p);
for(int i=-1; i<2; i++){
for(int j=-1; j<2; j++){
if(i == 0 && j == 0)
continue;
if(p.x+i < 0 || p.x+i >= image.getWidth() || p.y+j < 0 || p.y+j >= image.getHeight())
continue;
Pixel thisPoint = new Pixel(p.x+i, p.y+j); boolean add = true;
for(Pixel pp : open)
if(thisPoint.x == pp.x && thisPoint.y == pp.y)
add = false;
for(Pixel pp : closed)
if(thisPoint.x == pp.x && thisPoint.y == pp.y)
add = false;
if(add && areSimilar(master,new Color(image.getRGB(p.x+i, p.y+j)))){
open.add(thisPoint);
}
}
}
}
for(Pixel p : closed){
Color c = new Color(image.getRGB(p.x, p.y));
Color newC = new Color(0, 0, 0, 0);
image.setRGB(p.x, p.y, newC.getRGB());
}
try {
File outputfile = new File("C:/Users/Mike/Desktop/saved.png");
ImageIO.write(image, "png", outputfile);
} catch (IOException e) {
}
}
public static boolean areSimilar(Color c, Color d){
if(Math.abs(c.getRed() - d.getRed()) < pixelSimilarityLimit &&
Math.abs(c.getBlue() - d.getBlue()) < pixelSimilarityLimit &&
Math.abs(c.getGreen() - d.getGreen()) < pixelSimilarityLimit &&
Math.abs(c.getAlpha() - d.getAlpha()) < pixelSimilarityLimit){
return true;
}
else{
return false;
}
}
}
In case anyone's worried, consider this public domain. Cheers! Hope it helps.
An unsatisfactory solution that I'm currently using is simply anticipating the background color that you're going to place your transparent image against (as you usually will do this) and using the solution with an RgbImageFilter
as described here.
If someone wants to post a satisfactory solution, please do - until then, I'm going to accept this, as it works.
Here is something that I just put together to remove the background from a BufferedImage. It is pretty simple but there may be more efficient ways of doing it.
I have it set up with three inputs (a source image, the tolerance allowed, and the color that you want to replace the background with). It simply returns a buffered image with the changes made to it.
It finds the color near each corner and averages them to create a reference color then it replaces each pixel that is within the tolerance range of the reference.
In order the make the background transparent you would need to pass in
BufferedImage RemoveBackground(BufferedImage src, float tol, int color)
{
BufferedImage dest = src;
int h = dest.getHeight();
int w = dest.getWidth();
int refCol = -(dest.getRGB(2,2)+dest.getRGB(w-2,2)+dest.getRGB(2,h-2)+dest.getRGB(w-2,h-2))/4;
int Col = 0;
int x = 1;
int y = 1;
int upperBound = (int)(refCol*(1+tol));
int lowerBound = (int)(refCol*(1-tol));
while (x < w)
{
y = 1;
while (y < h)
{
Col = -dest.getRGB(x,y);
if (Col > lowerBound && Col < upperBound)
{
dest.setRGB(x,y,color);
}
y++;
}
x++;
}
return dest;
}
I know this is an old thread but hopefully this will come in handy for someone.
Edit: I just realized that this does not work for transparencies, just for replacing a RGB value with another RGB value. It would need a little adaptation to do ARGB values.
精彩评论