How to change it to store message into least significant bit of R,G,B. The code below only embed message into Alpha (0~7bit)
embedInteger deals with embedding the length of the message in the first 32 pixels.
embedByte embeds your message characters, one by one. Every time it is called upon, it takes as an input the next character in your message in byte form, b[i]. There, it embeds one bit per pixel, for a total of 8 bits per byte.
private void embedMessage(BufferedImage img, byte[] mess) {
int messageLength = mess.length;
int imageWidth = img.getWidth(), imageHeight = img.getHeight(),
imageSize = imageWidth * imageHeight;
if(messageLength * 8 + 32 > imageSize) {
System.out.println("Message is too logn");
return;
}
embedInteger(img, messageLength, 0, 0);
for(int i=0; i<mess.length; i++){
embedByte(img, mess[i], i*8+32, 0);
}
}
private void embedInteger(BufferedImage img, int n, int start, int storageBit) {
int maxX = img.getWidth(), maxY = img.getHeight(),
startX = start/maxY, startY = start - startX*maxY, count=0;
for(int i=startX; i<maxX && count<32; i++) {
for(int j=startY; j<maxY && count<32; j++) {
int rgb = img.getRGB(i, j), bit = getBitValue(n, count);
rgb = setBitValue(rgb, storageBit, bit);
img.setRGB(i, j, rgb);
count++;
}
}
}
private void embedByte(BufferedImage img, byte b, int start, int storageBit) {
int maxX = img.getWidth(), maxY = img.getHeight(),
startX = start/maxY, startY = start - startX*maxY, count=0;
for(int i=startX; i<maxX && count<8; i++) {
for(int j=startY; j<maxY && count<8; j++) {
int rgb = img.getRGB(i, j), bit = getBitValue(b, count);
rgb = setBitValue(rgb, storageBit, bit);
img.setRGB(i, j, rgb);
count++;
}
}
}
private int getBitValue(int n, int location) { //n=messageLength, location=count
int v = n & (int) Math.round(Math.pow(2, location));
return v==0?0:1;
}
private int setBitValue(int n, int location, int bit) {
int toggle = (int) Math.pow(2, location), bv = getBitValue(n, location);
if(bv == bit)
return n;
if(bv == 0 && bit == 1){
n |= toggle;
System.out.println("n{toggle: "+n);
}else if(bv == 1 && bit == 0){
n ^= toggle;
}
return n;
}
You want to change the following lines in the embedMessage
method.
embedInteger(img, messageLength, 0, 0);
embedByte(img, mess[i], i*8+32, 0);
The last input, which is 0 in this case, determines the bit location of the RGBA pixel value you will embed your bit. The following image, which is taken from the website you found the code, shows you the bit order for the pixel value.
So, for the LSB of the R component, you want 8, for the G, 16 and for B 24.
A lot of the literature report steganography in RGB. RGBA is very similar, but with the extra information of transparency. Wikipedia is as good a place to read up on that. Effectively, the difference is that RGB has 3 components with 24 bits per pixel in total, while RGBA has 4 components with 32 bits per pixel. By embedding into multiple components, you can increase your hiding capacity by a factor of 3 or 4.
If you want to embed a byte into RGB, you will need 2 and 2/3 pixels (3+3+2 components). But for RGBA you only need two pixels (4+4 components). I will demonstrate how to extend your code to hide a single message in RGBA, as it is simpler in this case. As stated above, this will quadruple your hiding capacity. There are quite a few changes that take place all over the code to make this possible, but they can be boiled down to:
To apply the changes, just start clean with the code as provided in the website and substitute fully the following methods for both the encoding and decoding process.
Encode
private void openImage() {
java.io.File f = showFileDialog(true);
try {
sourceImage = ImageIO.read(f);
sourceImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = sourceImage.createGraphics();
g.drawImage(ImageIO.read(f), 0, 0, null);
g.dispose();
JLabel l = new JLabel(new ImageIcon(sourceImage));
originalPane.getViewport().add(l);
this.validate();
} catch(Exception ex) { ex.printStackTrace(); }
}
private void embedMessage(BufferedImage img, String mess) {
int messageLength = mess.length();
int imageWidth = img.getWidth(), imageHeight = img.getHeight(),
imageSize = imageWidth * imageHeight;
if(messageLength * 2 + 8 > imageSize) {
JOptionPane.showMessageDialog(this, "Message is too long for the chosen image",
"Message too long!", JOptionPane.ERROR_MESSAGE);
return;
}
embedInteger(img, messageLength, 0);
byte b[] = mess.getBytes();
for(int i=0; i<b.length; i++)
embedByte(img, b[i], i*2+8);
}
private void embedInteger(BufferedImage img, int n, int start) {
int maxX = img.getWidth(), maxY = img.getHeight(),
startX = start/maxY, startY = start - startX*maxY, count=0;
for(int i=startX; i<maxX && count<32; i++) {
for(int j=startY; j<maxY && count<32; j++) {
int rgb = img.getRGB(i, j), bit = getBitValue(n, count);
rgb = setBitValue(rgb, 0, bit);
bit = getBitValue(n, count+1); rgb = setBitValue(rgb, 8, bit);
bit = getBitValue(n, count+2); rgb = setBitValue(rgb, 16, bit);
bit = getBitValue(n, count+3); rgb = setBitValue(rgb, 24, bit);
img.setRGB(i, j, rgb);
count = count+4;
}
}
}
private void embedByte(BufferedImage img, byte b, int start) {
int maxX = img.getWidth(), maxY = img.getHeight(),
startX = start/maxY, startY = start - startX*maxY, count=0;
for(int i=startX; i<maxX && count<8; i++) {
for(int j=startY; j<maxY && count<8; j++) {
if(j==maxY-1){
startY = 0;
}
int rgb = img.getRGB(i, j), bit = getBitValue(b, count);
rgb = setBitValue(rgb, 0, bit);
bit = getBitValue(b, count+1); rgb = setBitValue(rgb, 8, bit);
bit = getBitValue(b, count+2); rgb = setBitValue(rgb, 16, bit);
bit = getBitValue(b, count+3); rgb = setBitValue(rgb, 24, bit);
img.setRGB(i, j, rgb);
count = count+4;
}
}
}
Decode
private void openImage() {
java.io.File f = showFileDialog(true);
try {
image = ImageIO.read(f);
image = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.drawImage(ImageIO.read(f), 0, 0, null);
g.dispose();
JLabel l = new JLabel(new ImageIcon(image));
imagePane.getViewport().add(l);
this.validate();
} catch(Exception ex) { ex.printStackTrace(); }
}
private void decodeMessage() {
int len = extractInteger(image, 0);
byte b[] = new byte[len];
for(int i=0; i<len; i++)
b[i] = extractByte(image, i*2+8);
message.setText(new String(b));
}
private int extractInteger(BufferedImage img, int start) {
int maxX = img.getWidth(), maxY = img.getHeight(),
startX = start/maxY, startY = start - startX*maxY, count=0;
int length = 0;
for(int i=startX; i<maxX && count<32; i++) {
for(int j=startY; j<maxY && count<32; j++) {
int rgb = img.getRGB(i, j), bit = getBitValue(rgb, 0);
length = setBitValue(length, count, bit);
bit = getBitValue(rgb, 8); length = setBitValue(length, count+1, bit);
bit = getBitValue(rgb, 16); length = setBitValue(length, count+2, bit);
bit = getBitValue(rgb, 24); length = setBitValue(length, count+3, bit);
count = count+4;
}
}
return length;
}
private byte extractByte(BufferedImage img, int start) {
int maxX = img.getWidth(), maxY = img.getHeight(),
startX = start/maxY, startY = start - startX*maxY, count=0;
byte b = 0;
for(int i=startX; i<maxX && count<8; i++) {
for(int j=startY; j<maxY && count<8; j++) {
if(j==maxY-1){
startY = 0;
}
int rgb = img.getRGB(i, j), bit = getBitValue(rgb, 0);
b = (byte)setBitValue(b, count, bit);
bit = getBitValue(rgb, 8); b = (byte)setBitValue(b, count+1, bit);
bit = getBitValue(rgb, 16); b = (byte)setBitValue(b, count+2, bit);
bit = getBitValue(rgb, 24); b = (byte)setBitValue(b, count+3, bit);
count = count+4;
}
}
return b;
}
I have modified the code so this time you can choose in which colour component you want to hide your secret from the GUI. This is is effectively superior to the version above hiding in all RGBA. Here, you have the versatility in which colour component to hide your message and if you have a really long one, you can split it in four parts. To do this I made the following changes in various part of the code:
storageBit
internally to 0, 8, 16 or 24 depending on whether you have chosen A, R, G or B respectively.To apply the changes, start clean from the code as provided in the website and substitute wholly the following methods, for both the encoding and decoding processes.
public class EmbedMessage extends JFrame implements ActionListener
{
JButton open = new JButton("Open"), embed = new JButton("Embed"),
save = new JButton("Save into new file"), reset = new JButton("Reset");
String[] rgbaList = { "B", "G", "R", "A" };
JComboBox<String> chooseRGBA = new JComboBox<>(rgbaList);
JTextArea message = new JTextArea(10,3);
BufferedImage sourceImage = null, embeddedImage = null;
JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
JScrollPane originalPane = new JScrollPane(),
embeddedPane = new JScrollPane();
private void assembleInterface() {
JPanel p = new JPanel(new FlowLayout());
p.add(open);
p.add(chooseRGBA);
p.add(embed);
p.add(save);
p.add(reset);
this.getContentPane().add(p, BorderLayout.SOUTH);
open.addActionListener(this);
embed.addActionListener(this);
save.addActionListener(this);
reset.addActionListener(this);
open.setMnemonic('O');
embed.setMnemonic('E');
save.setMnemonic('S');
reset.setMnemonic('R');
p = new JPanel(new GridLayout(1,1));
p.add(new JScrollPane(message));
message.setFont(new Font("Arial",Font.BOLD,20));
p.setBorder(BorderFactory.createTitledBorder("Message to be embedded"));
this.getContentPane().add(p, BorderLayout.NORTH);
sp.setLeftComponent(originalPane);
sp.setRightComponent(embeddedPane);
originalPane.setBorder(BorderFactory.createTitledBorder("Original Image"));
embeddedPane.setBorder(BorderFactory.createTitledBorder("Steganographed Image"));
this.getContentPane().add(sp, BorderLayout.CENTER);
}
public void actionPerformed(ActionEvent ae) {
Object o = ae.getSource();
if(o == open)
openImage();
else if(o == embed){
int rgbaChoice = chooseRGBA.getSelectedIndex(), sb = 0;
if(rgbaChoice == 0)
sb = 24;
else if(rgbaChoice == 1)
sb = 16;
else if(rgbaChoice == 2)
sb = 8;
else if(rgbaChoice == 3)
sb = 0;
embedMessage(sb);
}
else if(o == save)
saveImage();
else if(o == reset)
resetInterface();
}
private void openImage() {
java.io.File f = showFileDialog(true);
try {
sourceImage = ImageIO.read(f);
sourceImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = sourceImage.createGraphics();
g.drawImage(ImageIO.read(f), 0, 0, null);
g.dispose();
JLabel l = new JLabel(new ImageIcon(sourceImage));
originalPane.getViewport().add(l);
this.validate();
} catch(Exception ex) { ex.printStackTrace(); }
}
private void embedMessage(int storageBit) {
String mess = message.getText();
embeddedImage = sourceImage.getSubimage(0,0,
sourceImage.getWidth(),sourceImage.getHeight());
embedMessage(embeddedImage, mess, storageBit);
JLabel l = new JLabel(new ImageIcon(embeddedImage));
embeddedPane.getViewport().add(l);
this.validate();
}
private void embedMessage(BufferedImage img, String mess, int storageBit) {
int messageLength = mess.length();
int imageWidth = img.getWidth(), imageHeight = img.getHeight(),
imageSize = imageWidth * imageHeight;
if(messageLength * 8 + 32 > imageSize) {
JOptionPane.showMessageDialog(this, "Message is too long for the chosen image",
"Message too long!", JOptionPane.ERROR_MESSAGE);
return;
}
embedInteger(img, messageLength, 0, storageBit);
byte b[] = mess.getBytes();
for(int i=0; i<b.length; i++)
embedByte(img, b[i], i*8+32, storageBit);
}
Decode
public class DecodeMessage extends JFrame implements ActionListener
{
JButton open = new JButton("Open"), decode = new JButton("Decode"),
reset = new JButton("Reset");
String[] rgbaList = { "B", "G", "R", "A" };
JComboBox<String> chooseRGBA = new JComboBox<>(rgbaList);
JTextArea message = new JTextArea(10,3);
BufferedImage image = null;
JScrollPane imagePane = new JScrollPane();
private void assembleInterface() {
JPanel p = new JPanel(new FlowLayout());
p.add(open);
p.add(chooseRGBA);
p.add(decode);
p.add(reset);
this.getContentPane().add(p, BorderLayout.NORTH);
open.addActionListener(this);
decode.addActionListener(this);
reset.addActionListener(this);
open.setMnemonic('O');
decode.setMnemonic('D');
reset.setMnemonic('R');
p = new JPanel(new GridLayout(1,1));
p.add(new JScrollPane(message));
message.setFont(new Font("Arial",Font.BOLD,20));
p.setBorder(BorderFactory.createTitledBorder("Decoded message"));
message.setEditable(false);
this.getContentPane().add(p, BorderLayout.SOUTH);
imagePane.setBorder(BorderFactory.createTitledBorder("Steganographed Image"));
this.getContentPane().add(imagePane, BorderLayout.CENTER);
}
public void actionPerformed(ActionEvent ae) {
Object o = ae.getSource();
if(o == open)
openImage();
else if(o == decode){
int rgbaChoice = chooseRGBA.getSelectedIndex(), sb = 0;
if(rgbaChoice == 0)
sb = 24;
else if(rgbaChoice == 1)
sb = 16;
else if(rgbaChoice == 2)
sb = 8;
else if(rgbaChoice == 3)
sb = 0;
decodeMessage(sb);
}
else if(o == reset)
resetInterface();
}
private void openImage() {
java.io.File f = showFileDialog(true);
try {
image = ImageIO.read(f);
image = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.drawImage(ImageIO.read(f), 0, 0, null);
g.dispose();
JLabel l = new JLabel(new ImageIcon(image));
imagePane.getViewport().add(l);
this.validate();
} catch(Exception ex) { ex.printStackTrace(); }
}
private void decodeMessage(int storageBit) {
int len = extractInteger(image, 0, storageBit);
byte b[] = new byte[len];
for(int i=0; i<len; i++)
b[i] = extractByte(image, i*8+32, storageBit);
message.setText(new String(b));
}
private byte extractByte(BufferedImage img, int start, int storageBit) {
int maxX = img.getWidth(), maxY = img.getHeight(),
startX = start/maxY, startY = start - startX*maxY, count=0;
byte b = 0;
for(int i=startX; i<maxX && count<8; i++) {
for(int j=startY; j<maxY && count<8; j++) {
if(j==maxY-1){
startY = 0;
}
int rgb = img.getRGB(i, j), bit = getBitValue(rgb, storageBit);
b = (byte)setBitValue(b, count, bit);
count++;
}
}
return b;
}