Replacing image pixel in Java2D - java

I am trying to replace some pixels from my source image(PNG format). But i am end up with some confusing result. Basically i am replacing a particular RGB values with black and white colors. Here is my code,
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ChangePixel
{
public static void main(String args[]) throws IOException
{
File file = new File(System.getProperty("user.dir"), "D4635.png");
FileInputStream fis = new FileInputStream(file);
BufferedImage image = ImageIO.read(fis);
int[] replaceColors = new int[2];
replaceColors[0] = Color.BLACK.getRGB();
replaceColors[1] = Color.WHITE.getRGB();
Color src = new Color(133, 101, 51);
int srcRGBvalue = src.getRGB();
changeAlg2(image, srcRGBvalue, replaceColors);
}
private static void changeAlg2(BufferedImage image, int srcRGBvalue, int[] replaceColors) throws IOException
{
for (int width = 0; width < image.getWidth(); width++)
{
for (int height = 0; height < image.getHeight(); height++)
{
if (image.getRGB(width, height) == srcRGBvalue)
{
image.setRGB(width, height, ((width + height) % 2 == 0 ? replaceColors[0] : replaceColors[1]));
}
}
}
File file = new File(System.getProperty("user.dir"), "107.png");
ImageIO.write(image, "png", file);
}
}
It changes my source pixels to black and some other color, instead of white. Please advice me, what's going wrong here.
Since this is my first post, I can't able to attach my images. Sorry for the inconvenience.
Edit: I have uploaded the source and the output images in a site. Here is the URL,
Source : http://s20.postimage.org/d7zdt7kwt/D4635.png
Output : http://s20.postimage.org/kdr4vntzx/107.png
Expected output : After the black pixel, white pixel has to come.
Edit : Resolved code as per Jan Dvorak advice,
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ChangePixel
{
public static void main(String args[]) throws IOException
{
File file = new File(System.getProperty("user.dir"), "D12014.gif");
FileInputStream fis = new FileInputStream(file);
BufferedImage image = ImageIO.read(fis);
Color src = new Color(223, 170, 66);
int srcRGBvalue = src.getRGB();
int[] replaceColors = new int[2];
replaceColors[0] = Color.MAGENTA.getRGB();
replaceColors[1] = Color.CYAN.getRGB();
changeAlg2(image, srcRGBvalue, replaceColors);
}
private static void changeAlg2(BufferedImage image, int srcRGBvalue, int[] replaceColors) throws IOException
{
BufferedImage image2 = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
for (int width = 0; width < image.getWidth(); width++)
{
for (int height = 0; height < image.getHeight(); height++)
{
if (image.getRGB(width, height) == srcRGBvalue)
{
image2.setRGB(width, height, ((width + height) % 2 == 0 ? replaceColors[0] : replaceColors[1]));
}
else
{
image2.setRGB(width, height, image.getRGB(width, height));
}
}
}
File file = new File(System.getProperty("user.dir"), "110.gif");
ImageIO.write(image2, "gif", file);
}
}
Regards
Raja.

Since you are adding colors that are not present in the original image palette, the pixels you are trying to set are clipped to the nearest color in the palette. You need to set a new color mode. Either convert to 24bpp RGB (true-color) or extend the palette with the new colors.
It doesn't seem to be possible to modify an existing BufferedImage ColorModel or assign a new one, but you can create a new buffer and copy the data there. Creating a new BufferedImage with the same Raster might work as well (only if the bit depth does not change?).
If you don't mind, you can always create a True-color image. try:
{
BufferedImage old = image;
image = new BufferedImage(
old.getWidth(),
old.getHeight(),
BufferedImage.TYPE_INT_RGB
);
image.setData(old.getRaster());
} // old is no longer needed
API reference: http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/image/BufferedImage.html
You could try to detect if the image is already in true-color (image.getColorModel() instanceof ???) to avoid having to copy the buffer when not needed.
You could try to extend the existing palette. If that is not possible (there is no palette to start with or there is not enough space), you have to fallback to RGB.
See:
http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/image/BufferedImage.html (getColorModel and the constructor taking a ColorModel and type)
http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/image/IndexColorModel.html (getMapSize, getRGBs and the corresponding constructor)
From seeing your actual palette, you'll need some sort of deduplication logic because your palette is already 256 bytes - the maximum size of a PNG palette. Note that you should not save the image with larger palette than there are colors in the image (especially when you want to add new colors later). your original file could have been saved with a 2-color palette, saving 762 bytes.
Note that you don't gain much from storing the image as indexed as opposed to true-color with the same number of colors. The reason is that the byte stream (palette = 1 byte per pixel, true-color = 3 or 4 bytes per pixel) is losslessly compressed (with DEFLATE) anyways. Indexed can save you a few bytes (or lose you a few bytes, if the palette is big), but it won't reduce the file size to one third.

Related

Why is the color of my image changed after writing it as a jpg file?

I'm currently making a method that converts a ppm file to a jpg, png, and bmp file. The way I did it is reading the content of a ppm file, creating a BufferedImage, and assigning each pixel from the ppm file to the corresponding pixel in the BufferedImage. My bmp and png files look correct. However, the jpg file looks completely different.
Below is my code:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import javax.imageio.ImageIO;
public class readPPMOutputOthers {
public static void main(String[] args) throws InterruptedException {
// read a ppm file
Scanner sc;
// if the file is not found, it will throw an exception
try {
sc = new Scanner(new FileInputStream("res/test2.ppm"));
} catch (FileNotFoundException e) {
throw new IllegalArgumentException("File not found!");
}
// the file now is a StringBuilder
// read line by line to get information
StringBuilder builder = new StringBuilder();
while (sc.hasNextLine()) {
String s = sc.nextLine();
// ignore comment #
if (s.charAt(0) != '#') {
builder.append(s).append(System.lineSeparator());
}
}
sc = new Scanner(builder.toString());
String token;
token = sc.next();
// set the fields
// initial load image
int width = sc.nextInt();
int height = sc.nextInt();
int maxValue = sc.nextInt();
List<Integer> pixels = new ArrayList<>();
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int r = sc.nextInt();
int g = sc.nextInt();
int b = sc.nextInt();
int rgb = r;
rgb = (rgb << 8) + g;
rgb = (rgb << 8) + b;
pixels.add(rgb);
}
}
// make a BufferedImage from pixels
BufferedImage outputImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int[] outputImagePixelData = ((DataBufferInt) outputImg.getRaster().getDataBuffer()).getData();
for (int i = 0; i < pixels.size(); i++) {
outputImagePixelData[i] = pixels.get(i);
}
try {
ImageIO.write(outputImg, "png",
new File("res/test.png"));
ImageIO.write(outputImg, "jpg",
new File("res/test2.jpg"));
ImageIO.write(outputImg, "bmp",
new File("res/test.bmp"));
} catch (IOException e) {
System.out.println("Exception occurred :" + e.getMessage());
}
System.out.println("Images were written successfully.");
}
}
images comparison
The weird thing is it works for a very large image but not for this small image. I need to make it work for such small images because of testing. I've been digging posts about this on google and still didn't find a way to solve this. Any help would be appreciated!
The reason for the strange colors is YUV420 chroma subsumpling used by JPEG encoding.
In YUV420 every 2x2 pixels have the same chroma information (the 2x2 pixels have the same color).
The 2x2 pixels have the same color, but each pixel has different luminance (brighness).
The YUV420 Chroma subsumpling is demonstrated in Wikipedia:
And in our case:
becomes
The brown color is a mixture of the original red, cyan magenta and the yellow colors (the brown color is "shared" by the 4 pixels).
Note:
Chroma subsumpling is not considered as "compression", is the sense that it not performed as part of the JPEG compression stage.
We can't control the chroma subsumpling by setting the compression quality parameter.
Chroma subsumpling is referred as part of the "color format conversion" pre-processing stage - converting from RGB to YUV420 color format.
The commonly used JPEG color format is YUV420, but JPEG standard does support YUV444 Chroma subsumpling.
GIMP manages to save JPEG images with YUV444 Chroma subsumpling.
Example (2x2 image):
Too small: Enlarged:
I couldn't find an example for saving YUV444 JPEG in JAVA...
To some degree the effect you describe is to be expected.
From https://en.wikipedia.org/wiki/JPEG:
JPEG is a commonly used method of lossy compression for digital
images, particularly for those images produced by digital photography.
The degree of compression can be adjusted, allowing a selectable
tradeoff between storage size and image quality. JPEG typically
achieves 10:1 compression with little perceptible loss in image
quality.
Maybe when storing small files you can set the compression to be low and thus increase quality. See Setting jpg compression level with ImageIO in Java

Manipulating the Alpha value of BufferedImage

In the code i'm setting the alpha value of a pixel to 100 for entire image and I want the Alpha value to be 100 while reading the image. But at the retrieving part it gives me 255(Default Value) . What is wrong ? and how to solve it ? Any Help would be appreciated...
class Demo
{
Demo()
{
try
{
BufferedImage im2 = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
File f2 = new File("test2.jpg");
im2 = ImageIO.read(f2);
int width1 = im2.getWidth();
int height1 = im2.getHeight();
for(int i=0;i<height1;i++)
{
for(int j=0;j<width1;j++)
{
Color c = new Color(50,0,0,100); //Set the alpha value to 100
im2.setRGB(j,i,c.getRGB()); // for every pixel
}
}
File f = new File("Demo_copy.jpg");
ImageIO.write(im2,"jpg",f);
// Retrieving.........
BufferedImage im1;
File f1 = new File("Demo_copy.jpg");
im1 = ImageIO.read(f1);
int width = im1.getWidth();
int height = im1.getHeight();
for(int i=0;i<height;i++)
{
for(int j=0;j<width;j++)
{
int pixel = im1.getRGB(j,i);
Color c = new Color(pixel,true);
int a = c.getAlpha();
System.out.println("Alpha value is :"+a); // Printing Alpha : 255 for every pixel
}
}
}catch(Exception e){}
}
public static void main(String [] ar)
{
new Demo();
}
}
The new BufferedImage(...) you assign to im2 is just thrown away (garbage collected) after you assign a new value from ImageIO.read(..). As the new value is a JPEG and doesn't have alpha, it does not matter what alpha values you set. They will always stay 255 (completely opaque).
Instead, you probably want to do something like this:
// Read opaque image...
BufferedImage img = ImageIO.read(new File("test2.jpg"));
// ...convert image to TYPE_INT_ARGB...
BufferedImage im2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = im2.createGraphics();
try {
g.drawImage(img, 0, 0, null);
}
finally {
g.dispose();
}
// ... loop over and change alpha in im2 as before
Finally, you should write the image in a format that supports lossless alpha, like PNG instead of JPEG, to be sure you get the values you expect:
ImageIO.write(im2,"PNG", new File("Demo_copy.png"));
PS: It might just work using JPEG too, as the built-in Java ImageIO JPEG plugin supports reading/writing JPEGs with alpha values. However, most other software will misinterpret these as CMYK JPEGs, and the colors will look all wrong. Also, JPEG is lossy, so you will most likely not see the exact alpha value (100) as you would expect on the receiving end. That's why I suggest using PNG. TIFF or other format that supports alpha would also work, but requires extra plugins.

Java AlphaComposite blending massive quality loss

I'm working with timelapse photography and was trying to implement a script to smooth out the transitions by blending multiple photos to one frame. Like photos 1 through 30 yielding frame 1, photos 2 through 31 yielding frame 2 and so on.
When I tried merging 30 consecutive photographs with the GIMP the result was super smooth; when I implement the same logic with Java BufferedImages and AlphaComposite I'm getting what seem to be unacceptable compression artifacts.
I already tried different things, like replacing ImageIO.write() with a compression free ImageWriter, but the result is almost identical. Now I'm out of ideas. Could it be that the AlphaComposite blending causes the quality loss?
GIMP generated image
Java generated image
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
public class SimplePhotoBlender {
public static void main(String[] args) throws IOException {
if (args.length < 1)
System.out.println("Usage: java SimplePhotoBlender <PATH_TO_IMAGE_FOLDER>");
else
new SimplePhotoBlender(args[0]);
}
public SimplePhotoBlender(String path) throws IOException {
// Get all images in folder
List<File> fileList = getFilesInFolder(new File(path));
Collections.sort(fileList);
// Iterate through the list ...
for (int i = 0; i < fileList.size(); i++) {
File originalImage = fileList.get(i);
BufferedImage blended = ImageIO.read(originalImage);
// ... and blend it with the following 30 images.
for (int j = 1; j <= 30 && i + j < fileList.size() - 1; j++)
blended = blend(blended, ImageIO.read(fileList.get(i + j)), 0.9);
// Finally, save it in a new file.
File blendedFile = new File(originalImage.getParent() + "/blended/" + originalImage.getName());
blendedFile.getParentFile().mkdirs();
blendedFile.createNewFile();
// I double-checked but the JPG compression in ImageIO.write()
// doesn't account for the tremendous amount of quality loss.
ImageIO.write(blended, "jpg", blendedFile);
}
}
// Needless to say that I copied this from the sources like
// http://www.informit.com/articles/article.aspx?p=1245201&seqNum=2
private BufferedImage blend(BufferedImage bi1, BufferedImage bi2, double weight) {
BufferedImage bi3 = new BufferedImage(bi1.getWidth(), bi1.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bi3.createGraphics();
g2d.drawImage(bi1, null, 0, 0);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) (1.0 - weight)));
g2d.drawImage(bi2, null, 0, 0);
g2d.dispose();
return bi3;
}
public List<File> getFilesInFolder(final File folder) {
List<File> fileNames = new ArrayList<>();
for (final File fileEntry : folder.listFiles())
if (!fileEntry.isDirectory())
fileNames.add(fileEntry);
return fileNames;
}
}

Getting different values from getRGB and setRGB after XORing two images

I want to XOR two images pixel by pixel. I am using the following code.
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.*;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.Scanner;
import java.security.*;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
public class sefi
{
public static void main(String[] args) throws Exception
{
encdec ed = new encdec();
String plainimagename = args[0];
String keyfilename = args[1];
String choice = args[2];
BufferedImage bikey = ImageIO.read(new File(keyfilename));
BufferedImage biplain = ImageIO.read(new File(plainimagename));
BufferedImage resizedImage = new BufferedImage(biplain.getWidth(), biplain.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(bikey, 0, 0, biplain.getWidth(), biplain.getHeight(), null);
g.dispose();
ImageIO.write(resizedImage, "jpg", new File("resizeimage.jpg"));
if(choice.equals("enc"))
{
ed.encrypt(resizedImage,biplain);
}
else if(choice.equals("dec"))
{
ed.decrypt(resizedImage,biplain);
}
}
}
class encdec
{
public void encrypt(BufferedImage bikey, BufferedImage biplain) throws Exception
{
BufferedImage xoredimage = xor(bikey, biplain);
File xored = new File("xored.jpg");
ImageIO.write(xoredimage, "JPEG", xored);
}
public void decrypt(BufferedImage bikey, BufferedImage biplain) throws Exception
{
BufferedImage xoredimage = xor(bikey, biplain);
File xored = new File("newplain.jpg");
ImageIO.write(xoredimage, "JPEG", xored);
}
private BufferedImage xor(BufferedImage image1, BufferedImage image2) throws Exception
{
BufferedImage outputimage = new BufferedImage(image1.getWidth(), image1.getHeight(), BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < image1.getHeight(); y++)
{
for (int x = 0; x < image1.getWidth(); x++)
{
outputimage.setRGB(x,y,((image1.getRGB(x, y))^(image2.getRGB(x, y))));
System.out.println("one:" + image1.getRGB(x, y) + "\ttwo:" + image2.getRGB(x, y) + "\txor:" + ((image1.getRGB(x, y))^(image2.getRGB(x, y))));
System.out.println("one:" + Integer.toBinaryString(image1.getRGB(x, y)) + "\ttwo:" + Integer.toBinaryString(image2.getRGB(x, y)) + "\txor:" + Integer.toBinaryString((image1.getRGB(x, y)^image2.getRGB(x, y))));
}
}
return outputimage;
}
}
First time I run this code where image1 is a 4-pixel colored image and image2 is a 4-pixel white image, I get the output as:
input: #java sefi white.jpg key.jpg enc
one:-201053 two:-1 xor:201052
one:11111111111111001110111010100011 two:11111111111111111111111111111111 xor:110001000101011100
one:-265579 two:-1 xor:265578
one:11111111111110111111001010010101 two:11111111111111111111111111111111 xor:1000000110101101010
one:-664247 two:-1 xor:664246
one:11111111111101011101110101001001 two:11111111111111111111111111111111 xor:10100010001010110110
one:-925624 two:-1 xor:925623
one:11111111111100011110000001001000 two:11111111111111111111111111111111 xor:11100001111110110111
Next time I run with image1 as the xored image file and image 2 as the 4-pixel colored file, which should give me the original white image as output. But I get this as output instead:
Input:#java sefi xored.jpg key.jpg dec
one:-1 two:-16773753 xor:16773752
one:11111111111111111111111111111111 two:11111111000000000000110110000111 xor:111111111111001001111000
one:-1 two:-16773753 xor:16773752
one:11111111111111111111111111111111 two:11111111000000000000110110000111 xor:111111111111001001111000
one:-1 two:-15786601 xor:15786600
one:11111111111111111111111111111111 two:11111111000011110001110110010111 xor:111100001110001001101000
one:-1 two:-15786601 xor:15786600
one:11111111111111111111111111111111 two:11111111000011110001110110010111 xor:111100001110001001101000
If you look at the output we can see that the colors of the xored image from first time has changed.
I am not able to understand why I am getting different color value for the same image file.
There's something wrong with your image selection. If you were selecting the RGB for the generated image then the first pixel would be 110001000101011100 and not 11111111000000000000110110000111.
So my advice is for you to check if you are using the correct images on the second step.
Your code looks ok, although you'd have to paste the whole code for me to have a better idea.
Found the Answer.
It is because I am using JPEG images. JPEG compresses raw image, but when it decompress it does not guarantee to produce the exact same colors as it was before compression. Thus the different color values before and after.
When I used bmp images, I got the same colors before and after.

Writing ico files java

Recently i have become interested in creating .ico file or windows icon files in java. This is the current code i use. I have gotten the file format specs from here http://en.wikipedia.org/wiki/ICO_%28file_format%29
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
g.setColor(Color.GREEN);
g.fillRect(0, 0, 16, 16);
byte[] imgBytes = getImgBytes(img);
int fileSize = imgBytes.length + 22;
ByteBuffer bytes = ByteBuffer.allocate(fileSize);
bytes.order(ByteOrder.LITTLE_ENDIAN);
bytes.putShort((short) 0);//Reserved must be 0
bytes.putShort((short) 1);//Image type
bytes.putShort((short) 1);//Number of image in file
bytes.put((byte) img.getWidth());//image width
bytes.put((byte) img.getHeight());//image height
bytes.put((byte) 0);//number of colors in color palette
bytes.put((byte) 0);//reserved must be 0
bytes.putShort((short) 0);//color planes
bytes.putShort((short) 0);//bits per pixel
bytes.putInt(imgBytes.length);//image size
bytes.putInt(22);//image offset
bytes.put(imgBytes);
byte[] result = bytes.array();
FileOutputStream fos = new FileOutputStream("C://Users//Owner//Desktop//picture.ico");
fos.write(result);
fos.close();
fos.flush();
private static byte[] getImgBytes(BufferedImage img) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(img, "png", bos);
return bos.toByteArray();
}
The problem is that windows doesn't seem to be able to open the image, giving an error when i try to open the image using Windows Photo Gallery. However when i try to open the image using gimp the image opens fine. What am i doing wrong. I feel like i am messing up something in the file header. Edit: Even stranger on the desktop the picture looks right, just not when i try to open it.
On my desktop the image looks like this
When i try to open it in Windows Photo Gallery it displays this error
After having failed with the png attempt i tried it with bitmap image instead, here is my new code
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import javax.imageio.ImageIO;
public class IconWriter
{
public static void main(String[] args) throws HeadlessException, AWTException, IOException
{
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
g.setColor(Color.GREEN);
g.fillRect(0, 0, 16, 16);
byte[] imgBytes = getImgBytes(img);
int fileSize = imgBytes.length + 22;
ByteBuffer bytes = ByteBuffer.allocate(fileSize);
bytes.order(ByteOrder.LITTLE_ENDIAN);
bytes.putShort((short) 0);//Reserved must be 0
bytes.putShort((short) 1);//Image type
bytes.putShort((short) 1);//Number of images in file
bytes.put((byte) img.getWidth());//image width
bytes.put((byte) img.getHeight());//image height
bytes.put((byte) 0);//number of colors in color palette
bytes.put((byte) 0);//reserved must be 0
bytes.putShort((short) 0);//color planes
bytes.putShort((short) 0);//bits per pixel
bytes.putInt(imgBytes.length);//image size
bytes.putInt(22);//image offset
bytes.put(imgBytes);
byte[] result = bytes.array();
FileOutputStream fos = new FileOutputStream("C://Users//Owner//Desktop//hi.ico");
fos.write(result);
fos.close();
fos.flush();
}
private static byte[] getImgBytes(BufferedImage img) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(img, "bmp", bos);
byte[] bytes = bos.toByteArray();
return Arrays.copyOfRange(bytes, 14, bytes.length);
}
}
now when i try to open my image in photo gallery the image looks like this i have no idea why it isn't working now and especially why the weird lines are appearing, although i suspect it has to with the color planes attribute in the ico image header.
Strange…but: make the BMP picture twice as high as the desired icon. Keep the declared icon size in the ICO header as before, only the picture should be higher. Then keep the area (0,0)-(16,16) black (its defining the transparency but I don’t know how it is encoded, all black for opaque works). Draw the desired contents in the BufferedImage in the area (0,16)-(16,32). In other words, add the half of the height to all pixel coordinates.
Beware that the Windows Desktop might cache icons and refuse to update them on the desktop. If in doubt open the desktop folder through another Explorer window and perform “Update” there.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import javax.imageio.ImageIO;
public class IconWriter
{
public static void main(String[] args) throws IOException
{
// note the double height
BufferedImage img = new BufferedImage(16, 32, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
g.setColor(Color.GREEN);
g.fillRect(0, 16, 16, 16);// added 16 to y coordinate
byte[] imgBytes = getImgBytes(img);
int fileSize = imgBytes.length + 22;
ByteBuffer bytes = ByteBuffer.allocate(fileSize);
bytes.order(ByteOrder.LITTLE_ENDIAN);
bytes.putShort((short) 0);//Reserved must be 0
bytes.putShort((short) 1);//Image type
bytes.putShort((short) 1);//Number of images in file
bytes.put((byte) img.getWidth());//image width
bytes.put((byte) (img.getHeight()>>1));//image height, half the BMP height
bytes.put((byte) 0);//number of colors in color palette
bytes.put((byte) 0);//reserved must be 0
bytes.putShort((short) 0);//color planes
bytes.putShort((short) 0);//bits per pixel
bytes.putInt(imgBytes.length);//image size
bytes.putInt(22);//image offset
bytes.put(imgBytes);
byte[] result = bytes.array();
FileOutputStream fos = new FileOutputStream(System.getProperty("user.home")+"\\Desktop\\hi.ico");
fos.write(result);
fos.close();
fos.flush();
}
private static byte[] getImgBytes(BufferedImage img) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(img, "bmp", bos);
byte[] bytes = bos.toByteArray();
return Arrays.copyOfRange(bytes, 14, bytes.length);
}
}
Actually, the problem you are having is mentioned in the specs (at wikipedia).
Quote:
Images with less than 32 bits of color depth[6] follow a particular
format: the image is encoded as a single image consisting of a color
mask (the "XOR mask") together with an opacity mask (the "AND mask").
That's very complicated.
Creating a 32-bit image -> fails
So, the quote above might make you think: "Oh, I just have to make the image 32-bit instead of 24-bit", as a workaround. Unfortunately that won't work. Well, actually there exists a 32-bit BMP format. But the last 8 bits are not really used, because BMP files do not really support transparency.
So, you could get tempted to use a different image type: INT_ARGB_PRE which uses a 32-bit color depth. But as soon as you try to save it with the ImageIO class, you will notice that nothing happens. The content of the stream will be null.
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB_PRE);
ImageIO.write(img, "bmp", bos);
Alternative solution: image4j
ImageIO cannot handle 32-bit images, but there are other libraries that can do the trick. The image4J libs can save 32-bit bmp files. But my guess is that for some reason you do not want to use this library. (Using image4J would make most of your code above pointless, because image4jhas built-in ICO creation support).
Second option: creating a shifted 24-bit image -> works
So, let's take a second look at what wikipedia says about < 32-bit BMP data.
The height for the image in the ICONDIRENTRY structure of the ICO/CUR
file takes on that of the intended image dimensions (after the masks
are composited), whereas the height in the BMP header takes on that
of the two mask images combined (before they are composited).
Therefore, the masks must each be of the same dimensions,
and the height specified in the BMP header must be exactly twice the
height specified in the ICONDIRENTRY structure.
So, the second solution is to create an image that is twice the original size. And you actually only have to replace your getImageBytes function for that, with the one below. As mentioned above the ICONDIRENTRY header specified in the other part of your code keeps the original image height.
private static byte[] getImgBytes(BufferedImage img) throws IOException
{
// create a new image, with 2x the original height.
BufferedImage img2 = new BufferedImage(img.getWidth(), img.getHeight()*2, BufferedImage.TYPE_INT_RGB);
// copy paste the pixels, but move them half the height.
Raster sourceRaster = img.getRaster();
WritableRaster destinationRaster = img2.getRaster();
destinationRaster.setRect(0, img.getHeight(), sourceRaster);
// save the new image to BMP format.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(img2, "bmp", bos);
// strip the first 14 bytes (contains the bitmap-file-header)
// the next 40 bytes contains the DIB header which we still need.
// the pixel data follows until the end of the file.
byte[] bytes = bos.toByteArray();
return Arrays.copyOfRange(bytes, 14, bytes.length);
}
I propose to use the headers as follows:
ByteBuffer bytes = ByteBuffer.allocate(fileSize);
bytes.order(ByteOrder.LITTLE_ENDIAN);
bytes.putShort((short) 0);
bytes.putShort((short) 1);
bytes.putShort((short) 1);
bytes.put((byte) img.getWidth());
bytes.put((byte) img.getHeight()); //no need to multiply
bytes.put((byte) img.getColorModel().getNumColorComponents()); //the pallet size
bytes.put((byte) 0);
bytes.putShort((short) 1); //should be 1
bytes.putShort((short) img.getColorModel().getPixelSize()); //bits per pixel
bytes.putInt(imgBytes.length);
bytes.putInt(22);
bytes.put(imgBytes);
Have you tried:
bytes.putShort((short) img.getColorModel().getPixelSize()); //bits per pixel
as seen in image4j.BMPEncoder#createInfoHeader, which is called by image4j.ICOEncoder#write?
If there are other issues, most of the relevant code for you seems to be in those two methods.
I believe bytes.putShort((short) 0);//bits per pixel should be changed to have the value 32, instead of 0.
If you're getting that little picture you edited in after changing the value to 32, then I'm going to say that, on second thought, it's probably actually 16.
Please try below,
ImageIo will support only png,jpg,jpeg,gif,bmp formats.
To write icons, please add below dependancy.
<!-- https://mvnrepository.com/artifact/net.sf.image4j/image4j -->
<dependency>
<groupId>net.sf.image4j</groupId>
<artifactId>image4j</artifactId>
<version>0.7zensight1</version>
</dependency>
Use ICODecoder.write(image, file);

Categories