I want to dynamically create an image, and the created image must meet some requirements.
The created image should be a png, and it must have the same behavior as if it's a loaded png from a file.
It's for creating a texture to use in LWJGL.
When I load a png image as a file and have a BufferedImage, I can use the following code for my texture:
(The Texture constructor is designed for using with loaded images)
public class Texture {
public Texture(BufferedImage bi) {
width = bi.getWidth();
height = bi.getHeight();
System.out.println(bi.toString());
int[] pixels_raw = new int[width * height];
pixels_raw = bi.getRGB(0, 0, width, height, null, 0, width);
ByteBuffer pixels = BufferUtils.createByteBuffer(width * height * 4);
for(int i = 0; i < width; i++) {
for(int j = 0; j < height; j++) {
int pixel = pixels_raw[i * width + j]; // This is the error line.
pixels.put((byte)((pixel >> 16) & 0xFF)); // red
pixels.put((byte)((pixel >> 8) & 0xFF)); // green
pixels.put((byte)(pixel & 0xFF)); // blue
pixels.put((byte)((pixel >> 24) & 0xFF)); // alpha
}
}
pixels.flip();
id = glGenTextures();
glBindTexture(GL_TEXTURE_2D, id);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
}
But when I try to create an image dynamically, without loading anything from a file, then I get an ArrayIndexOutOfBoundsException on line 18 of the above code (see comment in code).
Of course it has something to do with the bits per pixel of the created BufferedImage. I tried changing the image type for my BufferedImage, and changing the array size when initializing the pixels_raw array. But I still get array exceptions. So, the above constructor method does only works when I pass a BufferedImage instance which comes from a loaded png. When I pass in a BurfferedImage I created dynamically with the code below, it gives me the exceptions I mentioned before.
public class TextDrawer {
public BufferedImage drawText(String text, Font font, Color color) {
BufferedImage graphicsGetterBi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics g = graphicsGetterBi.getGraphics();
Graphics2D g2 = (Graphics2D) g;
Rectangle2D bounds = font.getStringBounds(text, 0, text.length(), g2.getFontRenderContext());
BufferedImage bi = new BufferedImage((int) bounds.getWidth(), (int) bounds.getHeight(), BufferedImage.TYPE_INT_ARGB);
System.out.println("Created the image. \n");
g2.setColor(color);
g2.setFont(font);
g2.drawString(text, 0, 0);
return bi;
}
}
instead of int pixel = pixels_raw[i * width + j]; it should be int pixel = pixels_raw[i * height + j]; or int pixel = pixels_raw[j * width + i];. Consider you have image of width = 2x and height = x. Then the array size is 2x^2, while the maximum index you request for is (2x - 1) * 2x + x - 1 = 4x^2 - x - 1, which is more than 2x^2 for x > 2
Related
I have a requirement to create a BMP image (black & white) from the hex string created through Excel Macro. I have next to zero experience working on this and need some help. Below is the Hex String, output image example of the hex, and little code snippet that I am trying :
String:
001E00FE07FE3FE03C003C003F801FFE03FE007E3E003FE03FFC0FFE0E3E0FFE3FFC3FE03E0000000FF83FFC3FFE300630063006380E180C00003E003FE03FFC0FFE0E3E0FFE3FFC3FE03E00000000003FFE3FFE3FFE01F807F01FC03FFE3FFE3FFE0000000E000E000E3FFE3FFE3FFE000E000E000E0000
Image to be created of the above string:
Code Snippet :
public void createImageFromHex() throws IOException {
String hex="001E00FE07FE3FE03C003C003F801FFE03FE007E3E003FE03FFC0FFE0E3E0FFE3FFC3FE03E0000000FF83FFC3FFE300630063006380E180C00003E003FE03FFC0FFE0E3E0FFE3FFC3FE03E00000000003FFE3FFE3FFE01F807F01FC03FFE3FFE3FFE0000000E000E000E3FFE3FFE3FFE000E000E000E0000";
byte[] imageInByte= ByteString.decodeHex(hex).toByteArray();
for (byte b : imageInByte) {
System.out.println("Byte : " + b);
}
InputStream in = new ByteArrayInputStream(imageInByte);
Font font = new Font("Arial", Font.PLAIN, 12);
BufferedImage img = new BufferedImage(1, 1, BufferedImage.BITMASK);
Graphics2D g2d = img.createGraphics();
FontMetrics fm = g2d.getFontMetrics(font);
g2d.dispose();
int width = fm.stringWidth(hex);
int height = fm.getHeight();
img = new BufferedImage(width, height, BufferedImage.BITMASK);
g2d = img.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.BLACK);
g2d.setFont(font);
g2d.drawString(hex, 0, fm.getAscent());
g2d.dispose();
try {
ImageIO.write(img, "bmp", new File("Hex.bmp"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
The clue here is the format of the hex/binary data. You need to know this from some kind of specification, from the device/software/service that produces the hex string. From the data and sample image, I believe #tgdavies has correctly reverse-engineered it.
Each pixel is one bit. The white pixels are stored as 0, black as 1. Each column of pixels is stored as two bytes. Each column is stored bottom-up.
Assuming that format is correct, it's quite trivial to write code to convert it:
public void createImageFromHex() throws IOException {
// Assumption: hex contains a bitmap format, where
// * each pair of bytes is one *column*
// * each column is stored "bottom-up" (LSB is bottom pixel, MSB is top pixel)
String hex = "001E00FE07FE3FE03C003C003F801FFE03FE007E3E003FE03FFC0FFE0E3E0FFE3FFC3FE03E0000000FF83FFC3FFE300630063006380E180C00003E003FE03FFC0FFE0E3E0FFE3FFC3FE03E00000000003FFE3FFE3FFE01F807F01FC03FFE3FFE3FFE0000000E000E000E3FFE3FFE3FFE000E000E000E0000";
// (result is 120 bytes)
byte[] imageInByte = ByteString.decodeHex(hex).toByteArray();
int height = 16; // 16 according to sample
int width = imageInByte.length / 2; // 60 according to the sample, but as we know each column is 16 bits or 2 bytes, we can calculate it
// Create a BufferedImage of correct type: For a bitmap this is TYPE_BYTE_BINARY
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
// Loop over pixels and insert black or white
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int pos = height * x + y;
int bytePos = pos / 8;
int bitPos = 7 - (pos % 8);
byte value = imageInByte[bytePos];
int bit = (value >> bitPos) & 1;
// Each pixel is either all white (0) or all black (1)
int rgb = bit != 0 ? 0x000000 : 0xFFFFFF;
img.setRGB(x, height - 1 - y, rgb);
}
}
// Write to BMP format
File output = new File("Hex.bmp");
if (!ImageIO.write(img, "bmp", output)) {
System.err.printf("Could not write %s in BMP format...%n", output.getAbsolutePath());
}
}
Result file:
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'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 making a game with LWJGL and by using openGL, I believe my best option is to use Textures and render them with quads. However, I can only seem to find information on loading a texture from an image where the entire image is only ONE texture. What I would like to do is read an entire spritesheet in and be able to separate it into different textures. Is there a somewhat simple way to do this?
You could load the image, from e.g. a .png file to a BufferedImage with
public static BufferedImage loadImage(String location)
{
try {
BufferedImage image = ImageIO.read(new File(location));
return image;
} catch (IOException e) {
System.out.println("Could not load texture: " + location);
}
return null;
}
Now you are able to call getSubimage(int x, int y, int w, int h) on that resulting BufferedImage, giving you the seperated part. You now just need to create a Texture of the BufferedImage. This code should do the work:
public static int loadTexture(BufferedImage image){
if (image == null) {
return 0;
}
int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * BYTES_PER_PIXEL); //4 for RGBA, 3 for RGB
for(int y = 0; y < image.getHeight(); y++){
for(int x = 0; x < image.getWidth(); x++){
int pixel = pixels[y * image.getWidth() + x];
buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component
buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component
buffer.put((byte) (pixel & 0xFF)); // Blue component
buffer.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component. Only for RGBA
}
}
buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS
// You now have a ByteBuffer filled with the color data of each pixel.
// Now just create a texture ID and bind it. Then you can load it using
// whatever OpenGL method you want, for example:
int textureID = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureID);
//setup wrap mode
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
//setup texture scaling filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//Send texel data to OpenGL
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); //GL_RGBA8 was GL_RGB8A
return textureID;
}
You are now able to bind the returned textureID with glBindTexture(GL_TEXTURE_2D, textureID); if you need the texture.
This way you only have to split the BufferedImage in the desired parts.
I recommend reading this: LWJGL Textures and Strings
I have two BufferedImages I loaded in from pngs. The first contains an image, the second an alpha mask for the image.
I want to create a combined image from the two, by applying the alpha mask. My google-fu fails me.
I know how to load/save the images, I just need the bit where I go from two BufferedImages to one BufferedImage with the right alpha channel.
I'm too late with this answer, but maybe it is of use for someone anyway. This is a simpler and more efficient version of Michael Myers' method:
public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask)
{
int width = image.getWidth();
int height = image.getHeight();
int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);
for (int i = 0; i < imagePixels.length; i++)
{
int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha
int alpha = maskPixels[i] << 24; // Shift blue to alpha
imagePixels[i] = color | alpha;
}
image.setRGB(0, 0, width, height, imagePixels, 0, width);
}
It reads all the pixels into an array at the beginning, thus requiring only one for-loop. Also, it directly shifts the blue byte to the alpha (of the mask color), instead of first masking the red byte and then shifting it.
Like the other methods, it assumes both images have the same dimensions.
I played recently a bit with this stuff, to display an image over another one, and to fade an image to gray.
Also masking an image with a mask with transparency (my previous version of this message!).
I took my little test program and tweaked it a bit to get the wanted result.
Here are the relevant bits:
TestMask() throws IOException
{
m_images = new BufferedImage[3];
m_images[0] = ImageIO.read(new File("E:/Documents/images/map.png"));
m_images[1] = ImageIO.read(new File("E:/Documents/images/mapMask3.png"));
Image transpImg = TransformGrayToTransparency(m_images[1]);
m_images[2] = ApplyTransparency(m_images[0], transpImg);
}
private Image TransformGrayToTransparency(BufferedImage image)
{
ImageFilter filter = new RGBImageFilter()
{
public final int filterRGB(int x, int y, int rgb)
{
return (rgb << 8) & 0xFF000000;
}
};
ImageProducer ip = new FilteredImageSource(image.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(ip);
}
private BufferedImage ApplyTransparency(BufferedImage image, Image mask)
{
BufferedImage dest = new BufferedImage(
image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = dest.createGraphics();
g2.drawImage(image, 0, 0, null);
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.DST_IN, 1.0F);
g2.setComposite(ac);
g2.drawImage(mask, 0, 0, null);
g2.dispose();
return dest;
}
The remainder just display the images in a little Swing panel.
Note that the mask image is gray levels, black becoming full transparency, white becoming full opaque.
Although you have resolved your problem, I though I could share my take on it. It uses a slightly more Java-ish method, using standard classes to process/filter images.
Actually, my method uses a bit more memory (making an additional image) and I am not sure it is faster (measuring respective performances could be interesting), but it is slightly more abstract.
At least, you have choice! :-)
Your solution could be improved by fetching the RGB data more than one pixel at a time(see http://java.sun.com/javase/6/docs/api/java/awt/image/BufferedImage.html), and by not creating three Color objects on every iteration of the inner loop.
final int width = image.getWidth();
int[] imgData = new int[width];
int[] maskData = new int[width];
for (int y = 0; y < image.getHeight(); y++) {
// fetch a line of data from each image
image.getRGB(0, y, width, 1, imgData, 0, 1);
mask.getRGB(0, y, width, 1, maskData, 0, 1);
// apply the mask
for (int x = 0; x < width; x++) {
int color = imgData[x] & 0x00FFFFFF; // mask away any alpha present
int maskColor = (maskData[x] & 0x00FF0000) << 8; // shift red into alpha bits
color |= maskColor;
imgData[x] = color;
}
// replace the data
image.setRGB(0, y, width, 1, imgData, 0, 1);
}
For those who are using alpha in the original image.
I wrote this code in Koltin, the key point here is that if you have the alpha on your original image you need to multiply these channels.
Koltin Version:
val width = this.width
val imgData = IntArray(width)
val maskData = IntArray(width)
for(y in 0..(this.height - 1)) {
this.getRGB(0, y, width, 1, imgData, 0, 1)
mask.getRGB(0, y, width, 1, maskData, 0, 1)
for (x in 0..(this.width - 1)) {
val maskAlpha = (maskData[x] and 0x000000FF)/ 255f
val imageAlpha = ((imgData[x] shr 24) and 0x000000FF) / 255f
val rgb = imgData[x] and 0x00FFFFFF
val alpha = ((maskAlpha * imageAlpha) * 255).toInt() shl 24
imgData[x] = rgb or alpha
}
this.setRGB(0, y, width, 1, imgData, 0, 1)
}
Java version (just translated from Kotlin)
int width = image.getWidth();
int[] imgData = new int[width];
int[] maskData = new int[width];
for (int y = 0; y < image.getHeight(); y ++) {
image.getRGB(0, y, width, 1, imgData, 0, 1);
mask.getRGB(0, y, width, 1, maskData, 0, 1);
for (int x = 0; x < image.getWidth(); x ++) {
//Normalize (0 - 1)
float maskAlpha = (maskData[x] & 0x000000FF)/ 255f;
float imageAlpha = ((imgData[x] >> 24) & 0x000000FF) / 255f;
//Image without alpha channel
int rgb = imgData[x] & 0x00FFFFFF;
//Multiplied alpha
int alpha = ((int) ((maskAlpha * imageAlpha) * 255)) << 24;
//Add alpha to image
imgData[x] = rgb | alpha;
}
image.setRGB(0, y, width, 1, imgData, 0, 1);
}
Actually, I've figured it out. This is probably not a fast way of doing it, but it works:
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
Color c = new Color(image.getRGB(x, y));
Color maskC = new Color(mask.getRGB(x, y));
Color maskedColor = new Color(c.getRed(), c.getGreen(), c.getBlue(),
maskC.getRed());
resultImg.setRGB(x, y, maskedColor.getRGB());
}
}