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));
}
}
}
Related
I would like to know how to isolate a certain color in an image and set the rest to gray using java.
So far I managed to do so, but in a hardcode way by looping through all the pixels of the image and then checking the RGB values.
I am using BufferedImage to load the image and the Color class to deal with RGB values.
The isColorRed checks if the color is red or not.
private static boolean isColorRed(Color color) {
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();
return ((red >= 70 && red <= 255) && (green >= 0 && green <= 80) && (blue >= 0 && blue <= 80));
}
The isolateColor method is responsible to set all pixels to grey except for the red ones.
private static void isolateColor(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color c = new Color(image.getRGB(x, y));
if (!isColorRed(c)) {
int red = (int) (c.getRed() * 0.3);
int green = (int) (c.getGreen() * 0.587);
int blue = (int) (c.getBlue() * 0.114);
int gray = red + green + blue;
Color newColor = new Color(gray, gray, gray);
image.setRGB(x, y, newColor.getRGB());
} else {
Color newColor = new Color(c.getRed(), c.getGreen(), c.getBlue());
image.setRGB(x, y, newColor.getRGB());
}
}
}
File file;
try {
file = new File("src/grey.png");
ImageIO.write(image, "png", file);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
The original image :
After the treatment :
Is there any API built-in or external that can isolate one color or more in a single image
I am working on a 2D platform game and I have a sprite sheet which includes the sprites of tiles and blocks.
I noticed that there was a pink-ish background behind the transparent sprites so I thought that Java wasn't loading the sprites as PNG and I tried to re-draw the sprite on a new bufferedImage, pixel by pixel checking if the pixel was R=255, G=63, B=52 but unfortunately, the code wasn't able to detect that either and at this point I have no more options left to try.
I made sure that the "pink" color values are correct by using a color picker.
original spritesheet (transparent):
The class that loads the sprite(s) is:
public class SpriteSheet {
private BufferedImage image;
public SpriteSheet(BufferedImage image) {
this.image = image;
}
public BufferedImage grabImage(int col, int row, int width, int height) {
BufferedImage alpha = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
BufferedImage img = image.getSubimage(
(col * width) - width,
(row * height) - height,
width,
height);
int w = img.getWidth();
int h = img.getHeight();
for(int y = 0; y < h; y++) {
for(int x = 0; x < w; x++) {
int pixel = img.getRGB(x, y);
int red, green, blue;
red = (pixel >> 16) & 0xff;
green = (pixel >> 8) & 0xff;
blue = (pixel) & 0xff;
if(red == 255 && green == 63 && blue == 52)
alpha.setRGB(x, y, new Color(0, 0, 0, 0).getRGB());
else
alpha.setRGB(x, y, pixel);
}
}
return alpha;
}
}
the class that loads the sprite sheet is:
public class Texture {
SpriteSheet bs, ss;
private BufferedImage block_sheet = null;
public BufferedImage[] block = new BufferedImage[3];
public Texture() {
BufferedImageLoader loader = new BufferedImageLoader();
try {
block_sheet = loader.loadImage("/tiles.png");
} catch(Exception e) {
e.printStackTrace();
}
bs = new SpriteSheet(block_sheet);
getTextures();
}
private void getTextures() {
block[0] = bs.grabImage(1, 1, 32, 32);
block[1] = bs.grabImage(2, 1, 32, 32);
block[2] = bs.grabImage(4, 1, 32, 32);
}
}
How do I get rid of the pink-ish background and keep transparency?
I dont understand why you're using subImage.
try {
BufferedImage img = ImageIO.read(new File("D:/image.png"));
for (int i = 0; i < img.getWidth(); i++) {
for (int j = 0; j < img.getHeight(); j++) {
Color pixelcolor = new Color(img.getRGB(i, j));
int r = pixelcolor.getRed();
int g = pixelcolor.getGreen();
int b = pixelcolor.getBlue();
if (r == 255 && g == 63 && b == 52) {
int rgb = new Color(255, 255, 255).getRGB();
img.setRGB(i, j, rgb);
}
}
}
ImageIO.write(img, "png", new File("D:/transparent.png"));
} catch (Exception e) {
System.err.println(e.getMessage());
}
cough, It worked all along, I had forgotten to disable the test blocks which was representing the blocks. Realized this after some time.
So the transparency was working fine. I just saw the rectangle i was drawing behind it.
I've put all the black pixels of the image in an array and I want them to get the color of their left neighbor. I run the code without errors but the result is not really what I'm expecting.
Where those black stripes comes form? I was expecting it to be all red.
Here's my code and results.
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.*;
public class ImageTest {
public static BufferedImage Threshold(BufferedImage img) {
int height = img.getHeight();
int width = img.getWidth();
BufferedImage finalImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
int r = 0;
int g = 0;
int b = 0;
List<Integer> blackpixels = new ArrayList<Integer>();
for (int x = 0; x < width; x++) {
try {
for (int y = 0; y < height; y++) {
//Get RGB values of pixels
int rgb = img.getRGB(x, y);
r = ImageTest.getRed(rgb);
g = ImageTest.getGreen(rgb);
b = ImageTest.getBlue(rgb);
int leftLoc = (x-1) + y*width;
if ((r < 5) && (g < 5) && (b < 5)) {
blackpixels.add(rgb);
Integer[] simpleArray = new Integer[ blackpixels.size() ];
System.out.print(simpleArray.length);
int pix = 0;
while(pix < simpleArray.length) {
r = leftLoc;
pix = pix +1;
}
}
finalImage.setRGB(x,y,ImageTest.mixColor(r, g,b));
}
}
catch (Exception e) {
e.getMessage();
}
}
return finalImage;
}
private static int mixColor(int red, int g, int b) {
return red<<16|g<<8|b;
}
public static int getRed(int rgb) {
return (rgb & 0x00ff0000) >> 16;
}
public static int getGreen(int rgb) {
return (rgb & 0x0000ff00) >> 8;
}
public static int getBlue(int rgb) {
return (rgb & 0x000000ff) >> 0;
}
}
The following might work.
The main change is that it first collects the locations of ALL dark pixels, then goes over them to assign the colour from their left neighbours.
import java.awt.image.BufferedImage;
import java.util.*;
public class BlackRedImage
{
public static BufferedImage Threshold( BufferedImage img )
{
int height = img.getHeight();
int width = img.getWidth();
BufferedImage finalImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
List<Integer> blackpixels = new ArrayList<Integer>();
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height; y++ )
{
int rgb = img.getRGB(x, y); // Get the pixel in question
int r = BlackRedImage.getRed(rgb);
int g = BlackRedImage.getGreen(rgb);
int b = BlackRedImage.getBlue(rgb);
if ( (r < 5) && (g < 5) && (b < 5) )
{ // record location of any "black" pixels found
blackpixels.add(x + (y * width));
}
finalImage.setRGB(x, y, rgb);
}
}
// Now loop through all "black" pixels, setting them to the colour found to their left
for ( int blackPixelLocation: blackpixels )
{
if ( blackPixelLocation % width == 0 )
{ // these pixels are on the left most edge, therefore they do not have a left neighbour!
continue;
}
int y = blackPixelLocation / width;
int x = blackPixelLocation - (width * y);
int rgb = img.getRGB(x - 1, y); // Get the pixel to the left of the "black" pixel
System.out.println("x = " + x + ", y = " + y + ", rgb = " + rgb);
finalImage.setRGB(x, y, rgb);
}
return finalImage;
}
private static int mixColor( int red, int g, int b )
{
return red << 16 | g << 8 | b;
}
public static int getRed( int rgb )
{
return (rgb & 0x00ff0000) >> 16;
}
public static int getGreen( int rgb )
{
return (rgb & 0x0000ff00) >> 8;
}
public static int getBlue( int rgb )
{
return (rgb & 0x000000ff) >> 0;
}
}
EDIT: Here is a simpler version (doesn't collect the black pixels)
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.*;
public class ColourMove
{
public static BufferedImage Threshold( BufferedImage img )
{
int width = img.getWidth();
int height = img.getHeight();
BufferedImage finalImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
for ( int x = 1; x < width; x++ ) // Start at 1 as the left most edge doesn't have a left neighbour
{
for ( int y = 0; y < height; y++ )
{
Color colour = new Color(img.getRGB(x, y));
int red = colour.getRed();
int green = colour.getGreen();
int blue = colour.getBlue();
if ( (red < 5) && (green < 5) && (blue < 5) )
{ // Encountered a "black" pixel, now replace it with it's left neighbour
finalImage.setRGB(x, y, img.getRGB(x - 1, y));
}
else
{ // Non-black pixel
finalImage.setRGB(x, y, colour.getRGB());
}
}
}
return finalImage;
}
}
HiI was wondering how to flip and image horizontally, for a practce task I was given a code that reads an image, inverting it to an image indicating it's brightness from 0-5, I had to flip an image.
This is my code of my reading an image and drawing it
public int[][] readImage(String url) throws IOException
{
// fetch the image
BufferedImage img = ImageIO.read(new URL(url));
// create the array to match the dimensions of the image
int width = img.getWidth();
int height = img.getHeight();
int[][] imageArray = new int[width][height];
// convert the pixels of the image into brightness values
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
// get the pixel at (x,y)
int rgb = img.getRGB(x,y);
Color c = new Color(rgb);
int red = c.getRed();
int green = c.getGreen();
int blue = c.getBlue();
// convert to greyscale
float[] hsb = Color.RGBtoHSB(red, green, blue, null);
int brightness = (int)Math.round(hsb[2] * (PIXEL_CHARS.length - 1));
imageArray[x][y] = brightness;
}
}
return imageArray;
}
public void draw() throws IOException
{
int[][] array = readImage("http://sfpl.org/images/graphics/chicklets/google-small.png");
for(int i=0; i<array.length; i++)
{
for(int pic=0; pic<array[i].length; pic++)
{
if(array[pic][i] == 0)
{
System.out.print("X");
}
else if(array[pic][i] == 1)
{
System.out.print("8");
}
else if(array[pic][i] == 2)
{
System.out.print("0");
}
else if(array[pic][i] == 3)
{
System.out.print(":");
}
else if(array[pic][i] == 4)
{
System.out.print(".");
}
else if (array[pic][i] == 5)
{
System.out.print(" ");
}
else
{
System.out.print("error");
break;
}
}
System.out.println();
}
}
and this is the code I tried to create to horizontally flip it,
void mirrorUpDown()
{
int[][] array = readImage("http://sfpl.org/images/graphics/chicklets/google-small.png");
int i = 0;
for (int x = 0; x < array.length; x++)
{
for (int y = 0; y < array[i].length; y++)
{{
int temp = array[x][y];
array[x][y]= array[-x][y];
array[array[i].length-x][y]=temp;
}
}
}
}
I get an error
unreported exception java.io.IException;
must be caught or declared to be thrown
I'd actually do it by this way...
BufferedImage flip(BufferedImage sprite){
BufferedImage img = new BufferedImage(sprite.getWidth(),sprite.getHeight(),BufferedImage.TYPE_INT_ARGB);
for(int xx = sprite.getWidth()-1;xx>0;xx--){
for(int yy = 0;yy < sprite.getHeight();yy++){
img.setRGB(sprite.getWidth()-xx, yy, sprite.getRGB(xx, yy));
}
}
return img;
}
Just a loop whose x starts at the end of the first image and places its rgba value on the flipped position of the second image. Clean, easy code :)
The function mirrorUpDown() , add a throws IOException there.
Also the function from which you are calling these methods, does that handle exception, does that code enclosed in a try catch block or the function is also set to throw IOException (one of either should be there)
How is your image supposed to know it should get it's data from imageArray ?
instead, you should access the raster of your image and modify the data in it.
void flip(BufferedImage image) {
WritableRaster raster = image.getRaster();
int h = raster.getHeight();
int w = raster.getWidth();
int x0 = raster.getMinX();
int y0 = raster.getMinY();
for (int x = x0; x < x0 + w; x++){
for (int y = y0; y < y0 + h / 2; y++){
int[] pix1 = new int[3];
pix1 = raster.getPixel(x, y, pix1);
int[] pix2 = new int[3];
pix2 = raster.getPixel(x, y0 + h - 1 - (y - y0), pix2);
raster.setPixel(x, y, pix2);
raster.setPixel(x, y0 + h - 1 - (y - y0), pix1);
}
}
return;
}
Sorry about posting this here over a year later but it should aid someone at a stage
try{
java.awt.image.BufferedImage bi = javax.imageio.ImageIO.read(getClass().getResource("Your image bro.jpg")) ;
int[] h = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), null, 0, bi.getWidth());
int [] h1 = new int[h.length];
System.out.println(""+h.length);
for(int j = 0;500>j;j++){
for(int i = 500;i>0;i--){
h1[j*500+(500-i)] = h[(j*500)+(i-1)];
}
}
bi.setRGB(0, 0, bi.getWidth(), bi.getHeight(), h1, 0, bi.getWidth());
}
catch(Exception e){e.printStackTrace();}
Lets break the code down
java.awt.image.BufferedImage bi =javax.imageio.ImageIO.read(getClass().getResource("Your image bro.jpg"));
Tries to read the image and stores the read image into the BufferedImage variable bi
int[] h = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), null, 0, bi.getWidth());
int [] h1 = new int[h.length];
instantiate two arrays, h is the original RGB Array and h1 will be the horizontally flipped RGB array.
for(int j = 0;500>j;j++){
for(int i = 500;i>0;i--){
h1[j*500+(500-i)] = h[(j*500)+(i-1)];
}
}
Lets look at something in particular more closely
h1[j*500+(500-i)] = h[(j*500)+(i-1)];
Images are scanned from position 0;0 to x.length;y.length
but it is scanned in a coninual array. Thus we use a psuedo-array to manipulate the flipping of the image. j*500 references the Y values and (500-i) references the x values.
bi.setRGB(0, 0, bi.getWidth(), bi.getHeight(), h1, 0, bi.getWidth());
Finally, the image gets stored back into the BufferedImage variable.
Note that the 500 constant is referencing your x resolution of the image. For example, 1920 x 1080 sized image uses a max value of 1920. The logic is yours to decide.
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);