HideImage

In this example below you will see how to do a HideImage with some HTML / CSS and Javascript

Thumbnail
This awesome code was written by linky, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright linky ©
  • HTML
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>HideImage</title>
  
  
  
  
  
</head>

<body>

  <body>
  <h1>Hiding Image</h1>
  <h2>Images</h2>
  <p>
    Using steganographic principles, I wrote code to hide one image inside another, using a variable number of bits <i>(between 1 and 7, since we have 8 bits per colour per pixel.)</i>
  </p>
  <table>
    <tr>
      <td>
        <img src="https://s19.postimg.org/pg5fxj043/6597402918681735466.jpg" width="400" height="257" />
      </td>
      <td>
        <img src="https://s19.postimg.org/cd9te99w3/xianhu.jpg" width="400" height="257" />
      </td>
    </tr>
   <tr>
      <th>Image to display, pre-encoding</th>
      <th>Image to hide, pre-encoding</th>
    </tr>
    <tr>
      <td>
        <img src="https://s19.postimg.org/6632fdg0j/JJ100_Two.png" width="400" height="257" />
      </td>
      <td>
        <img src="https://s19.postimg.org/s6jevzyoj/xianhu_two.png" width="400" height="257" />
      </td>
    </tr>
   <tr>
      <th>6 bits of first image,<br />second image hidden in 2 bits</th>
      <th>Decoded image, recovered</th>
    </tr>
  </table>
  
  <h2>Algorithm Explanation</h2>
  
  <p>
    Rather than explaining the full code, I'll focus on the changes required to support n-bit encoding and decoding. I also cleaned up the code such that a user just has to call one function to encode or decode images - <code>encodeImages()</code> and <code>decodeImages()</code>. Each of these functions supports an argument <code>nBits</code> which tells the function how many bits should be used to encode the hidden image. <i>(Or how many bits were used to encode the image, in the case of the <code>decodeImage()</code> function.)</i>
  </p>
  
  <h3>encodeImages( image1, image2, nBits )</h3>
  <p>
    Takes two images and the number of bits to use for the hidden image. It takes care of cropping images, clearing the lower bits of the visible image, shifting the bits for the hidden image and adding the pixel values together, all by calling the functions below. <code>nBits</code> was added as an argument to all relevant functions to allow the number of bits to be varied easily.
  </p>
  
  <h3>cropImage( image, width, height )</h3>
  <p>
    Crops an image to the specified size. No change required to support variable length encoding.
  </p>
  
  <h3>zeroLowerBits( image, nBits )</h3>
  <p>
    We want to zero the lower <code>nBits</code> of the visible image. I added the <code>nBits</code> argument to allow variable length bit encodings of the hidden image.  I then used <code>mask = Math.pow( 2, ( 8 - nBits ) )</code> to calculate the bits I would need to leave. Since we're using <code>nBits</code> to encode the hidden image, this leaves <code>8 - nBits</code> for the visible image.
  </p>
  
  <h3>shiftImageBitsRight( image, nBits )</h3>
  <p>
    We want to shift the most significant bits of the hidden image to the left, to use only <code>nBits</code>. Again, the <code>nBits</code> argument was added to allow variable bit lengths. In this case I needed to shift the bits of the hidden image to the right, equivalent to dividing them by a power of 2. Since the image had to hide in <code>nBits</code>, this meant right-shifting by <code>8 - nBits</code> - the same number of bits we cleared in the <code>zeroLowerBits()</code> function above. Getting the floor value of the division ensured the result was truncated, making it equivalent to the right shift we required.
  </p>
  
  <h3>combineImages( image1, image2 )</h3>
  <p>
    Adds the visible and hidden image together, pixel by pixel. No change required for variable length encoding.
  </p>
  
  <h3>decodeImage( image, nBits )</h3>
  <p>
    What use is an encoder without a decoder? This function recovers a hidden image if you tell it how many of the lower bits to use. In this case, I generated a bit mask and used masking and bit-shifting native operations instead of multiplying and dividing by powers of 2. If you look at the code, you can see that a binary mask can be generated for the lower bits <i>(that we want to keep, as they are the hidden image)</i> by using <code>mask = Math.pow( 2, nBits ) - 1</code>.
  </p>
  <p>
      For <code>nBits = 2</code>, this would generate the binary <code>00000011</code> - which we can then boolean AND <i>(the <code>&</code> operator in javascript)</i> with the encoded image to keep only the bits of the hidden image. This would make a very dark, difficult to see version of our recovered image, so we should shift those bits left, back to where they came from, by <code>( 8 - nBits )</code>. This is the opposite of what the <code>shiftImageBitsRight()</code> did to encode the image. Javascript has a bit-shift operator, <code>&lt;&lt;</code> for shift left, so we use it.
    </p>  
    <p>
      For example, the red value of each pixel being masked off to keep only the lower bits, and then shifted left to its original place is in the code: <br />
      <code>px.setRed( ( ( p.getRed() & mask ) &lt;&lt; ( 8 - nBits ) ) );</code><br />
      Exactly the same is done for the green and blue pixels.
  </p>
  
  
  <h2>Hidden Message</h2>
  <table>
    <tr>
      <td>
        <img src="http://ares.signal2noise.ie/scratch/coursera/week4_steg_2bit_Question.png" />
      </td>
      <td>
        <img src="http://ares.signal2noise.ie/scratch/coursera/week4_steg_2bit_Solution.png" />
      </td>
    </tr>
    <tr>
      <th>Image with 2-bit<br />hidden image encoded</th>
      <th>2-bit image<br />recovered</th>
    </tr>
  </table>

  <p>
    The hidden message is &quot;Program testing can be used to show the presence of bugs, but never to show their absence! E. Dijkstra"
  </p>
  
  <p>
    The hidden message was decoded with the code below, using the <code>decodeImage()</code> function.
  </p>
  
  <h2>Javascript Program Code</h2>
  
  <code>
    <pre>
// Steganographically hide one image in another...

// USAGE:
//   To combine images: encodeImages( img1, img2, nBits )   - nBits = no. of bits for hidden image.
//   To decode images: decodeImage( img, nBits )   - nBits = no. of bits used to hide hidden image.


////////////////////////////////////////////////////////////////////////////////
// cropImage : Crop an image to the passwd width and height.
//   returns an image with the passed dimensions.
function cropImage( image, width, height ) {
    var imgOut = new SimpleImage( width, height );

    // Copy the image pixel by pixel...
    for( x = 0; x < width; x++ ) {
        for( y = 0; y < height; y++ ) {
            var p = image.getPixel( x, y );
            var p_out = imgOut.getPixel( x, y );
            
            // Copy the Red, Green and Blue values of the source to destination image...
            p_out.setRed( p.getRed() );
            p_out.setGreen( p.getGreen() );
            p_out.setBlue( p.getBlue() );
        }
    }
    
    // Return the new, cropped image...
    return imgOut;
}


////////////////////////////////////////////////////////////////////////////////
// zeroLowerBits : zeros the nBits lower bits of each color of each pixel in
//   the passed in image.
//   returns an image with the lower nBits of each pixel/color zeroed.
function zeroLowerBits( image, nBits ) {
    // nBits should be between 1 and 7 bits, for 8-bit colour images...
    if( nBits < 1 || nBits > 7 ) {
        // Log an error and return the image unaltered...
        print( "ERROR! : Invalid number of bits passed to zeroLowerBits. 1 < nBits < 7.");
        return image;
    }
    
    // Generate a bit mask for the upper bits we want to keep...
    var mask = Math.pow(2, nBits );
    
    // Mask each pixel colour with the mask...
    for( var p of image.values() ) {
        p.setRed( Math.floor( p.getRed() / mask ) * mask );
        p.setGreen( Math.floor( p.getGreen() / mask ) * mask );
        p.setBlue( Math.floor( p.getBlue() / mask ) * mask );
    }
    return image;
}


////////////////////////////////////////////////////////////////////////////////
// shiftImageBitsRight : Shift the bits of an image to the right, zeroing the
//   MSBs as we go, UNTIL ONLY nBits remain.
//   returns an image with only nBits LSBs populated (and 8-nBits MSBs zeroed.)
function shiftImageBitsRight( image, nBits )
{
    // nBits should be between 1 and 7 bits, for 8-bit colour images...
    if( nBits < 1 || nBits > 7 ) {
        // Log an error and return the image unaltered...
        print( "ERROR! : Invalid number of bits passed to shiftImageBitsRight. 1 < nBits < 7.");
        return image;
    }
    // Shift the bits of each pixel value to the left (MSB -> LSB) for a hidden image.
    // This is equivalent to dividing by 2^nBits, so if nBits = 4, dividing by 16...
    var divisor = Math.pow( 2, ( 8 - nBits ) );
    
    for( var p of image.values() ) {
        p.setRed( Math.floor( p.getRed() / divisor ) );
        p.setGreen( Math.floor( p.getGreen() / divisor ) );
        p.setBlue( Math.floor( p.getBlue() / divisor ) );
    }
    return image;
}



////////////////////////////////////////////////////////////////////////////////
// combineImages : Adds corresponding colours of each pixel in two images together.
//   Assumes both images are the same size and have been  preprocessed such that
//   adding pixel values will not overflow (but logs an error if that happens.)
//   returns an image.
function combineImages( image1, image2 ) {
    // Combines two images by adding pixel values together...
    // NOTE: Assumes both images are the same size!
    var imgCombined = new SimpleImage( image1.getWidth(), image1.getHeight() );
    
    for( var p1 of image1.values() ) {
        var x = p1.getX();
        var y = p1.getY();
        var p2 = image2.getPixel( x, y );
        var p_out = imgCombined.getPixel( x, y );
        
        //Combine the idividual pixel values...
        if( p1.getRed() + p2.getRed() <= 255 ) {
            p_out.setRed( p1.getRed() + p2.getRed() );
        } else {
            debug( "WARNING! : combineImages, at (x, y) red value expected <=255, got ", p1.getRed() + p2.getRed() );
            p_out.setRed(255);      // We've overflowed and lost data here!
        }
        if( p1.getGreen() + p2.getGreen() <= 255 ) {
            p_out.setGreen( p1.getGreen() + p2.getGreen() );
        } else {
            debug( "WARNING! : combineImages, at (x, y) green value expected <=255, got ", p1.getGreen() + p2.getGreen() );
            p_out.setGreen(255);      // We've overflowed and lost data here!
        }
        if( p1.getBlue() + p2.getBlue() <= 255 ) {
            p_out.setBlue( p1.getBlue() + p2.getBlue() );
        } else {
            debug( "WARNING! : combineImages, at (x, y) blue value expected <=255, got ", p1.getBlue() + p2.getBlue() );
            p_out.setBlue(255);      // We've overflowed and lost data here!
        }
    }
    return imgCombined;
}



////////////////////////////////////////////////////////////////////////////////
// encodeImages : Hides image2 in image1, using nBits to encode the second image,
//   and (8 - nBits) to encode the first, visible image, image1.
//   returns an image.
function encodeImages( image1, image2, nBits ) {
    // nBits should be between 1 and 7 bits, for 8-bit colour images...
    if( nBits < 1 || nBits > 7 ) {
        // Log an error and return the image to be hidden unaltered...
        print( "ERROR! : Invalid number of bits passed to encodeImages. 1 < nBits < 7.");
        return image2;
    }
    
    // 1. Crop the two images to be the same size, using the smaller dimensions of each image...
    var cropWidth = Math.min( image1.getWidth(), image2.getWidth() );
    var cropHeight = Math.min( image1.getHeight(), image2.getHeight() );
    var imgMain = cropImage( image1, cropWidth, cropHeight );
    var imgHide = cropImage( image2, cropWidth, cropHeight );
    
    // 2. Zero the lower bits of the main image...
    imgMain = zeroLowerBits( imgMain, nBits );
    
    // 3. Shift the bits of the image we want to hide...
    imgHide = shiftImageBitsRight( imgHide, nBits );
    
    // 4. Combine the two processed images...
    imgOut = combineImages( imgMain, imgHide );
    
    // Return the processed image, now with another image hidden in it...
    return imgOut;
}


////////////////////////////////////////////////////////////////////////////////
// decodeImage : Recovers a hidden image from the passed in image, using the
//   lower nBits to generate the recovered image.
//   returns an image.
function decodeImage( image, nBits ) {
    var width = image.getWidth();
    var height = image.getHeight();
    var imgOut = new SimpleImage( width, height );
    
    var mask = Math.pow( 2, nBits ) - 1;
    
    for( var p of image.values() ) {
        var x = p.getX();
        var y = p.getY();
        var px = imgOut.getPixel( x, y );
        
        // Get the LSB (4 bits) and set if as the MSB for the output image...
        // Mask out the upper bits (since javascript will cast up to a 32-bit int)...
        px.setRed( ( ( p.getRed() & mask ) << ( 8 - nBits ) ) );
        px.setGreen( ( ( p.getGreen() & mask ) << ( 8 - nBits ) ) );
        px.setBlue( ( ( p.getBlue() & mask ) << ( 8 - nBits ) ) );
    }
    return imgOut;
}


var imgMain = new SimpleImage( "jj100.jpg" );
var imgHide = new SimpleImage( "xianhu_hide.jpg" );

// Only need these four lines to generate the cropped images for posting on the web...
var cropWidth = Math.min( imgMain.getWidth(), imgHide.getWidth() );
var cropHeight = Math.min( imgMain.getHeight(), imgHide.getHeight() );
var imgMain = cropImage( imgMain, cropWidth, cropHeight );
var imgHide = cropImage( imgHide, cropWidth, cropHeight );

// Encode Usain image in Astrachan image using 2-bits...
imgOutput = encodeImages( imgMain, imgHide, 2 );

print( imgMain );
print( imgHide );
print( imgOutput );

// Decode the hidden image, encoded using 2 bits...
var imgHidden = decodeImage( imgOutput, 2 );
print( imgHidden );
    </pre>
  </code>
</body>
  
  

</body>

</html>

Comments