Main class
import java.io.File;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.JTextArea;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.swing.JFileChooser;
/*
*Steganography_Controller Class
*/
public class Steganography_Controller
{
//Program Variables
private Steganography_View view;
private Steganography model;
//Panel Displays
private JPanel decode_panel;
private JPanel encode_panel;
//Panel Variables
private JTextArea input;
private JButton encodeButton,decodeButton;
private JLabel image_input;
//Menu Variables
private JMenuItem encode;
private JMenuItem decode;
private JMenuItem exit;
//action event classes
private Encode enc;
private Decode dec;
private EncodeButton encButton;
private DecodeButton decButton;
//decode variable
private String stat_path = "";
private String stat_name = "";
/*
*Constructor to initialize view, model and environment variables
*@param aView A GUI class, to be saved as view
*@param aModel A model class, to be saved as model
*/
public Steganography_Controller(Steganography_View aView, Steganography aModel)
{
//program variables
view = aView;
model = aModel;
//assign View Variables
//2 views
encode_panel = view.getTextPanel();
decode_panel = view.getImagePanel();
//2 data options
input = view.getText();
image_input = view.getImageInput();
//2 buttons
encodeButton = view.getEButton();
decodeButton = view.getDButton();
//menu
encode = view.getEncode();
decode = view.getDecode();
exit = view.getExit();
//assign action events
enc = new Encode();
encode.addActionListener(enc);
dec = new Decode();
decode.addActionListener(dec);
exit.addActionListener(new Exit());
encButton = new EncodeButton();
encodeButton.addActionListener(encButton);
decButton = new DecodeButton();
decodeButton.addActionListener(decButton);
//encode view as default
encode_view();
}
/*
*Updates the single panel to display the Encode View.
*/
private void encode_view()
{
update();
view.setContentPane(encode_panel);
view.setVisible(true);
}
/*
*Updates the single panel to display the Decode View.
*/
private void decode_view()
{
update();
view.setContentPane(decode_panel);
view.setVisible(true);
}
/*
*Encode Class - handles the Encode menu item
*/
private class Encode implements ActionListener
{
/*
*handles the click event
*@param e The ActionEvent Object
*/
public void actionPerformed(ActionEvent e)
{
encode_view(); //show the encode view
}
}
/*
*Decode Class - handles the Decode menu item
*/
private class Decode implements ActionListener
{
/*
*handles the click event
*@param e The ActionEvent Object
*/
public void actionPerformed(ActionEvent e)
{
decode_view(); //show the decode view
//start path of displayed File Chooser
JFileChooser chooser = new JFileChooser("./");
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
chooser.setFileFilter(new Image_Filter());
int returnVal = chooser.showOpenDialog(view);
if (returnVal == JFileChooser.APPROVE_OPTION){
File directory = chooser.getSelectedFile();
try{
String image = directory.getPath();
stat_name = directory.getName();
stat_path = directory.getPath();
stat_path = stat_path.substring(0,stat_path.length()-stat_name.length()-1);
stat_name = stat_name.substring(0, stat_name.length()-4);
image_input.setIcon(new ImageIcon(ImageIO.read(new File(image))));
}
catch(Exception except) {
//msg if opening fails
JOptionPane.showMessageDialog(view, "The File cannot be opened!",
"Error!", JOptionPane.INFORMATION_MESSAGE);
}
}
}
}
/*
*Exit Class - handles the Exit menu item
*/
private class Exit implements ActionListener
{
/*
*handles the click event
*@param e The ActionEvent Object
*/
public void actionPerformed(ActionEvent e)
{
System.exit(0); //exit the program
}
}
/*
*Encode Button Class - handles the Encode Button item
*/
private class EncodeButton implements ActionListener
{
/*
*handles the click event
*@param e The ActionEvent Object
*/
public void actionPerformed(ActionEvent e)
{
//start path of displayed File Chooser
JFileChooser chooser = new JFileChooser("./");
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
chooser.setFileFilter(new Image_Filter());
int returnVal = chooser.showOpenDialog(view);
if (returnVal == JFileChooser.APPROVE_OPTION){
File directory = chooser.getSelectedFile();
try{
String text = input.getText();
String ext = Image_Filter.getExtension(directory);
String name = directory.getName();
String path = directory.getPath();
path = path.substring(0,path.length()-name.length()-1);
name = name.substring(0, name.length()-4);
String stegan = JOptionPane.showInputDialog(view,
"Enter output file name:", "File name",
JOptionPane.PLAIN_MESSAGE);
if(model.encode(path,name,ext,stegan,text))
{
JOptionPane.showMessageDialog(view, "The Image was encoded Successfully!",
"Success!", JOptionPane.INFORMATION_MESSAGE);
}
else
{
JOptionPane.showMessageDialog(view, "The Image could not be encoded!",
"Error!", JOptionPane.INFORMATION_MESSAGE);
}
//display the new image
decode_view();
image_input.setIcon(new ImageIcon(ImageIO.read(new File(path + "/" + stegan + ".png"))));
}
catch(Exception except) {
//msg if opening fails
JOptionPane.showMessageDialog(view, "The File cannot be opened!",
"Error!", JOptionPane.INFORMATION_MESSAGE);
}
}
}
}
/*
*Decode Button Class - handles the Decode Button item
*/
private class DecodeButton implements ActionListener
{
/*
*handles the click event
*@param e The ActionEvent Object
*/
public void actionPerformed(ActionEvent e)
{
String message = model.decode(stat_path, stat_name);
System.out.println(stat_path + ", " + stat_name);
if(message != "")
{
encode_view();
JOptionPane.showMessageDialog(view, "The Image was decoded Successfully!",
"Success!", JOptionPane.INFORMATION_MESSAGE);
input.setText(message);
}
else
{
JOptionPane.showMessageDialog(view, "The Image could not be decoded!",
"Error!", JOptionPane.INFORMATION_MESSAGE);
}
}
}
/*
*Updates the variables to an initial state
*/
public void update()
{
input.setText(""); //clear textarea
image_input.setIcon(null); //clear image
stat_path = ""; //clear path
stat_name = ""; //clear name
}
/*
*Main Method for testing
*/
public static void main(String args[])
{
new Steganography_Controller(
new Steganography_View("Steganography"),
new Steganography()
);
}
}
Front end
import java.awt.Color;
import java.awt.Insets;
import java.awt.Container;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import javax.swing.JMenu;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JTextArea;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.BorderFactory;
/*
*Class Steganography_View
*/
public class Steganography_View extends JFrame
{
//sie variables for window
private static int WIDTH = 500;
private static int HEIGHT = 400;
//elements for JPanel
private JTextArea input;
private JScrollBar scroll,scroll2;
private JButton encodeButton,decodeButton;
private JLabel image_input;
//elements for Menu
private JMenu file;
private JMenuItem encode;
private JMenuItem decode;
private JMenuItem exit;
/*
*Constructor for Steganography_View class
*@param name Used to set the title on the JFrame
*/
public Steganography_View(String name)
{
//set the title of the JFrame
super(name);
//Menubar
JMenuBar menu = new JMenuBar();
JMenu file = new JMenu("File"); file.setMnemonic('F');
encode = new JMenuItem("Encode"); encode.setMnemonic('E'); file.add(encode);
decode = new JMenuItem("Decode"); decode.setMnemonic('D'); file.add(decode);
file.addSeparator();
exit = new JMenuItem("Exit"); exit.setMnemonic('x'); file.add(exit);
menu.add(file);
setJMenuBar(menu);
// display rules
setResizable(true); //allow window to be resized: true?false
setBackground(Color.lightGray); //background color of window: Color(int,int,int) or Color.name
setLocation(100,100); //location on the screen to display window
setDefaultCloseOperation(EXIT_ON_CLOSE);//what to do on close operation: exit, do_nothing, etc
setSize(WIDTH,HEIGHT); //set the size of the window
setVisible(true); //show the window: true?false
}
/*
*@return The menu item 'Encode'
*/
public JMenuItem getEncode() { return encode; }
/*
*@return The menu item 'Decode'
*/
public JMenuItem getDecode() { return decode; }
/*
*@return The menu item 'Exit'
*/
public JMenuItem getExit() { return exit; }
/*
*@return The TextArea containing the text to encode
*/
public JTextArea getText() { return input; }
/*
*@return The JLabel containing the image to decode text from
*/
public JLabel getImageInput() { return image_input; }
/*
*@return The JPanel displaying the Encode View
*/
public JPanel getTextPanel() { return new Text_Panel(); }
/*
*@return The JPanel displaying the Decode View
*/
public JPanel getImagePanel() { return new Image_Panel(); }
/*
*@return The Encode button
*/
public JButton getEButton() { return encodeButton; }
/*
*@return The Decode button
*/
public JButton getDButton() { return decodeButton; }
/*
*Class Text_Panel
*/
private class Text_Panel extends JPanel
{
/*
*Constructor to enter text to be encoded
*/
public Text_Panel()
{
//setup GridBagLayout
GridBagLayout layout = new GridBagLayout();
GridBagConstraints layoutConstraints = new GridBagConstraints();
setLayout(layout);
input = new JTextArea(10,10);
layoutConstraints.gridx = 0; layoutConstraints.gridy = 0;
layoutConstraints.gridwidth = 1; layoutConstraints.gridheight = 1;
layoutConstraints.fill = GridBagConstraints.BOTH;
layoutConstraints.insets = new Insets(0,0,0,0);
layoutConstraints.anchor = GridBagConstraints.CENTER;
layoutConstraints.weightx = 1.0; layoutConstraints.weighty = 50.0;
JScrollPane scroll = new JScrollPane(input,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
layout.setConstraints(scroll,layoutConstraints);
scroll.setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
add(scroll);
encodeButton = new JButton("Encode Now");
layoutConstraints.gridx = 0; layoutConstraints.gridy = 1;
layoutConstraints.gridwidth = 1; layoutConstraints.gridheight = 1;
layoutConstraints.fill = GridBagConstraints.BOTH;
layoutConstraints.insets = new Insets(0,-5,-5,-5);
layoutConstraints.anchor = GridBagConstraints.CENTER;
layoutConstraints.weightx = 1.0; layoutConstraints.weighty = 1.0;
layout.setConstraints(encodeButton,layoutConstraints);
add(encodeButton);
//set basic display
setBackground(Color.lightGray);
setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
}
}
/*
*Class Image_Panel
*/
private class Image_Panel extends JPanel
{
/*
*Constructor for displaying an image to be decoded
*/
public Image_Panel()
{
//setup GridBagLayout
GridBagLayout layout = new GridBagLayout();
GridBagConstraints layoutConstraints = new GridBagConstraints();
setLayout(layout);
image_input = new JLabel();
layoutConstraints.gridx = 0; layoutConstraints.gridy = 0;
layoutConstraints.gridwidth = 1; layoutConstraints.gridheight = 1;
layoutConstraints.fill = GridBagConstraints.BOTH;
layoutConstraints.insets = new Insets(0,0,0,0);
layoutConstraints.anchor = GridBagConstraints.CENTER;
layoutConstraints.weightx = 1.0; layoutConstraints.weighty = 50.0;
JScrollPane scroll2 = new JScrollPane(image_input,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
layout.setConstraints(scroll2,layoutConstraints);
scroll2.setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
image_input.setHorizontalAlignment(JLabel.CENTER);
add(scroll2);
decodeButton = new JButton("Decode Now");
layoutConstraints.gridx = 0; layoutConstraints.gridy = 1;
layoutConstraints.gridwidth = 1; layoutConstraints.gridheight = 1;
layoutConstraints.fill = GridBagConstraints.BOTH;
layoutConstraints.insets = new Insets(0,-5,-5,-5);
layoutConstraints.anchor = GridBagConstraints.CENTER;
layoutConstraints.weightx = 1.0; layoutConstraints.weighty = 1.0;
layout.setConstraints(decodeButton,layoutConstraints);
add(decodeButton);
//set basic display
setBackground(Color.lightGray);
setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
}
}
/*
*Main Method for testing
*/
public static void main(String args[])
{
new Steganography_View("Steganography");
}
}
Image filter class
import java.io.*;
/*
*Image_Filter Class
*/
public class Image_Filter extends javax.swing.filechooser.FileFilter
{
/*
*Determines if the extension is of the defined types
*@param ext Extension of a file
*@return Returns true if the extension is 'jpg' or 'png'
*/
protected boolean isImageFile(String ext)
{
return (ext.equals("jpg")||ext.equals("png"));
}
/*
*Determines if the file is a directory or accepted extension
*@param f The File to run the directory/proper extension check on
*@return Returns true if the File is a directory or accepted extension
*/
public boolean accept(File f)
{
if (f.isDirectory())
{
return true;
}
String extension = getExtension(f);
if (extension.equals("jpg")||extension.equals("png"))
{
return true;
}
return false;
}
/*
*Supplies File type description
*@return Returns the String description
*/
public String getDescription()
{
return "Supported Image Files";
}
/*
*Determines the Extension
*@param f File to return the extension of
*@return Returns the String representing the extension
*/
protected static String getExtension(File f)
{
String s = f.getName();
int i = s.lastIndexOf('.');
if (i > 0 && i < s.length() - 1)
return s.substring(i+1).toLowerCase();
return "";
}
}
New class
import java.io.File;
import java.awt.Point;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.awt.image.DataBufferByte;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
/*
*Class Steganography
*/
public class Steganography
{
/*
*Steganography Empty Constructor
*/
public Steganography()
{
}
/*
*Encrypt an image with text, the output file will be of type .png
*@param path The path (folder) containing the image to modify
*@param original The name of the image to modify
*@param ext1 The extension type of the image to modify (jpg, png)
*@param stegan The output name of the file
*@param message The text to hide in the image
*@param type integer representing either basic or advanced encoding
*/
public boolean encode(String path, String original, String ext1, String stegan, String message)
{
String file_name = image_path(path,original,ext1);
BufferedImage image_orig = getImage(file_name);
//user space is not necessary for Encrypting
BufferedImage image = user_space(image_orig);
image = add_text(image,message);
return(setImage(image,new File(image_path(path,stegan,"png")),"png"));
}
/*
*Decrypt assumes the image being used is of type .png, extracts the hidden text from an image
*@param path The path (folder) containing the image to extract the message from
*@param name The name of the image to extract the message from
*@param type integer representing either basic or advanced encoding
*/
public String decode(String path, String name)
{
byte[] decode;
try
{
//user space is necessary for decrypting
BufferedImage image = user_space(getImage(image_path(path,name,"png")));
decode = decode_text(get_byte_data(image));
return(new String(decode));
}
catch(Exception e)
{
JOptionPane.showMessageDialog(null,
"There is no hidden message in this image!","Error",
JOptionPane.ERROR_MESSAGE);
return "";
}
}
/*
*Returns the complete path of a file, in the form: path\name.ext
*@param path The path (folder) of the file
*@param name The name of the file
*@param ext The extension of the file
*@return A String representing the complete path of a file
*/
private String image_path(String path, String name, String ext)
{
return path + "/" + name + "." + ext;
}
/*
*Get method to return an image file
*@param f The complete path name of the image.
*@return A BufferedImage of the supplied file path
*@see Steganography.image_path
*/
private BufferedImage getImage(String f)
{
BufferedImage image = null;
File file = new File(f);
try
{
image = ImageIO.read(file);
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null,
"Image could not be read!","Error",JOptionPane.ERROR_MESSAGE);
}
return image;
}
/*
*Set method to save an image file
*@param image The image file to save
*@param file File to save the image to
*@param ext The extension and thus format of the file to be saved
*@return Returns true if the save is succesful
*/
private boolean setImage(BufferedImage image, File file, String ext)
{
try
{
file.delete(); //delete resources used by the File
ImageIO.write(image,ext,file);
return true;
}
catch(Exception e)
{
JOptionPane.showMessageDialog(null,
"File could not be saved!","Error",JOptionPane.ERROR_MESSAGE);
return false;
}
}
/*
*Handles the addition of text into an image
*@param image The image to add hidden text to
*@param text The text to hide in the image
*@return Returns the image with the text embedded in it
*/
private BufferedImage add_text(BufferedImage image, String text)
{
//convert all items to byte arrays: image, message, message length
byte img[] = get_byte_data(image);
byte msg[] = text.getBytes();
byte len[] = bit_conversion(msg.length);
try
{
encode_text(img, len, 0); //0 first positiong
encode_text(img, msg, 32); //4 bytes of space for length: 4bytes*8bit = 32 bits
}
catch(Exception e)
{
JOptionPane.showMessageDialog(null,
"Target File cannot hold message!", "Error",JOptionPane.ERROR_MESSAGE);
}
return image;
}
/*
*Creates a user space version of a Buffered Image, for editing and saving bytes
*@param image The image to put into user space, removes compression interferences
*@return The user space version of the supplied image
*/
private BufferedImage user_space(BufferedImage image)
{
//create new_img with the attributes of image
BufferedImage new_img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
Graphics2D graphics = new_img.createGraphics();
graphics.drawRenderedImage(image, null);
graphics.dispose(); //release all allocated memory for this image
return new_img;
}
/*
*Gets the byte array of an image
*@param image The image to get byte data from
*@return Returns the byte array of the image supplied
*@see Raster
*@see WritableRaster
*@see DataBufferByte
*/
private byte[] get_byte_data(BufferedImage image)
{
WritableRaster raster = image.getRaster();
DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();
return buffer.getData();
}
/*
*Gernerates proper byte format of an integer
*@param i The integer to convert
*@return Returns a byte[4] array converting the supplied integer into bytes
*/
private byte[] bit_conversion(int i)
{
//originally integers (ints) cast into bytes
//byte byte7 = (byte)((i & 0xFF00000000000000L) >>> 56);
//byte byte6 = (byte)((i & 0x00FF000000000000L) >>> 48);
//byte byte5 = (byte)((i & 0x0000FF0000000000L) >>> 40);
//byte byte4 = (byte)((i & 0x000000FF00000000L) >>> 32);
//only using 4 bytes
byte byte3 = (byte)((i & 0xFF000000) >>> 24); //0
byte byte2 = (byte)((i & 0x00FF0000) >>> 16); //0
byte byte1 = (byte)((i & 0x0000FF00) >>> 8 ); //0
byte byte0 = (byte)((i & 0x000000FF) );
//{0,0,0,byte0} is equivalent, since all shifts >=8 will be 0
return(new byte[]{byte3,byte2,byte1,byte0});
}
/*
*Encode an array of bytes into another array of bytes at a supplied offset
*@param image Array of data representing an image
*@param addition Array of data to add to the supplied image data array
*@param offset The offset into the image array to add the addition data
*@return Returns data Array of merged image and addition data
*/
private byte[] encode_text(byte[] image, byte[] addition, int offset)
{
//check that the data + offset will fit in the image
if(addition.length + offset > image.length)
{
throw new IllegalArgumentException("File not long enough!");
}
//loop through each addition byte
for(int i=0; i<addition.length; ++i)
{
//loop through the 8 bits of each byte
int add = addition[i];
for(int bit=7; bit>=0; --bit, ++offset) //ensure the new offset value carries on through both loops
{
//assign an integer to b, shifted by bit spaces AND 1
//a single bit of the current byte
int b = (add >>> bit) & 1;
//assign the bit by taking: [(previous byte value) AND 0xfe] OR bit to add
//changes the last bit of the byte in the image to be the bit of addition
image[offset] = (byte)((image[offset] & 0xFE) | b );
}
}
return image;
}
/*
*Retrieves hidden text from an image
*@param image Array of data, representing an image
*@return Array of data which contains the hidden text
*/
private byte[] decode_text(byte[] image)
{
int length = 0;
int offset = 32;
//loop through 32 bytes of data to determine text length
for(int i=0; i<32; ++i) //i=24 will also work, as only the 4th byte contains real data
{
length = (length << 1) | (image[i] & 1);
}
byte[] result = new byte[length];
//loop through each byte of text
for(int b=0; b<result.length; ++b )
{
//loop through each bit within a byte of text
for(int i=0; i<8; ++i, ++offset)
{
//assign bit: [(new byte value) << 1] OR [(text byte) AND 1]
result[b] = (byte)((result[b] << 1) | (image[offset] & 1));
}
}
return result;
}
}