2

I have this code, trying to do encoding message in images using least significant bit method. What I can't figure out is that how to end the encoding of message when there is no message to encode left.

It just continues until the end of the end of the loop.

I tried counters like limit. Same with what I did in the decoding part but with no avail that's why I removed it.

I included these methods because they were used and can be used to contain counters to identify if message is done encoding and can stop iterating

Encoding and Decoding are in the constructor.

public class Steganography {

    public static SteganographyGUI gui;
    public static String binarizedMessage = "", encodedMessage = "", decodedMessage = "";
    public static int count = 0, limit = 0;

    public static BufferedImage image;
    public static int width, height, numLSB;

    public Steganography() {
        if(gui.isEncode()){
            try { 
                String messageToBeHidden = gui.getMessage();
                binarizedMessage = stringToBinary(messageToBeHidden); 

                File input = new File(gui.getPath());
                image = ImageIO.read(input);
                width = image.getWidth();
                height = image.getHeight(); 
                gui.appendStatus("File Name: " + gui.getFileName() + "\nWidth: " + width + "\nHeight: " + height + "\nLSB: " + gui.getSelectedLSB());
                numLSB = gui.getSelectedLSB();
                //encoding
                for(int i = 0; i < height; i++){
                    for(int j = 0; j < width; j++){
                        Color c = new Color(image.getRGB(j, i));                  

                        int red = binaryToInteger(insertMessage(integerToBinary((int)(c.getRed())),numLSB));
                        int green = binaryToInteger(insertMessage(integerToBinary((int)(c.getGreen())),numLSB));
                        int blue = binaryToInteger(insertMessage(integerToBinary((int)(c.getBlue())),numLSB));

                        Color newColor = new Color(red,green,blue);
                        image.setRGB(j,i,newColor.getRGB());

                    }
                }
                gui.appendStatus("Binarized message is: " + binarizedMessage);
                File output = new File(gui.getOutput()+"lossy.jpg");

                ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
                ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
                jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                jpgWriteParam.setCompressionQuality(1f);

                FileImageOutputStream outputStream = new FileImageOutputStream(output); //For example, FileImageOutputStream
                jpgWriter.setOutput(outputStream);
                IIOImage outputImage = new IIOImage(image, null, null);
                jpgWriter.write(null, outputImage, jpgWriteParam);
                jpgWriter.dispose();

                File output2 = new File(gui.getOutput()+"lossless.jpg");
                ImageIO.write(image, "png", output2);
                gui.appendStatus("Message \""+ messageToBeHidden +"\" was encoded in "+ gui.getFileName() +".\nOutput files are: " + gui.getOutput() + "lossy.jpg \n\t"+ gui.getOutput() + "lossless.jpg");
            } catch (Exception e) {}
        }
        else{
            File input = new File(gui.getPath());
            String encodedData = "";
            try {
                image = ImageIO.read(input);
            } catch (IOException ex) {}
            width = image.getWidth();
            height = image.getHeight(); 
            gui.appendStatus("File Name: " + gui.getFileName() + "\nWidth: " + width + "\nHeight: " + height + "\nLSB: " + gui.getSelectedLSB());
            numLSB = gui.getSelectedLSB();
            String eData = "";
            //decoding
            for(int i = 0; i < height; i++){
                for(int j = 0; j < width; j++){
                    Color c = new Color(image.getRGB(j, i));                  

                    encodedData += getLSB(integerToBinary((int)(c.getRed())),numLSB);
                    encodedData += getLSB(integerToBinary((int)(c.getGreen())),numLSB);
                    encodedData += getLSB(integerToBinary((int)(c.getBlue())),numLSB);

                    if(limit >= 8 * numLSB){
                        break;
                    }
                }
            }
            int counter = 0;
            while(counter * 8 < encodedData.length()){
                int index = counter * 8;
                String str = encodedData.substring(index, index + 8);
                eData += str;
                if(!str.equals("00000000")){
                    encodedMessage += new Character((char)Integer.parseInt(str, 2)).toString();
                    counter++;
                }
                else{
                    eData = eData.substring(0,eData.length()-8);
                    break;
                }
            } 
            gui.appendStatus("Data decoded was: \""   + eData + "\".");
            gui.appendStatus("Message decoded was: \""   + encodedMessage + "\".");
            gui.appendStatus("Number of characters: " + counter); 
        }
        reinitialize();
    }

    public static void reinitialize(){
        binarizedMessage = encodedMessage = decodedMessage = "";
        count = limit = width = height = numLSB = 0;
    }

    public static String stringToBinary(String s){
        byte[] bytes = s.getBytes();                    // http://stackoverflow.com/questions/917163/convert-a-string-like-testing123-to-binary-in-java
        StringBuilder binary = new StringBuilder();
        for (byte b : bytes) {
            int val = b;
            for (int i = 0; i < 8; i++){
                binary.append((val & 128) == 0 ? 0 : 1);
                val <<= 1;
            } 
        }
        return binary.toString(); 
    }

    public static String integerToBinary(int i){                   
        return String.format("%8s", Integer.toBinaryString(i)).replace(' ', '0');           //http://stackoverflow.com/questions/21856626/java-integer-to-binary-string
    }

    public static int binaryToInteger(String s){                   
        return Integer.parseInt(s, 2);                                       //http://stackoverflow.com/questions/7437987/how-to-convert-binary-string-value-to-decimal
    }                                                                       //http://stackoverflow.com/questions/10178980/how-to-convert-a-binary-string-to-a-base-10-integer-in-java

    public static String clearLSB(String s, int x){
        StringBuilder result = new StringBuilder(s);                //http://stackoverflow.com/questions/6952363/java-replace-a-character-at-a-specific-index-in-a-string
        for (int i = 8 - x; i < 8; i++){
            result.setCharAt(i, '0');
        }
        return result.toString();                       //http://www.tutorialspoint.com/java/lang/stringbuilder_tostring.htm
    }

    public static String insertMessage(String s, int x){            
        String result = clearLSB(s, x);
        StringBuilder temp = new StringBuilder(result);

        for (int i = 8 - x; i < 8; i++){
            if(count < binarizedMessage.length())
                temp.setCharAt(i, binarizedMessage.charAt(count++));           
        }

        return temp.toString();
    }

    public static String getLSB(String s, int x){
        String result = "";        
        for (int i = 8 - x; i < 8; i++){
            result += s.charAt(i);
            if(s.charAt(i) == '0')
                limit += 1;
            else
                limit = 0;
        }
        return result; 
    }

    public static String binaryToText(String s){ 
        StringBuilder sb = new StringBuilder();
        count = 0;
        while(count<s.length()){
            StringBuilder sb2 = new StringBuilder();
            for (int i = 0; i < 8; i++){
                if(count<s.length())
                    sb2.append(s.charAt(count++));
                else
                    sb2.append('0');
                break;
            }
            //binary to char and append to sb
            sb.append((char)Integer.parseInt( sb.toString(), 2 ) );   
        }   
        return sb.toString();
    }    

    public static void main(String[] args) {
        gui = new SteganographyGUI();
        gui.setVisible(true);
    }

}

EDIT:

Seems that the first problem was fixed but created another problem.

Now that i've changed my decoding and encoding based on the answer below by Reti43, I cant get my dataComparison to work. I tried applying the decoding lines you pointed out but I fail to get the encoded data in the picture.

Here is the original DataComparison

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Arrays;
import javax.imageio.ImageIO;

public class DataComparison {

    public static DataComparisonGUI gui;
    public static BufferedImage image, image2;
    public static int width, height, lsb, limit = 0;

    public static String lossyData = "", losslessData = "", lsData = "", lyData = "";
    public static String lossyMessage = "", losslessMessage = "";

    public static int[][][] cLossyData, cLosslessData;

    public DataComparison(){
        lsb = gui.getLSB(); 

        try{
            File lossy = new File(gui.getLossyPath());
            File lossless = new File(gui.getLosslessPath());

            image = ImageIO.read(lossy);
            image2 = ImageIO.read(lossless);
            width = image.getWidth();
            height = image.getHeight();

            cLossyData = new int[height][width][3];
            cLosslessData = new int[height][width][3];

            for(int i = 0; i < height; i++){
                for(int j = 0; j < width; j++){
                    Color c = new Color(image.getRGB(j, i)); 
                    Color c2 = new Color(image2.getRGB(j, i)); 

                    lossyData += getLSB(integerToBinary((int)(c.getRed())),lsb);
                    lossyData += getLSB(integerToBinary((int)(c.getGreen())),lsb);
                    lossyData += getLSB(integerToBinary((int)(c.getBlue())),lsb);

                    losslessData += getLSB(integerToBinary((int)(c2.getRed())),lsb);
                    losslessData += getLSB(integerToBinary((int)(c2.getGreen())),lsb);
                    losslessData += getLSB(integerToBinary((int)(c2.getBlue())),lsb);

                    cLossyData[i][j][0] = c.getRed();
                    cLossyData[i][j][1] = c.getGreen();
                    cLossyData[i][j][2] = c.getBlue();

                    cLosslessData[i][j][0] = c2.getRed();
                    cLosslessData[i][j][1] = c2.getGreen();
                    cLosslessData[i][j][2] = c2.getBlue();

                    if(limit >= 8 * lsb){
                        break;
                    }
                }
            }

            int counter = 0;
            while(counter * 8 < losslessData.length()){
                int index = counter * 8;
                String str = lossyData.substring(index, index + 8);
                String str2 = losslessData.substring(index, index + 8);
                lyData += str;
                lsData += str2;
                if(!str2.equals("00000000")){
                    lossyMessage += new Character((char)Integer.parseInt(str, 2)).toString();
                    losslessMessage += new Character((char)Integer.parseInt(str2, 2)).toString();
                    counter++;
                }
                else{
                    lyData = lyData.substring(0,lyData.length()-8);
                    lsData = lsData.substring(0,lsData.length()-8);
                    break;
                }
            }
            int i = 0, lostBits = 0;
            while(i < lyData.length()){
                if(lyData.charAt(i) != lsData.charAt(i)){
                    lostBits++;
                }
                i++;
            } 
            gui.appendStatus("Data decoded was (Lossless):\n\""   + lsData + "\".");
            gui.appendStatus("Data decoded was (Lossy):\n\""   + lyData + "\".");
            gui.appendStatus("Number of lsb: " + lsb);
            gui.appendStatus("Number of bits (hidden message): " + counter * 8);
            gui.appendStatus("Number of lost bits (hidden message): " + lostBits);

            float z = ((lostBits*100)/(counter*8));
            String percentage = String.format("%.04f", z);

            gui.appendStatus("Percentage of lost bits (hidden message): " + percentage + "%");
            gui.appendStatus("Message decoded was (Lossless): \""   + losslessMessage + "\".");
            gui.appendStatus("Message decoded was (Lossy): \""   + lossyMessage + "\".");
            gui.appendStatus("Number of characters: " + counter);

            int counterR = 0, counterG = 0, counterB = 0;
            for(int p = 0; p < height; p++){
                for(int q = 0; q < width; q++){
                    if(cLosslessData[p][q][0] != cLossyData[p][q][0]){
                        counterR++;
                    }
                    else if(cLosslessData[p][q][1] != cLossyData[p][q][1]){
                        counterG++;
                    }
                    else if(cLosslessData[p][q][2] != cLossyData[p][q][2]){
                        counterB++;
                    }
                }
            }
            gui.appendStatus("Total RGB values: " + width * height * 3);
            gui.appendStatus("Altered Red values: " + counterR);
            gui.appendStatus("Altered Green values: " + counterG);
            gui.appendStatus("Altered Blue values: " + counterB);
            gui.appendStatus("Total Altered values: " + (counterR + counterG + counterB));

            z = ((counterR + counterG + counterB)*10000)/(width * height * 3);
            percentage = String.format("%.02f", z/100);

            gui.appendStatus("Percentage Altered values: " + percentage + "%");

            reinitialize();
        } catch (Exception e) {}

    }

    public static void reinitialize(){
        losslessData = lossyData = lsData = lyData = losslessMessage = lossyMessage = "";
        limit = width = height = lsb = 0;
        Arrays.fill(cLossyData, 0);
        Arrays.fill(cLosslessData, 0); 
    }

    public static String integerToBinary(int i){                   
        return String.format("%8s", Integer.toBinaryString(i)).replace(' ', '0');           //http://stackoverflow.com/questions/21856626/java-integer-to-binary-string
    }

    public static String getLSB(String s, int x){
        String result = "";        
        for (int i = 8 - x; i < 8; i++){
            result += s.charAt(i);
            if(s.charAt(i) == '0')
                limit += 1;
            else
                limit = 0;
        }
        return result; 
    }

    public static void main(String[] args) {
        // TODO code application logic here
        gui = new DataComparisonGUI();
        gui.setVisible(true);
    }

}

The code above was working based on my original encoding/decoding algorithm. The one that doesnt stop the loop. I tried editing the code and applying the new decoding algorithm but I cant make it work.

  • What exactly does `numLSB` contain? Can you show us the methods for `binaryToInteger`, `insertMessage` and `integerToBinary`? As it currently stands, `numLSB` is outside the loop and doesn't update so it only makes sense to contain all 1s and 0s of your message. But I see no place where you increment a counter to embed the next binary bit. – Reti43 Apr 23 '15 at 07:59
  • @Reti43 `numLSB` contains an int value that determines how many bits are to be changed in each rgb value. The methods `binaryToInteger`, `insertMessage ` and `insertMessage` are located below. I increment a counter in insertmessage method. that count++ which starts from 0. –  Apr 23 '15 at 13:43
  • You shouldn't edit questions to drastically change the topic. Create a new question instead. This question was concerned with exiting some loops and not its effects to some other piece of code which was not mentioned in the question until the issue loop breaking issue was resolved. – Reti43 Apr 25 '15 at 08:21

1 Answers1

2

Encoding changes

First things first, for the encoding part you need to change the following line

binarizedMessage = stringToBinary(messageToBeHidden);

to

binarizedMessage = stringToBinary(messageToBeHidden) + "00000000";

This is because in the extraction, the end of message is reached when you find eight 0s in a row.

Now, for terminating the encoding sequence, you need to modify your loops accordingly.

encoding:
for(int i = 0; i < height; i++){
    for(int j = 0; j < width; j++){
        Color c = new Color(image.getRGB(j, i));

        int red = binaryToInteger(insertMessage(integerToBinary((int)(c.getRed())),numLSB));
        int green = binaryToInteger(insertMessage(integerToBinary((int)(c.getGreen())),numLSB));
        int blue = binaryToInteger(insertMessage(integerToBinary((int)(c.getBlue())),numLSB));

        Color newColor = new Color(red,green,blue);
        image.setRGB(j,i,newColor.getRGB());

        if (count == binarizedMessage.length()) break encoding;
    }
}

The variable count is incremented every time you embed a bit in the insertMessage method. As soon as it has reached the value of binarizedMessage.length(), it will stop embedding even though it is called. So, you just need to check whether you have embedded all bits every time you access a new pixel at (i, j).

However, the embedding may be finished with the red color of a pixel, but calling insertMessage for green and blue, even though it will not embed anything new, it will still clear the lsbs of those colors. So, let's modify insertMessage to prevent these unnecessary modifications.

public static String insertMessage(String s, int x){
    String result;
    if (count < binarizedMessage.length()) {
        result = clearLSB(s, x);
    } else {
        result = s;
    }
    StringBuilder temp = new StringBuilder(result);

    for (int i = 8 - x; i < 8; i++){
        if(count < binarizedMessage.length())
            temp.setCharAt(i, binarizedMessage.charAt(count++));           
    }

    return temp.toString();
}

Decoding changes

decoding:
for(int i = 0; i < height; i++){
    for(int j = 0; j < width; j++){
        Color c = new Color(image.getRGB(j, i));                  

        encodedData += getLSB(integerToBinary((int)(c.getRed())),numLSB);
        if(limit >= 8) break decoding;
        encodedData += getLSB(integerToBinary((int)(c.getGreen())),numLSB);
        if(limit >= 8) break decoding;
        encodedData += getLSB(integerToBinary((int)(c.getBlue())),numLSB);
        if(limit >= 8) break decoding;
    }
}

Every time you extract new bits from a color, you use the limit variable to check whether (at least) the last 8 bits are 0, signifying the end of message. This condition must be checked after embedding in each color and not after blue. Consider the case where the end of message is found after red, but carrying on to green and blue, you come across with a 1 which resets limit.

Finally, you need to change the following block

int counter = 0;
while(counter * 8 < encodedData.length()){
    int index = counter * 8;
    String str = encodedData.substring(index, index + 8);
    eData += str;
    if(!str.equals("00000000")){
        encodedMessage += new Character((char)Integer.parseInt(str, 2)).toString();
        counter++;
    }
    else{
        eData = eData.substring(0,eData.length()-8);
        break;
    }
}

to

for (int index = 0; index < encodedData.length()/8; index++) {
    String str = encodedData.substring(8*index, 8*index+8);
    eData += str;
    encodedMessage += new Character((char)Integer.parseInt(str, 2)).toString();
}

Because of the way you check for the last eight bits being 0, consider the message "hello world". That's 11 characters, so 88 bits + 8 for terminating the message = 96 bits. However, the binary for "d" is 01100100, So once you have extracted 94 bits, you have come across with eight 0s and break the extracting sequence. If we do integer division of 94 with 8, we get the whole number 11, which is the number of characters of our message. So we convert the first 88 bits to characters and our job is done.


This is out of scope for the answer, but a piece of advice. Consider clearing, modifying and extracting bits using the bitwise operations AND and OR. It's more elegant since you don't have to convert integers to strings and then back to integers and it would provide minor, though not noticeable in your case, performance improvements.

Reti43
  • 9,656
  • 3
  • 28
  • 44
  • gonna try this tomorrow. Never thought of adding those 8 zeros directly to the binarizedMessage. Gonna keep you posted. Got an exam coming and gonna watch avengers tomorrow :) thanks for the effort on understanding my hell of a code and at the same time, explaining this answer :) –  Apr 23 '15 at 22:19
  • Now that i've changed my decoding and encoding. I cant get my dataComparison to work. I tried applying the decoding lines you pointed out but I fail to get the full message. Whenever there is a space between the message, it ends. –  Apr 24 '15 at 23:41
  • 1
    A space has ascii code 32. The end-of-message is 0 so this shouldn't be a reason for premature termination. I personally tried this code for various messages (some long, some have spaces, some had punctuation) and for various values for `lsb` and they all worked perfectly fine. This algorithm **works**. However, it only does so for png and not for jpeg as it has been pointed out in a [different question](http://stackoverflow.com/questions/29677726/steganography-in-lossy-compression-java/29678676#29678676). Before applying my fix suggestions, could you get `dataComparison` to work? – Reti43 Apr 25 '15 at 07:57
  • Yes. I made it work. Just slower compared to yours. `dataComparison` works when the encoding I'll use was my previous encoding. When I switched to yours. There is no problem in encoding. Just failing to decode in `dataComparison` because it was based on my first encoding. I tried editing the encoding to make it look like your encoding BUT didnt succeed. I was hoping you could enlighten me again. I'll edit my post on the other question and add my edited decoding algorithm that was based on your answer. Hope you can see what's wrong. –  Apr 25 '15 at 15:43