Image Puzzles

Below you can find a few puzzles about manipulating images by directly getting and setting the image pixels. In general, the goal of each of the puzzles is to take an image which has been transformed in some way and transform it back (as much as possible) to uncover the original image. The five puzzles increase in difficulty as you go down the page. Puzzle 0 is an introduction and has been completed for you, and Puzzle 4 is a somewhat tricky puzzle that requires bitwise operations on the pixels. There is also a bonus puzzle that is even more difficult than Puzzle 4.

This activity is based on the original “Nifty Image Puzzles” activity by Nick Parlante, though this page does not share any of the code used in the original.

Puzzle 0

The purpose of this puzzle is to introduce you to manipulating images with the SimpleImage class, which has been provided to make loading images and manipulating the image pixels easier. The solution for this puzzle has already been provided for you in the code editor below. You can click on the green triangle just above the editor to run the provided code.

In this puzzle, the image you are given, puzzle-0.png, has had its red, green, and blue channels rearranged—the values for the red, green, and blue channels have been put in the green, blue, and red channels, respectively. To undo this change, it is necessary to get the red, green, and blue channels in the provided image and set them as the values for the blue, red, and green channels, respectively.

This puzzle demonstrates many of the elements that will be necessary for the later puzzles.

First, you can create a new SimpleImage for an image at a given URL by calling the SimpleImage constructor with the new keyword. In the provided sample code, we load an image called puzzle-0.png as follows.

im = new SimpleImage("puzzle-0.png");

You can then write a function to process the image. In this example, the function is simply named process. Your processing function should accept one argument—the image to process. (You could also simply use your image from the enclosing scope of the function—i.e., im in this example—but it is better to use the argument since it allows us to call the same function for other images.) Your function should also return the image you want displayed; usually, this will be the same image that was passed as an argument to the function.

function process(img) {
    // Your processing code here ...
    // ...
    return img; 
}

In all the puzzles on this page (possibly excluding the bonus puzzle), you can perform your manipulations inside two nested for loops. The outer loop moves along the x axis, ensuring every vertical column in the image is processed. The inner loop moves along the y axis, ensuring every pixel within every vertical column is processed. In other words, the nested for loops allow you to hit every (x, y) coordinate in the image, and, hence, every pixel. Notice that we get the width and height of the image with img.width and img.height below.

for (x = 0; x < img.width; x++) {
  for (y = 0; y < img.height; y++) {
    // Change pixels in here.
  }
}

We can get a channel of a given pixel by calling the getRed, getGreen, or getBlue functions with the x and y coordinates of the pixel. For example,

var red = img.getRed(x, y);
var green = img.getGreen(x, y);
var blue = img.getBlue(x, y);

Likewise, we can set channels of a given pixel by calling the setRed, setGreen, and setBlue with the x and y coordinates of the pixel and the new value. For example,

img.setRed(x, y, green);
img.setGreen(x, y, blue);
img.setBlue(x, y, red);

At the end of your code, you need to return something. For these puzzles, you can think of it as returning the processed image to the page to be displayed. (This is not quite accurate, though; we’re actually returning a Promise that the page can eventually use to process the image and then display the image asynchronously.) Although it may appear that we are returning from outside a function (which is not allowed in JavaScript), the code you write is actually being run in a function for you.

The easiest way to get an object to return is to call the process method of your image with the function you want to use for processing. For example,

return img.process(process);

Again, the complete code for this puzzle is below. You may want to play with this puzzle to get an idea of how you can use a SimpleImage.

Run code

Puzzle 1

In this puzzle, the image you are given has been manipulated in such a way that the red and green channels have been set to random noise, and the blue channel has been divided by 8. The red and green channels of the original image cannot be recovered now; you will need to set them to zero. The blue channel should be multiplied by 8 to undo the previous division.

The image you will be working with for this puzzle is named puzzle-1.png.

Run code

Puzzle 2

The image you are given was manipulated using the following JavaScript code. You need to undo this transformation.

function process(img) {
    for (x = 0; x < img.width; x++) {
      for (y = 0; y < img.height; y++) {
          img.setRed(x, y, Math.sqrt(img.getGreen(x, y)));
          img.setGreen(x, y, Math.random()*255);
          img.setBlue(x, y, Math.random()*255);
      }
    }
    return img;
}

(Math.sqrt computes the square root of a number.)

The image you will be working with for this puzzle is named puzzle-2.png.

Run code

Puzzle 3

All channels of the image you are given have been changed according to the following formula.

c := (c*35 + 40)%256

For this puzzle, it may help to know about the getChannel and setChannel functions, which allow you to access or change one channel of a pixel by using the channel’s index—0 for red, 1 for green, and 2 for blue. This means you can loop over the channels and avoid repeating the same code for each channel. getChannel can be used as follows.

var red = img.getChannel(x, y, 0);
var green = img.getChannel(x, y, 1);
var blue = img.getChannel(x, y, 2);

setChannel can be used as follows.

// Set red to 128.
img.setChannel(x, y, 128, 0);
// Set green to 64.
img.setChannel(x, y, 64, 1);
// Set blue to 32.
img.setChannel(x, y, 32, 2);

The image you will be working with for this puzzle is named puzzle-3.png.

Run code

Puzzle 4

Steganography is the practice of hiding secret information so that the presence of the secret information goes undetected. This is in contrast to cryptography, which seeks to ensure that only those for whom the information is intended can decrypt the information but does not try to prevent others from detecting that there is secret information.

One way of hiding information in images is to alter some of the least significant bits of the image’s pixels; such changes usually have little impact on our perception of the image because changes to less significant bits represent smaller changes to the image overall. For example, one could potentially hide text in the least significant bits of an image’s pixels. In this puzzle, an entirely different image has been hidden in the two least significant bits of the image you are given.

In order for this to be possible, the values for the hidden image’s pixel value had to be scaled down from the usual 0–255 range to a 0–3 range. The puzzle image was then created by replacing the two least significant bits of another image with the bits representing the scaled hidden image’s pixels. In order to recover the image, you will need to extract the two least significant bits and scale them back up to the 0–255 range.

The image you will be working with for this puzzle is named puzzle-4.png.

Run code

Bonus Puzzle

How did we hide the image in Puzzle 4? In this Bonus Puzzle, you will need to hide an image of a crane (crane1.png) inside the two least significant bits of an image of another crane (crane2.png).

Once you think you have a solution, you should try using your code from Puzzle 4 to reveal the hidden image.

This puzzle requires using multiple images. You can process multiple images at once by calling the processAll function. processAll takes a function to use for processing followed by a variable number of arguments representing the images to process. For example,

var img1 = new SimpleImage("foo.png");
var img2 = new SimpleImage("bar.png");
return processAll(process, img1, img2);

The processing function given to processAll should accept as many arguments as were given to processAll not including the processing function itself. However, the processing function should still only return the image that should be displayed. For example, the process function in the code above could look like the following.

function process(img1, img2) {
  // Processing code here ...
  // ...
  // Return img2 to be displayed.
  return img2;
}

The images you will be working with for this puzzle are named crane1.png and crane2.png.

Run code