Tutorials‎ > ‎

Steganography using Pixel Value Differencing (PVD) Algorithm Implemented with Java (Part 2) - PVD Program

posted Jun 18, 2015, 1:02 AM by Sujono _   [ updated Aug 15, 2016, 11:38 PM by Surya Wang ]

Welcome back to the Steganography and PVD Tutorial. In this second and final part, we will implement the PVD algorithm using Java.

1.    The Code
       
       We will use Java for this tutorial. You can use any Java IDE, such as Eclipse, Netbeans, etc. It would not be a problem because we just need some basic features of Java.
  • Prelude.
       First of all, we need to declare all of the list that we need, Here are the list that we need:  
        I) vecRed, vecGreen, and vecBlue for RGB 
        II) vecDI for di 
        III) vecDI2 for di' 
        IV) vecTable for range bit that can be inserted into the current pixel.
        V) vecLJ for lj
        VI) vecUJ for uj
        VII) vecWJ for wj
        VIII) vecTI for ti
        IX) vecTI2 for ti'
        X) vecM for M
        XI) vecASCII for ASCII of all pixels
        XII) vecBinaryWord for the binary word from all character of the inserted message, and
        XIII) vecBinaryDecrypt for the binary word that get from the decrypt process.

  • Create the function to get the RGB value from the picture and save them in three different list or vector (vecRed, vecGreen, and vecBlue).
         
      void getPixelRGB(){
               try {
                vecRed.clear();
                vecGreen.clear();
                vecBlue.clear();
                img = ImageIO.read(new File(txtInputFile.getText()));
                width = img.getWidth();
                height = img.getHeight();
                pixels = img.getRGB(0,0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
                for(int i=0;i<pixels.length;i++)
                {
                    c = new Color(pixels[i]);
                    vecRed.add(c.getRed());
                    vecGreen.add(c.getGreen());
                    vecBlue.add(c.getBlue());
                }
            } catch (IOException ex) {
                Logger.getLogger(SteganoPVD.class.getName()).log(Level.SEVERE, null, ex);
            }
       }
  • Create the function to change the input text into ASCII binary with 8 length bits and save it into a vecASCII.
    void setBinaryText(){
        vecASCII.clear();
        String tmp = "";
        tmp = txtInputMsg.getText();
        for(int i=0;i<tmp.length();i++)
        {
          char temp = tmp.charAt(i);
          vecASCII.add((int)temp);
        }
        for(int i=0;i<vecASCII.size();i++)
        {
            String old_biner = Integer.toBinaryString(vecASCII.get(i));
            int min = 8 - old_biner.length();
            String new_biner = "";
            for(int j=0;j<min;j++)
            {
                new_biner+="0";
            }
            new_biner+=old_biner;
            vecBinaryWord.add(new_biner);
        }
       }
  • Create the function to reset all of the list except RGB list.
  void resetAll(){
      vecDI.clear();
      vecLJ.clear();
      vecWJ.clear();
      vecUJ.clear();
      vecTI.clear();
      vecTI2.clear();
      vecDI2.clear();
      vecM.clear();
    }
  • Create the function for the PVD Algorithm. We will change the red value of the vecRed and no need to change other RGB list. Each binary word will be inserted into four pixel where each pixel contain a quarter of one binary word.
  void PVDProcess(){
      colIndex = rowIndex = 0;
      for(int a=0;a<vecBinaryWord.size();a++)
      {
      for(int b=0;b<8;b+=2)
      {
        vecDI.add(Math.abs(vecRed.get(colIndex)-vecRed.get(colIndex+1)));
        low = high = cek = 0;
        for(int i=0;i<vecTable.size();i++)
        {
          high+=vecTable.get(i);
          --high;
          if(vecDI.get(vecDI.size()-1)>=low&&vecDI.get(vecDI.size()-1)<=high)
          {
              cek = i;
              vecLJ.add(low);
              vecUJ.add(high);
          }
          low = ++high;
         }
        vecWJ.add(vecUJ.get(vecUJ.size()-1)-vecLJ.get(vecLJ.size()-1)+1);
        vecTI.add(Math.log(vecWJ.get(vecWJ.size()-1))/Math.log(2));
        int sizeBit = vecTI.get(vecTI.size()-1).intValue();
        int angka = Integer.parseInt(vecBinaryWord.get(a).substring(b,b+sizeBit),sizeBit);
        vecTI2.add(angka);
        vecDI2.add(vecTI2.get(vecTI2.size()-1)+vecLJ.get(vecLJ.size()-1));
        vecM.add(Math.abs(vecDI2.get(vecDI2.size()-1)-vecDI.get(vecDI.size()-1)));
        if(vecRed.get(colIndex)>=vecRed.get(colIndex+1))
        {
          if(vecDI2.get(vecDI2.size()-1)>vecDI.get(vecDI.size()-1))
          {
              vecRed.set(colIndex, vecRed.get(colIndex)+(int)Math.ceil(vecM.get(vecM.size()-1)/2f));
              vecRed.set(colIndex+1, vecRed.get(colIndex+1)-(int)Math.floor(vecM.get(vecM.size()-1)/2f));
          }         
          else
          {
              vecRed.set(colIndex, vecRed.get(colIndex)-(int)Math.ceil(vecM.get(vecM.size()-1)/2f));
              vecRed.set(colIndex+1, vecRed.get(colIndex+1)+(int)Math.floor(vecM.get(vecM.size()-1)/2f));
          }
        }
        else
        {
          if(vecDI2.get(vecDI2.size()-1)>vecDI.get(vecDI.size()-1))
          {
              vecRed.set(colIndex, vecRed.get(colIndex)-(int)Math.floor(vecM.get(vecM.size()-1)/2f));
              vecRed.set(colIndex+1, vecRed.get(colIndex+1)+(int)Math.ceil(vecM.get(vecM.size()-1)/2f));
          }         
          else
          {
              vecRed.set(colIndex, vecRed.get(colIndex)+(int)Math.ceil(vecM.get(vecM.size()-1)/2f));
              vecRed.set(colIndex+1, vecRed.get(colIndex+1)-(int)Math.floor(vecM.get(vecM.size()-1)/2f));
          }
        }
        colIndex+=2;
      }
      }
   }
  • Create the function to call setBinaryText(), resetAll(), and PVDProcess()
    void setMessage(){
      setBinaryText();
      resetAll();
      PVDProcess();
      }
  • Create the function for show the normal and stego-image into jLabel.
    void setImage(){
        BufferedImage image = new BufferedImage(img.getWidth(),img.getHeight(), BufferedImage.TYPE_INT_RGB);
        WritableRaster raster = image.getRaster();
        for(int i=0;i<pixels.length;i++)
            pixels[i] = ((vecRed.get(i)&0x0ff)<<16)|((vecGreen.get(i)&0x0ff)<<8)|(vecBlue.get(i)&0x0ff);
        raster.setDataElements(0, 0, img.getWidth(),img.getHeight(), pixels);
        jLabel6.setIcon(new ImageIcon(image));
        try {
            img = ImageIO.read(new File(txtInputFile.getText()));
            jLabel1.setIcon(new ImageIcon(img));
        } catch (IOException ex) {
            Logger.getLogger(SteganoPVD.class.getName()).log(Level.SEVERE, null, ex);
        }
        jLabel1.setText("");
    }
  • Next is the display of the program. For this tutorial, we only need a simple interface. Here's my interface:

Picture 1. Display of The Program
  • We need the action event for each button. First, we need to create an event function for browse button. This function will show the file chooser dialog and fill the input based on the location of chosen file.
 
   private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
      choose = new JFileChooser();
      int rVal = choose.showOpenDialog(this);
      if (rVal == JFileChooser.APPROVE_OPTION) {
        txtInputFile.setText(choose.getCurrentDirectory().toString()+"\\"+choose.getSelectedFile().getName());
      }
      if (rVal == JFileChooser.CANCEL_OPTION) {
        txtInputFile.setText("");
      }
    }

  • Second, we create an event function to init the PVD process and show both image into jLabel.
   private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
        if(txtInputFile.getText().length()==0)
            JOptionPane.showMessageDialog(null,"Image must be chosen");
        else
        {
        for(int i=0;i<64;i++)
           vecTable.add(4);
        getPixelRGB();
        setMessage();
        setImage();
        jLabel5.setVisible(true);
        jLabel6.setVisible(true);
        jLabel1.setVisible(true);
        }
    }
  • Last, we create the function to regain our message from stego-image. 
    private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {

        colIndex = rowIndex = 0;
        String new_biner = "";
        vecBinaryDecrypt.clear();
        resetAll();
        for(int i=0;i<txtInputMsg.getText().length()*8;i+=2)
        {
        vecDI.add(Math.abs(vecRed.get(i)-vecRed.get(i+1)));
        low = high = cek = 0;
        for(int j=0;j<vecTable.size();j++)
        {
          high+=vecTable.get(j);
          --high;
          if(vecDI.get(vecDI.size()-1)>=low&&vecDI.get(vecDI.size()-1)<=high)
          {
              cek = i;
              vecLJ.add(low);
              vecUJ.add(high);
          }
          low = ++high;
         }
        vecWJ.add(vecUJ.get(vecUJ.size()-1)-vecLJ.get(vecLJ.size()-1)+1);
        vecTI.add(Math.log(vecWJ.get(vecWJ.size()-1))/Math.log(2));
        vecDI2.add(vecDI.get(vecDI.size()-1)-vecLJ.get(vecLJ.size()-1));
        String old_biner = Integer.toBinaryString(vecDI2.get(vecDI2.size()-1));
        int min = 2 - old_biner.length();
        for(int j=0;j<min;j++)
        {
            new_biner+="0";
        }
        new_biner+=old_biner;
            if((i+2)%8==0&&i!=0)
            {
            vecBinaryDecrypt.add(new_biner);
            new_biner="";
            } 
            else if((i+2)>=txtInputMsg.getText().length()*8)
            {
            vecBinaryDecrypt.add(new_biner); 
            new_biner="";
            }
        }
        String Result="";
        for(int i=0;i<vecBinaryDecrypt.size();i++)
        {
            String tmp = Integer.toString(Integer.parseInt(vecBinaryDecrypt.get(i),2));
            char tmp2 = (char) Integer.parseInt(tmp);
            Result+=tmp2;
        }
       txtResultMsg.setText(Result);
    }

2.    Run The Program!
        
       We will use the lorem ipsum text for the embedded message. Here's my lorem ipsum text:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc vulputate aliquet mauris, elementum elementum mi suscipit eu. Pellentesque consectetur mi mattis odio faucibus, non pellentesque sem sodales. Curabitur ultricies, nulla in ultricies cursus, purus quam posuere lorem, dapibus porta risus neque vitae velit. Nulla facilisi. Maecenas auctor quam et tempor bibendum. Nam eget mollis velit. Phasellus ultricies bibendum lacus at vulputate. Sed aliquet sem non massa porttitor, non vulputate massa aliquet. Fusce sit amet purus dapibus nisl tempus varius. Nunc in dapibus massa, sit amet vehicula augue. Nunc fermentum erat non metus mollis placerat.

Vivamus vitae odio iaculis, malesuada ex quis, volutpat velit. Mauris et placerat magna. Fusce eleifend ligula vel ultrices feugiat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Phasellus faucibus vel justo sodales dictum. Vivamus ut sodales diam, in volutpat justo. Sed luctus blandit euismod. Integer aliquet lorem lacinia, efficitur ex eget, ornare est.

Nam accumsan convallis sem a dapibus. Quisque placerat, massa nec finibus congue, ante leo volutpat enim, eu aliquam elit tortor posuere justo. Cras ultricies cursus mauris vel ullamcorper. Curabitur ut lorem et felis ullamcorper ultrices a et justo. Morbi aliquam odio enim, ut maximus ex vulputate dapibus. Sed gravida nisl eget pulvinar volutpat. Fusce vel vulputate dolor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;

Nam sed enim et ante tincidunt aliquet vitae a nunc. Suspendisse pellentesque nec nisl sed viverra. Donec dui ligula, pellentesque interdum volutpat malesuada, fermentum a odio. Sed non dapibus libero. Curabitur tempus tempus purus. Vivamus porttitor placerat eros, id pretium ex. Curabitur mattis est arcu, in consequat odio dapibus quis. In mi nunc, finibus sed pellentesque in, accumsan eget ligula. Phasellus augue massa, gravida et interdum vel, semper a nisi. Nam eget elit sed tellus rhoncus placerat. Aliquam imperdiet dictum enim eu sollicitudin. Mauris leo massa, sodales sed porta a, venenatis egestas arcu. Vestibulum et tincidunt eros, eget fermentum nibh. Sed eget turpis mattis sem ornare luctus ac sollicitudin odio.

Etiam ut efficitur est. Nunc ut viverra est, in tristique nulla. Proin sit amet erat et risus efficitur gravida. Fusce eu volutpat urna. Etiam porttitor ultrices arcu et blandit. Etiam faucibus turpis nec finibus maximus. Aenean ullamcorper ipsum accumsan nunc pharetra dictum.


        Here's the result of both image after embedding process:


Picture 2. Image and Stego-Image Result

        Looks like there are no difference, right? It's because we cannot see the difference of pixels that is not too significant. As the test for performance of PVD algorithm, we will use Peak Signal-To-Noise Ratio (PSNR).

2.      Peak Signal Noise Rato (PSNR)

        The Peak Signal-To-Noise Ratio (PSNR) was utilized to evaluate the stego-image quality. The PSNR is defined as follows:


        Where:
        

is the pixel of the cover image where the coordinate is (i,j)

and 

is the pixel of the stego-image where the coordinate is (i,j)

        Good range values for the PSNR in image are between 30 and 50 dB, provided the bit depth is 8 bits, where higher is better. Here is the code:

    double round(double value, int places) {
        if (places < 0) throw new IllegalArgumentException();

        long factor = (long) Math.pow(10, places);
        value = value * factor;
        long tmp = Math.round(value);
        return (double) tmp / factor;
    }
    void PSNRCheck(){
       Double sum = new Double("0");
       for(int i=0;i<height;i++)
       {
           for(int j=0;j<width;j++)
           {
               int remain = vecOldRed.get((i*width)+j) - vecRed.get((i*width)+j);
               Double result = Math.pow((double)remain, 2);
               sum += result;
           }
       }
       Double MSE = sum/Double.parseDouble((width*height)+"");
       Double PSNR = 10 * Math.log10(Math.pow(255, 2)/MSE);
       JOptionPane.showMessageDialog(null, "Result : "+round(PSNR,2)+"db");
   }

Here is our PNSR using the lorem ipsum text:


Picture 3. PSNR Result

3.    The End

       We can conclude that PVD algorithm return a good result, but it's still need more improvement and modification to increase its performance. There are a lot of journal and paper that use its own method to modfy PVD algorithm. If you are interested, you also can learn many Steganography method beside PVD algorithm and combine them to get a better result.

      If you want to see and learn my code, I've also attached it in the attachment link below. Feel free to download!

      As an afterword, I would like to thank you for the readers and all person who have helped me in this tutorial. Hopefully this tutorial can help you to get more knowledge about Data security especially Steganography.  

     Learning source : http://citeseerx.ist.psu.edu/viewdoc/download?rep=rep1&type=pdf&doi=10.1.1.98.5855
ċ
SteganoPVD.zip
(30k)
Sujono _,
Jun 18, 2015, 1:02 AM