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.
Embedding in multiple colour components
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:
- Ditch storageBit as it is not necessary anymore.
- You can embed each byte in two pixels. In the first pixel you embed the first 4 bits in the LSB of the A, B, G and R components of the first pixel, and the last 4 bits in the LSB components of the 2nd pixel.
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;
}
Embedding different secrets in each colour component
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:
- Change the value of
storageBit
internally to 0, 8, 16 or 24 depending on whether you have chosen A, R, G or B respectively.
- This choice is made on the GUI so you don't have to recompile the code for different colour components every time.
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("D