I would like to know how to implement the following makeWhiteTransparent() below so that it takes a file and only makes the white pixels transparent in my existing PNG. That is ONLY the perfectly white pixels (#FFFFFF).
public static void main(String[] args)
{
File pngFile = new File(pathToPngFile);
File outputPngFile = new File(pathToOutputPngFile);
makeWhiteTransparent(pngFile, outputPngFile);
}
I even looked for open source libraries in addition to finding a bunch of responses here on SO but nothing seemed to work. That or the code was complex and unless you know what you're doing it was hard to understand (for example thresholds, etc.). I basically just want all #FFFFFF pixels in my PNG to be transparent.
It should be as simple as setting the Alpha channel's value to 0, if the rest of the channels are at 255
private static void makeWhiteTransparent(File in, File out)throws IOException{
BufferedImage bi = ImageIO.read(in);
int[] pixels = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), null, 0, bi.getWidth());
for(int i=0;i<pixels.length;i++){
int color = pixels[i];
int a = (color>>24)&255;
int r = (color>>16)&255;
int g = (color>>8)&255;
int b = (color)&255;
if(r == 255 && g == 255 && b == 255){
a = 0;
}
pixels[i] = (a<<24) | (r<<16) | (g<<8) | (b);
}
BufferedImage biOut = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB);
biOut.setRGB(0, 0, bi.getWidth(), bi.getHeight(), pixels, 0, bi.getWidth());
ImageIO.write(biOut, "png", out);
}
Related
I created this code in order to remove all semi-transparent colors from an image and make them fully opaque. For some reason, the colors of the image change drastically, even though im only changing the alpha. Attached is the code and an example of what happens to the image.
Before:
After:
public class Main {
public static void main(String args[]) throws IOException
{
File file = new File("karambitlore.png");
FileInputStream fis = new FileInputStream(file);
BufferedImage image = ImageIO.read(fis);
image = convertToType(image, BufferedImage.TYPE_INT_ARGB);
BufferedImage image2 = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
for (int width = 0; width < image.getWidth(); width++)
{
for (int height = 0; height < image.getHeight(); height++)
{
int rgb = image.getRGB(width, height);
boolean transparent = (rgb & 0xFF000000) == 0x0;
boolean opaque = (rgb & 0xFF000000) == 0xFF000000;
if (!transparent && !opaque)
{
rgb = rgb | 0xFF000000;
image2.setRGB(width, height, rgb);
} else
{
image2.setRGB(width, height, image.getRGB(width, height));
}
}
}
fis.close();
ImageIO.write(image2, "png", file);
System.out.println(image.getType());
}
public static BufferedImage convertToType(BufferedImage image, int type) {
BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), type);
Graphics2D graphics = newImage.createGraphics();
graphics.drawImage(image, 0, 0, null);
graphics.dispose();
return newImage;
}
}
The main problem with your input image is that it has a nearly-transparent "halo" around the fully transparent pixels, kind of like a "dithered" transparency. When you turn these pixels fully opaque, they a) create opaque pixels where you don't want them, and b) make these pixels way too saturated as the pixels are not premultiplied with alpha (you can google "premultiplied alpha" if you are interested in alpha compositing/blending). Note that the pixels don't really "change color", it's just that they normally don't contribute much to the end result as they are almost fully transparent.
The easy fix, that works ok regardless of background color, is just to use a threshold value to decide whether the pixel should be transparent.
If you know the background color (either solid or some "average") you can blend the pixel color with the background color, using the alpha value. This will create smoother edges, but ONLY against this background. You may use a lower threshold in this case.
Here's a modified version of your code that produces better results for your input:
public static void main(String args[]) throws IOException {
File file = new File(args[0]);
BufferedImage image = convertToType(ImageIO.read(file), BufferedImage.TYPE_INT_ARGB);
Color bg = Color.ORANGE; // Change to the color of your background, if you want to blend
int bgR = bg.getRed();
int bgG = bg.getGreen();
int bgB = bg.getBlue();
for (int width = 0; width < image.getWidth(); width++) {
for (int height = 0; height < image.getHeight(); height++) {
int rgb = image.getRGB(width, height);
int opaqueness = (rgb >>> 24) & 0xFF;
if (opaqueness > 100) { // The threshold 100 works fine for your current input, tune to suit other needs
// Fully opaque
image.setRGB(width, height, rgb | 0xFF000000);
// Or if you prefer blending the background:
/*
// Multiply alpha
int r = ((rgb >> 16 & 0xFF) * opaqueness) + (bgR * (0xFF - opaqueness)) >> 8;
int g = ((rgb >> 8 & 0xFF) * opaqueness) + (bgG * (0xFF - opaqueness)) >> 8;
int b = ((rgb & 0xFF) * opaqueness) + (bgB * (0xFF - opaqueness)) >> 8;
image.setRGB(width, height, r << 16 | g << 8 | b | 0xFF000000);
*/
} else {
// Fully transparent
image.setRGB(width, height, rgb & 0xFFFFFF);
}
}
}
ImageIO.write(image, "png", new File(file.getParentFile(), file.getName() + "_copy_bg_mult.png"));
}
public static BufferedImage convertToType(BufferedImage image, int type) {
BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), type);
Graphics2D graphics = newImage.createGraphics();
graphics.setComposite(AlphaComposite.Src);
graphics.drawImage(image, 0, 0, null);
graphics.dispose();
return newImage;
}
The code as-is will create an image like this, and it will have slightly jagged edges, but the "halo" is removed:
Or, if you comment out the "Fully opaque" version, and comment in the "Multiply alpha" section, you can get an image like this (which will obviously only look good against a yellow background):
I have a program that is supposed to take the RGB values of an image and then multiply them by some constants, and then draw the new image on a JPanel. The problem is that if my image is over a certain height, specifically over 187 pixels, the new colored image is different than an image with a height of less than 187px.
The JPanel shows this: example.
Notice how the longer recolored image is different than the shorter one. I'm sure that the shorter image's colors are correct, and I have no idea how it's getting messed up.
public class RecolorImage extends JPanel {
public static int scale = 3;
public static BufferedImage walk, walkRecolored;
public static BufferedImage shortWalk, shortWalkRecolored;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(200*scale, 400*scale);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new RecolorImage());
walk = ImageLoader.loadImage("/playerWalk.png");
walkRecolored = recolor(walk);
shortWalk = ImageLoader.loadImage("/playerWalkShort.png");
shortWalkRecolored = recolor(shortWalk);
frame.setVisible(true);
}
#Override
public void paint(Graphics graphics) {
Graphics2D g = (Graphics2D) graphics;
g.scale(scale, scale);
g.drawImage(walk, 10, 10, null);
g.drawImage(walkRecolored, 40, 10, null);
g.drawImage(shortWalk, 70, 10, null);
g.drawImage(shortWalkRecolored, 100, 10, null);
}
The recolor method:
public static BufferedImage recolor(BufferedImage image) {
BufferedImage outputImage = deepCopy(image);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int rgb = image.getRGB(x, y);
Color c = new Color(rgb);
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
r *= 0.791;
g *= 0.590;
b *= 0.513;
int newRGB = (rgb & 0xff000000) | (r << 16) | (g << 8) | b;
outputImage.setRGB(x, y, newRGB);
}
}
return outputImage;
}
How I load the images and make deep copies:
public static BufferedImage loadImage(String path) {
try {
return ImageIO.read(ImageLoader.class.getResource(path));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static BufferedImage deepCopy(BufferedImage image) {
ColorModel colorModel = image.getColorModel();
boolean isAlphaPremultiplied = colorModel.isAlphaPremultiplied();
WritableRaster raster = image.copyData(null);
return new BufferedImage(colorModel, raster, isAlphaPremultiplied, null);
}
My original images: the tall image and short image. Thanks for any help!
Your source images have different color models:
the short image uses 4 bytes per pixel (RGB and alpha)
the tall image uses 1 byte per pixel (index into a palette of 256 colors)
Your recolored images use the same color model as the source images (thanks to the deepCopy method), therefore the recolored image for the tall image also uses the same color palette as the source image, meaning that it cannot contain all the colors you want.
Since your recoloring code overwrites each pixel of the output image anyway the deep copy operation is unnecessary. Instead you would better create a full color image as target image like this:
public static BufferedImage recolor(BufferedImage image) {
BufferedImage outputImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
//... code as before
}
I'm trying to make a Mario game clone, and right now, in my constructor, I have a method that is supposed to make a certain color transparent instead of the current pinkish (R: 255, G: 0, B: 254). According to Photoshop, the hex value is ff00fe. My method is:
public Mario(){
this.state = MarioState.SMALL;
this.x = 54;
this.y = 806;
URL spriteAtLoc = getClass().getResource("sprites/Mario/SmallStandFaceRight.bmp");
try{
sprite = ImageIO.read(spriteAtLoc);
int width = sprite.getWidth();
int height = sprite.getHeight();
int[] pixels = new int[width * height];
sprite.getRGB(0, 0, width, height, pixels, 0, width);
for (int i = 0; i < pixels.length; i++) {
if (pixels[i] == 0xFFff00fe) {
pixels[i] = 0x00ff00fe; //this is supposed to set alpha value to 0 and make the target color transparent
}
}
} catch(IOException e){
System.out.println("sprite not found");
e.printStackTrace();
}
}
it runs and compiles, but sprite comes out exactly the same when I render it. (edit: perhaps of note I do not have super.paintComponent(g) in my paintComponent(g) method. The sprites are .bmps.
You are only retrieving the pixels using BufferedImage.getRGB. That returns a copy of the data in a certain area of the BufferedImage.
Any change you make to the int[] returned is not automatically reflected back into the image.
To update the image, you need to call BufferedImage.setRGB after you change the int[]:
sprite.setRGB(0, 0, width, height, pixels, 0, width);
Another change you should probably make (and this involves a little guesswork as I don't have your bmp to test with) - the BufferedImage returned by ImageIO.read may have type BufferedImage.TYPE_INT_RGB - meaning that it doesn't have an alpha channel. You can verify by printing sprite.getType(), if that prints 1 it's TYPE_INT_RGB without an alpha channel.
To get an alpha channel, create a new BufferedImage of the right size and then set the converted int[] on that image, then use the new image from then on:
BufferedImage newSprite = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
newSprite.setRGB(0, 0, width, height, pixels, 0, width);
sprite = newSprite; // Swap the old sprite for the new one with an alpha channel
BMP images don't provide an alpha channel, you have to set it manually (as you do in your code)...
when you check your pixel to have a certain color you have to check without alpha (BMP has no alpha it's always 0x0).
if (pixels[i] == 0x00ff00fe) { //THIS is the color WITHOUT alpha
pixels[i] = 0xFFff00fe; //set alpha to 0xFF to make this pixel transparent
}
so in short: you did all right but mixed it up a bit ^^
This works:
private BufferedImage switchColors(BufferedImage img) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
// top left pixel is presumed to be BG color
int rgb = img.getRGB(0, 0);
for (int xx=0; xx<w; xx++) {
for (int yy=0; yy<h; yy++) {
int rgb2 = img.getRGB(xx, yy);
if (rgb2!=rgb) {
bi.setRGB(xx, yy, rgb2);
}
}
}
return bi;
}
I am currently trying to read in an image pixel by pixel and change each colored pixel to the rgb value of (100,100,100). For whatever reason when I check the values of each pixel one the image is saved it has all the colored pixels as (46,46,46) instead.
Here is the original image
After running my program this is the image it gives to me
Here is the code
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Cmaps {
public static void main(String[] args){
File originalImage = new File("C:\\Users\\Me\\Desktop\\0005.bmp");
BufferedImage img = null;
try{
img = ImageIO.read(originalImage);
for(int i = 0; i < img.getHeight(); i++){
for(int j = 0; j < img.getWidth(); j++){
//get the rgb color of the image and store it
Color c = new Color(img.getRGB(i, j));
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
//if the pixel is white then leave it alone
if((r == 255) && (g == 255) && (b == 255)){
img.setRGB(i, j, c.getRGB());
continue;
}
//set the colored pixel to 100,100,100
r = 100;// red component 0...255
g = 100;// green component 0...255
b = 100;// blue component 0...255
int col = (r << 16) | (g << 8) | b;
img.setRGB(i, j, col);
}
}
File f = new File("C:\\Users\\Me\\Desktop\\2\\1.png");
try {
ImageIO.write(img, "PNG", f);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (IOException e){
e.printStackTrace();
}
}
}
I have no clue why it doesn't set the pixels to the expected rgb value. I eventually want to be able to basically increment the rgb color as I move down rows and columns in the x and y so what the final image will look like is it will start off dark in the top left corner and then have a fade out effect as you get from that side to the bottom right corner.
Okay, based on the comments:
If the BufferedImage has an IndexColorModel (palette based color model), using setRGB to set a pixel to an arbitrary RGB value will not work. Instead, the color will be looked up, and the pixel will get the color that is considered the closest match in the palette.
Formats like BMP, GIF and PNG may all use IndexColorModel when read using ImageIO.
To convert the image to "true color" (either DirectColorModel or ComponentColorModel in Java will do), you can use:
BufferedImage img; // your original palette image
int w = img.getWidth();
int h = img.getHeight();
BufferedImage trueColor = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = trueColor.createGraphics();
try {
g.drawImage(img, 0, 0, null);
}
finally {
g.dispose();
}
img = trueColor;
After this, getRGB(x, y) should return what you specify, using setRGB(x, y, argb).
I'm converting a image to gray scale in Java with the following code:
BufferedImage originalImage = ImageIO.read(new File("/home/david/input.bmp"));
BufferedImage grayImage = new BufferedImage(originalImage.getWidth()
, originalImage.getHeight()
, BufferedImage.TYPE_BYTE_GRAY);
ColorSpace gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
ColorConvertOp colorConvert = new ColorConvertOp(gray, null);
colorConvert.filter(originalImage, grayImage);
ImageIO.write(grayImage, "bmp", new File("/home/david/output_java.bmp"));
That seems to work, but the problem is that the output image is very different from the gray scale image generated by gimp (see examples below).
Can I control someway how is the image generated?
How I can make the result more similar to the gimp result?
Original image:
Gray scale image generated in Java:
Gray scale image generated in Gimp (Image -> Mode -> Grayscale):
BTW: I have a bunch of images coming from ffmpeg (with gray option) and they are like Gimp images so because of that I want my image in that way.
Finally I've wrote GrayscaleFilter class implementing BufferedImageOp interface.
I've followed this really good guide about Java image processing.
This is the relevant code fragment:
public class GrayscaleFilter extends AbstractFilter
{
public final static double[] METHOD_AVERAGE = {1.0/3.0, 1.0/3.0, 1.0/3.0};
public final static double[] METHOD_GIMP_LUMINOSITY = {0.21, 0.71, 0.07};
public GrayscaleFilter(final double[] rgb)
{
this(rgb[0], rgb[1], rgb[2]);
}
public BufferedImage filter(BufferedImage src, BufferedImage dest)
{
if (src.getType() == BufferedImage.TYPE_BYTE_GRAY)
{
dest = src;
return dest;
}
if (dest == null)
dest = createCompatibleDestImage(src, null);
final int width = src.getWidth();
final int height = src.getHeight();
int[] inPixels = new int[width * height];
GraphicsUtilities.getPixels(src, 0, 0, width, height, inPixels);
byte[] outPixels = doFilter(inPixels);
GraphicsUtilities.setPixels(dest, 0, 0, width, height, outPixels);
return dest;
}
private byte[] doFilter(int[] inputPixels)
{
int red, green, blue;
int i = 0;
byte[] outPixels = new byte[inputPixels.length];
for(int pixel : inputPixels)
{
// Obtengo valores originales
red = (pixel >> 16) & 0xFF;
green = (pixel >> 8) & 0xFF;
blue = pixel & 0xFF;
// Calculo valores nuevos
outPixels[i++] = (byte)(
red * red_part +
green * green_part +
blue * blue_part
);
}
return outPixels;
}
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM)
{
return new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
}
}
Find out the conversion formula used by Gimp. It probably takes some human color perception into account, while the Java implementation is mathematical (R+G+B)/ 3.