In developing and writing your code, please use the classes provided as part of this assignment as a starting point. You'll need a basic understanding of pictures/pixels and text/bytes to hide and extract pictures and images, respectively. Details on these and other aspects of the code you'll develop are provided here as are some hints in developing the programs.
There are five sections in this howto: an overview
section that provides background on images and pixels and how the
programs work; an explanation of pixels and the
details you'll need for HideImage
and
ExtractImage
; an explanation of hiding text
and bit-chunks you'll need for HideText
and
ExtractText
; details about the design of
the code for each class you must write; and a caveat section which you should review before coding
and while coding;
Overview and Development
First we'll provide details about
how pictures are comprised of pixels represented in the programs you'll
write by the java.awt.Color
class.
A brief big-picture look at the ideas used in these programs is provided here to help guide your reading of the more detailed explanations that follow.
The key to hiding source image or text in a target image is to use some bits of each pixel in the target image to store some bits from the hidden source. When hiding source text, you'll hide as many characters as possible --- except for really long text or really small target images you'll be able to hide the entire text without substantially degrading the image. When hiding a source image, you'll reduce the quality of the source image and then hide this reduced quality image.
For example, the class/program ClearBitsFromImage clears the low-order bits from each (R,G,B) value in each pixel. The table below shows the gradual degradation of an image as the number of cleared bits increases. It's pretty amazing that in clearing seven of the eight bits in each RGB value of each pixel (image on far right below) that the original image is still discernible.
Original | Clear 2 bits | Clear 4 bits | Clear 6 bits | Clear 7 bits |
---|---|---|---|---|
| ||||
| ||||
HideImage
works
you may need to implement ExtractImage
, although you can
gain some confidence that hiding an image works by using the
program PixelPng which prints the RGB
values of the first several (user-prompted) pixels in an image.
For example, what does it mean to use two bits in each RGB value to hide an image? You'll clear the low-order two bits of each RGB value in each pixel of the target, e.g., the hidden image will start looking like the image second from the left in the table-above. However, instead of leaving these two bits as 0 (or clear) you'll store two bits from the source image.
To store source information in only two bits you'll need to scale or reduce the the source information. You'll scale or reduce each RGB value of the source image to fit into two bits, e.g., you'd store the image second from the right in the table above if that was the source image since clearing six bits essentially means taking an eight-bit RGB value such as 255 (11111111) and reducing it to a two-bit value of 3 (11) --- dividing by 64 = 26. Similarly the eight-bit RGB 128 would be reduced to the three-bit value 2 (dividing again by 64), but 127 would be reduced to 1. More details are found below.
Pixels
Images are made of individual picture elements or
pixels. Although there are different color models, including gray-scale,
RGB, and CMYK, we'll be using the standard RGB or red, green,
blue color model. You can find
details in the Wikipedia
RGB entry. For the purposes of this assignment you need to
understand that each pixel is made of a red, green, and blue value,
and each of these values is an integer between 0 and 255
(inclusive). For example, you can extract the values of a pixel
represented by the java.awt.Color
class we are using as
shown in the code below. This code illustrates how to construct
a Color
object representing a pixel from RGB values and
how to extract the individual RGB values from a pixel/Color.
The program ClearBitsFromImage shows the
standard way you'll loop over every pixel in a image, processing each
pixel to create a new image. You'll use similar code in all the classes
you write to hide and extract text and images. The code in HideImage has been started for you as
well, it's similar to the clearing-bits code. The method
reduce
from ClearBitsFromImage
constructs a new
pixel/Color, you'll write a modified but similar method for the other
classes. The basic idea is to process every pixel, creating a new pixel
for the image being produced (e.g., when hiding text or an image or
extracting an image). Sometimes the methods you write will have multiple
parameters, e.g., a source and target pixel as well as the number of
bits in writing HideImage
.
Here's the relevant code from ClearBitsFromImage
:
If you change the body of method reduce
as shown in the
code fragment below, the effects are shown by the images below the
code. Magenta is a combination of maximal red and blue values, which is
why the rightmost image has a magenta hue.
Original | VALUE=255 (all green) | VALUE=0 (no green) |
---|---|---|
In the diagrams below the process of hiding a source eight-bit RGB value in the target value is explained. You don't need to use binary values in any calculations for this program, the binary values are shown to provide a deeper understanding of the process of hiding and extracting image values. When changing pixels you'll change each of the three eight-bit R, G, and B values. Only one eight-bit value is shown in the pictures below.
The target value in which we'll hide information is 179. This could be
either the R, G, or B value from a Color/pixel. We're using two bits to
store hidden information, so we clear two bits by
dividing and multiplying by 4 = 22 (e.g., see method
reduce above).
| |
The source value we're hiding is 142. Since we have two bits in which to hide the value we must hide a two-bit value: either 0, 1, 2, or 3. To determine what value to hide we calculate 142/64 = 2. We choose 64 since we want to hide 2-bits which can represent four values and 256/4 = 64 (or you can think of hiding two-bits as using 64 because it's equal to 2(8-2) = 26). If we were using one bit to hide a value, we'd divide by 128 since we can represent two values with one bit and 256/2 = 128. If we we had 3 bits to hide information in we'd divide by 32 since we can represent eight values with three bits and 256/8 = 32. | |
The target value of 179 has been replaced by 178 in the new image --- this is the value used in the constructed image that represents both the target image bits (176) and the hidden, source image bits (2). The bits in red are the bits we're hiding. | |
When we're extracting the hidden image we need to know that two bits have been used to hide a value. We extract the hidden value by calculating 178 % 4 = 2 since 178 = 44*4+2. The value used to create the corresponding 8-bits of a pixel in the reconstructed hidden image is 2*64 = 128. We rescale by 64 since that's the number we divided by when hiding the original source value of 142. In our newly constructed/extracted image We've now got 128 as the eight-bit value that was originally 142 in the source image before the 142 was scaled down and hidden. | |
Information is stored using binary values, but you don't need to use binary arithmetic in doing the simple arithmetic operations that are part of inserting and extracting values/bits. Although you don't need to use the binary representations in the code you write, understanding the binary may help when you debug. However, you can do this entire assignment using only decimal/base-10 arithmetic operations.
Suppose a source pixel/color is represented by the RGB triple (57, 108, 213). In base two this triple is (00111001, 01101100, 11010101). What happens if you want to store/hide this source pixel of (57, 108, 213) using two bits? If you are using two bits to store this value you must reduce each value by a factor of 64 so that it's one of four different values since you can represent four values with two bits: 0, 1, 2, 3. Reducing (57, 108, 213) by dividing by 64 yields (1, 1, 3). These are the values that would be hidden. When they're extracted, the values would be multiplied by 64 yielding (64, 64, 192). As a result, the original source pixel of (57, 108, 213) is hidden and then extracted as (64, 64, 192).
Alternatively, suppose the RGB triple (57, 108, 213) is a target pixel/value in which information will be hidden using two bits. To clear two bits you divide and multiply by 4 as shown in the diagram above, e.g., you replace 57 by 57/4*4 = 56. This results in replacing (57, 108, 213) by (56, 108, 212). In binary these values are (00111000, 01101100, 11010100). Note that the rightmost two bits of each value are zero -- they've been "cleared". In the image you create you don't store these cleared values in a pixel, you use them to add the hidden information from the source, and then you store them in a pixel.
You'll use regular decimal/arithmetic operations to reduce values unless
you have a good understanding of bit operators. You can convert between
base-10 and base-2 using the Google query 57 in base 2
or
0b00011011 in base 10
. You can debug by using the static
Integer.toBinaryString(i)
method that returns a string
representing the base two/binary version of an int value. You can
examine individual pixels using the
PixelPng class.
hide
that works on pixels.
The R,G,B values have been obtained and cleared of the specified number
of bits, e.g., to clear 1578 to 1500 in base 10 you
could simply divide by 100 and then multiply by 100: that clears two
decimal digits. You do the same thing with 4 instead of 100 to clear two
bits (binary digits).
If you're clearing two bits You can store any of the values 0, 1, 2, or 3 in the two cleared bits since you can represent four values with two bits. For each RGB value in each pixel of the source (to be hidden) image you divide the RGB value by 64 since the original values were in the range 0-255 and you need to map them to 0-3. In general, you scale down by dividing each R,G,B, value by 256/(2bits).
You'll likely need to write ExtractImage
to see that you've
hidden an image successfully. When extracting RGB values from each pixel
of an image that hides two-bits per pixel you'll get 0, 1, 2, or 3. When
creating a new image, scale these by multiplying by 64 to get a value in
the range 0-255. You can use the PixelPng class to help debug --- it prints
(R,G,B) pixel values from an image.
Text
In computing text is represented using characters and each character is
represented by an ASCII or Unicode value. Java uses Unicode to represent
each character, and Unicode uses 16 bits/character. However, we'll
ignore the ASCII/Unicode distinction and treat text/strings as a
sequence of bytes, where bytes are eight bit values. The text stored in
files on computers is typically stored in bytes, and two bytes can be
combined to create a Unicode character when that's what's actually
stored, just as four bytes can be combined to create an int when that's
what is stored. For this suite of programs we'll treat Strings as simply
a sequence of bytes. This simplifies the process of hiding text.
Fortunately, the String
class provides the
getBytes
method as shown below. Using
printf
makes it simple to print a value as either a
character or an integer to illustrate what's going on.
Code | Output |
---|---|
|
a 97 1100001 b 98 1100010 c 99 1100011 d 100 1100100 32 100000 e 101 1100101 f 102 1100110 g 103 1100111 |
To hide text in an image, you'll need to convert an entire file to a
sequence of bytes. We can do that simply in Java with the
following code that leverages the power of the
java.util.Scanner
class.
Once you're converted a file to a sequence/array of bytes, you'll hide each of these bytes using by clearing either one or two bits of each RGB value in each pixel of the target image in which the text will be hidden. You clear either one or two bits using techniques and arithmetic operations described above in the section on hiding images. However, when hiding text, you don't reduce the information content of the text by scaling as was done with the hidden image. If the target image doesn't have enough pixels in which to hide all the text, hide as much as can fit. This means you'll need to break the text into a sequence of 1- or 2-bit chunks to hide the entire text. More on this below.
The process of hiding text in an image is similar to the process of hiding one image in another, but in the image hiding code we made the assumption that both images were the same size in width and height. Thus one pair of nested loops was sufficient to process both source and target pixels.
In hiding text, you'll need to either loop over pixels in the target image or bytes/bits in the source text. Do not try to loop over both at the same time. For example, if you loop over pixels in the target image, you'll need to hide different bits from the source text in each RGB value of each pixel. If these bits are in an array, you can access them simply by indexing into the array and incrementing the index after each access. It's recommended that you store all bytes in an array that's globally accessible (or a parameter) and then loop over each pixel storing 1 or 2 bits in each (R,G,B) value of each pixel. The recommendation is to loop over pixels, keeping a counter/index into the byte array of text.
When hiding text, you'll store either one- or two-bit chunks of text as specified by the user. One easy way to access and extract one or two bits at a time is to break the entire array of bytes into a larger array of appropriate size chunks. For example, when using two-bit chunks in hiding text whose size is 200 bytes you could first create an array of 800 bytes where each of the original 200 bytes is broken into four different two-bit chunks. You'll need to extract these text bit-chunks from each byte. Perhaps the simplest way to extract the chunks is to use code similar to the following which prints each decimal digit of a number from least significant digit to most significant using mod/div operators -- this code shows how to use the arithmetic operators with base-10 digits.
code | output |
---|---|
|
value of 12345 backwards: 54321 |
Using values of 2 or 4 rather than 10 when extracting digits in the loop above would result in getting eight one-bit chunks or four two bit chunks/byte, respectively. Of course you'll need to change the number of times the loop iterates, e.g., eight or four times depending on whether you're extracting 1 or 2 bits, respectively (the loop above iterates five times because the int has five digits). However, the code above results in extracting digits (or bits) in order from least to most significant just as the 5 is printed first in the code fragment above. You'll need to somehow reverse this order, or extract differently, so that your code will store the most significant bit-chunk from each byte first, then the next most significant, and finally the least significant bit(s). You could, of course, store values in an array and then reverse the order of the values.
For example, if you're using two bits per RGB value to hide the value
whose binary representation is given by 01110001
(decimal 113)
you'll need to store the two-bit chunks in the order
01
, 11
, 00
and
01
. If the first three chunks are stored in one pixel,
the last chunk, 01
will be
hidden/stored in the next pixel. Again, one simple way to do
this is to store each two-bit chunk in an array and index them one-at-a-time.
One way to make the process of getting bits from a byte simpler is to
create a new byte-array from the one obtained from
String.getBytes
. The new array would be either four
or eight times bigger depending on whether you're using two or one
bit-sized chunks, respectively. This method is fine to use, though
more memory intensive than avoiding the creation of the additional
array. However, creating the second array makes it much simpler to
iterate over the array when creating new pixels/Colors from the
target image in which to store the chunks representing the hidden
message. You can also extract bits using arithmetic operations or
bit-shifting operations.
For example, you could modify the extract-bytes-from Scanner code above as follows:
If you're using % and / to extract bits/digits as shown above, e.g., you're using % 2 or % 4 to extract 1 or 2 bits, respectively, you'll want to extract these from an int not from a byte. If you use a byte you'll have problems with negative values when using the % operator.
To fix this problem, access each byte in turn as described above. But don't use % and / on the byte value, convert the byte to an int as follows:
int
value created
from the byte use the bit-wise & operator with 0xFF, problems
with negative values and bad colors should go away.
You'll need to write code that creates the eight-bit byte 01101101 from these two-bit chunks. To see how to do this, consider creating a number from the array of digits below which prints the value 23481.
Once you have a byte, you can cast it to the type char and append it to a StringBuilder object you maintain to represent the text being extracted.
Code and Class Details
The class ClearBitsFromImage
illustrates one way to organize code for the classes you write in this
assignment. One idea that can help as you develop your programs is to
write a method that returns a new Picture
based on the
parameters to the method. In ClearBitsFromImage the method
clear
does this -- and you can see its use in the
main
method. Writing a method to alter each pixel, as
illustrated by the method reduce
is also a good
idea. Developing the code by isolating functionality in a method helps
when debugging and checking that your code is working properly.
Starter classes for HideImage and ExtractImage are provided to take care of some of the boilerplate code in opening files.
When developing ExtractText
it will help to write a method
that returns the String
representing the extracted
text. Writing such a method will help as you try to find all the images
in a directory that contain text: part of writing
StegoBenchmark
. It's not a good idea for the method that
extracts text to simply print the text. If you return the text, you can
print the string returned if you want to, e.g., for
testing/debugging. But you can also pass the string to other methods,
e.g., for determining if the string represents text or is simply
gibberish.
This means three of the four hide/extract classes you write will have a
method that returns an Picture
object.
You should make sure that you call the show
method on this
object since that pops up a frame in which the image appears
and the frame has a Save-option that allows the user to save
the image. When writing ExtractText
you won't
generate a new Picture
, you'll generate the
String extracted from an image. You should likely print this
String in the main
method you write, but you
won't print it when you call the method from
StegoBenchmark.
String.split
method, e.g., the code below breaks a String
on white-space into an array of "words" -- this is typically how
we break text represented by a String into its component words.
Character.isLetter
returns true if its parameter is a
letter (think 'a'-'z', upper or lowercase). Of course you can also use a
dictionary of English words, but for identifying text this is probably
not necessary.
Caveats
Here's a list of things to keep in mind when developing these
programs. These caveats can lead to subtle and hard-to-find bugs if
you're not careful in avoiding them.
Picture
class supports saving with either a .jpg or a .png suffix. If you don't
supply
a prefix .png is used by default.
However, .png is a
lossless compression scheme whereas .jpg is lossy -- information is lost
when compressing with the jpg format. You need to keep all the
information when you hide text or an image so you must use a .png
suffix when saving images using the Picture
class.
String.getBytes
method which
returns an array of bytes, use int
values. Bytes in
Java are signed, i.e., have values between -128 and 127.
ExtractText
must be able to extract
text from the images in which your program
HideText
hid the information, but ideally your
program should be able to extract text from every image in
which the text is stored most-significant-bits first, e.g., from images
in which your classmates' code hid text.
Picture
objects in
HideImage
, ExtractImage
and
HideText
. Be sure that each of these classes has a
main
method that allows the user to hide/extract
information. The Picture
object created should be made
visible to the user by calling its show
method, this allows
the user to save the image. Each of these classes and
ExtractText
should have a main
method that
allows them to run, prompting the user for files and bits as
appropriate.