The output image you have posted is a 16-color paletted image.
The data I am seeing shows that you have actually applied your changes to the palette index, not to the colors of the image. The reason you are seeing the distortion is because of the way the palette is organized, you aren't modifying the LSB of the color, you're modifying the LSB of the index, which could change it to a completely different (and very noticeable, as you can see) color. (Actually, you're modifying the LSB of every other index, the 16-color form is 4 bits per pixel, 2 pixels per byte.)
It looks like you loaded raw image data and didn't decode it in to RGB color information. Your algorithm will only work on raw RGB (or raw grayscale) data; 3 bytes (or 1 for grayscale) per pixel. You need to convert your image to RGB888 or something similar before you operate on it. When you save it, you need to save it in a lossless, full color (unless you actually can fit all your colors in a palette) format too, otherwise you risk losing your information.
Your problem actually doesn't lie in the steganography portion of your program, but in the loading and saving of the image data itself.
When you load the image data, you need to convert it to an RGB format. The most convenient format for your application will be BufferedImage.TYPE_3BYTE_BGR
, which stores each pixel as three bytes in blue, green, red order (so your byte array will be B,G,R,B,G,R,B,G,R,...). You can do that like so:
public static BufferedImage loadRgbImage (String filename) {
// load the original image
BufferedImage originalImage = ImageIO.read(filename);
// create buffer for converted image in RGB format
BufferedImage rgbImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
// render original image into buffer, changes to destination format
rgbImage.getGraphics().drawImage(originalImage, 0, 0, null);
return rgbImage;
}
If you are frequently working with source images that are already in BGR format anyways, you can make one easy optimization to not convert the image if it's already in the format you want:
public static BufferedImage loadRgbImage (String filename) {
BufferedImage originalImage = ImageIO.read(filename);
BufferedImage rgbImage;
if (originalImage.getType() == BufferedImage.TYPE_3BYTE_BGR) {
rgbImage = originalImage; // no need to convert, just return original
} else {
rgbImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
rgbImage.getGraphics().drawImage(originalImage, 0, 0, null);
}
return rgbImage;
}
You can then just use the converted image for all of your operations. Note that the byte array from the converted image will contain 3 * rgbImage.getWidth() * rgbImage.getHeight()
bytes.
You shouldn't have to make any changes to your current image saving code; ImageIO
will detect that the image is RGB and will write a 24-bit PNG.