Skip to content

← Computing Further 6–8

How Do You Hide a Picture Inside Another?

Topic 22 · Grades 6–8 · Hiding data in bits

Colors as numbers and binary, from Grade 7; the blocks from Grade 7 (Variables and Choices); the Python parts from Grade 8 (loops and lists).

What this builds on from class
In this unit 8 sections
  1. Try it
  2. How it works
  3. Program it
  4. What your program shows
  5. Going further still
  6. Challenges
  7. Questions
  8. Where to go next

You send a friend a photo of the Nyatapola, the five-roofed temple in Bhaktapur. It looks like any other photo of the temple: the roofs, the stone steps, people crossing the square. But your friend knows where to look, and finds a snow leopard inside it.

The leopard is in the numbers of the photo. Every pixel’s colors were changed a tiny amount, so tiny that no eye can see it, and those tiny changes are a whole second picture. Hiding a message so that nobody even knows it is there is called steganography (STEG-uh-NOG-ruh-fee), from the Greek for “covered writing”.

Here is that photo. Slide Bit to the right, one step at a time.

Try it

Look inside the photo

The left picture is the photo. The right picture shows one bit of every color in it. Slide from bit 8 to bit 1, then switch between With the secret and Without.

The photo
Bit 8 of every color
The photo:
Bit 8 is worth
128
Colors changed by hiding
0%
Most a color changed
1 of 255

Bit 8 is worth 128. It only says whether each color is in the bright half or the dark half, yet it already draws the whole temple.

The photo: the Nyatapola, Bhaktapur, by Göran Höglund (Kartläsarn), CC BY 2.0. The secret: a snow leopard by Tambako the Jaguar (derivative by Niabot), CC BY-SA 2.0, made black and white by us and shared under the same license; both via Wikimedia Commons. 230,400 color numbers in all.

A whole picture of a snow leopard is hidden in the photo. How much did the photo’s colors have to change to hold it?

Pick an answer to see why.

You need: binary and how a color is stored as numbers, from Grade 7. For the blocks, variables and the remainder, from Grade 7. For the Python, loops and lists, from Grade 8.

Try it

Play with the photo above.

  • a.Start at bit 8 and slide to the right, one bit at a time. At which bit does the temple stop being easy to see?
  • b.At bit 1, what do you see? Now press Without. What changes in the right picture, and what changes in the photo on the left?
  • c.Look at the readouts with the secret in. About how many of the photo’s colors were changed to hide the leopard?

How it works

A color is a byte

In Grade 7 you met colors from numbers: every pixel is three lights, red, green and blue, and each light’s brightness is a number from 0 to 255. That number is one byte: eight bits, worth 128, 64, 32, 16, 8, 4, 2 and 1. Here is a red of 202:

Worth 128 64 32 16 8 4 2 1
Bit 8 7 6 5 4 3 2 1
202 1 1 0 0 1 0 1 0

128 + 64 + 8 + 2 = 202. The bits are not equally important. If bit 8 flips, the color jumps by 128, from a bright red to a dark one. If bit 1 flips, it moves by 1, from 202 to 203. In Grade 7 you saw that a screen’s 16.7 million colors are “enough that the steps between neighboring colors are too small for your eyes to see”. One step is exactly what a change in bit 1 makes. Bit 8 is called the most significant bit, and bit 1 the least significant bit.

A pixel’s green is 77. Which change would you see most easily?

Pick an answer to see why.

Hiding one bit

The last bit of a number is the remainder when you divide it by 2: 0 for an even number, 1 for an odd one. To hide a secret bit in a color, make the color even or odd to match:

  • take away the remainder, which makes it even: 203 → 202, and 202 stays 202;
  • then add the secret bit: 0 keeps it even, 1 makes it odd.

As one rule: new color = color − (remainder of color ÷ 2) + secret bit.

Color Secret bit Color − remainder New color Change
202 1 202 203 +1
202 0 202 202 none
77 0 76 76 −1
255 1 254 255 none

Every color moves by 1 at most, and about half don’t move at all: their last bit already matched. And nothing ever goes below 0 or above 255.

To get the secret back, the person who receives the photo only needs the remainder of each color ÷ 2. That is the whole trick.

The leopard in the widget is a black-and-white picture of 160 × 120 squares, each square doubled to cover 2 × 2 of the photo’s pixels. A white square is a 1, a black square a 0. Each bit is hidden in all three colors of its pixel, red, green and blue, so the right picture shows the leopard in plain black and white.

Program it

With blocks

Here is the rule as a block program. It asks for a color and a secret bit, and says the new color.

Try it

Hide one bit

Press Run and type 202, then 1. Watch the trace table. Then press Test: it hides four bits in four colors.

What the program says and asks appears here.

color – no value
bit – no value
new – no value
StepBlockcolorbitnew
TestShould sayIt said
1 in 202203
0 in 202202
0 in 7776
1 in 255255

Press Run to start the program, or Step to do one block at a time.

Loading the blocks…

A photo is millions of colors: a program in a loop does them all in a moment.

In Python (from Grade 8): the bits of a number

// divides and throws away the remainder, and % gives the remainder. So (color // worth) % 2 is the bit worth worth: 202 // 8 is 25, and 25 % 2 is 1.

color = 202worth = 128for place in range(8):    bit = (color // worth) % 2    print('worth', worth, ':', bit)    worth = worth // 2

Output

worth 128 : 1worth 64 : 1worth 32 : 0worth 16 : 0worth 8 : 1worth 4 : 0worth 2 : 1worth 1 : 0

Reading down: 11001010, the table above.

A photo as a list of rows

The programs below use a small gray photo of the same temple, made from the same photo as the widget’s: 30 rows of 40 gray values, from 0 (black) to 255 (white), in the file nyatapola.py. A gray photo has one number a pixel instead of three, which keeps the programs short. They also use flag.py, our secret: Nepal’s flag drawn in 30 rows of 40 squares, '#' for the flag and '.' for the rest.

This program draws the photo, then three of its bit planes: one bit of every pixel, white where the bit is 1 and black where it is 0. A loop inside a loop goes through every pixel: the outer one takes each row, the inner one each value in it.

from nyatapola import photofrom pixels import show_grid def bit_plane(picture, worth):    plane = []    for row in picture:        new_row = []        for color in row:            new_row.append((color // worth) % 2 * 255)        plane.append(new_row)    return plane show_grid(photo)show_grid(bit_plane(photo, 128))show_grid(bit_plane(photo, 8))show_grid(bit_plane(photo, 1))

Picture

The Nyatapola temple in gray squares: its dark roofs against a pale sky.
Bit 8 of every pixel: the temple black against a white sky, a clear silhouette.
Bit 4 of every pixel: patches of black and white with only a hint of the temple's shape.
Bit 1 of every pixel: black and white squares scattered like noise, no temple at all.

Even in a photo this small, bit 8 is the temple and bit 1 is noise.

Hiding the flag

Now hide the flag in bit 1. hide() makes a new photo, pixel by pixel, with the rule from above. Then the program counts how many pixels changed, and by how much.

from nyatapola import photofrom flag import flagfrom pixels import show_grid def hide(photo, picture):    new_photo = []    for r in range(len(photo)):        new_row = []        for c in range(len(photo[r])):            color = photo[r][c]            if picture[r][c] == '#':                bit = 1            else:                bit = 0            new_row.append(color - color % 2 + bit)        new_photo.append(new_row)    return new_photo secret_photo = hide(photo, flag) changed = 0biggest = 0for r in range(30):    for c in range(40):        change = abs(secret_photo[r][c] - photo[r][c])        if change > 0:            changed = changed + 1        biggest = max(biggest, change)print(changed, 'of 1200 pixels changed')print('The biggest change:', biggest)show_grid(photo)show_grid(secret_photo)

Output

562 of 1200 pixels changedThe biggest change: 1

Picture

The Nyatapola photo before hiding: dark roofs against a pale sky.
The photo with the flag hidden in it: it looks exactly the same.

Put your finger between the two pictures. Can you find one square that is different?

Getting it back

The person who receives the photo needs only the remainders. This program hides the flag and then finds it again, using a palette: 0 is drawn white and 1 crimson, the flag’s color.

from nyatapola import photofrom flag import flagfrom pixels import show_grid def hide(photo, picture):    new_photo = []    for r in range(len(photo)):        new_row = []        for c in range(len(photo[r])):            color = photo[r][c]            if picture[r][c] == '#':                bit = 1            else:                bit = 0            new_row.append(color - color % 2 + bit)        new_photo.append(new_row)    return new_photo def reveal(photo):    found = []    for row in photo:        new_row = []        for color in row:            new_row.append(color % 2)        found.append(new_row)    return found secret_photo = hide(photo, flag)show_grid(reveal(photo), palette=['white', 'crimson'])show_grid(reveal(secret_photo), palette=['white', 'crimson'])

Picture

The last bits of the photo before hiding: white and crimson squares scattered at random.
The last bits of the photo with the secret: Nepal's flag in crimson, its two pennants with the moon and the sun cut out in white.

The photo’s own last bits are noise; the second picture is the flag, back exactly.

What your program shows

  • 562 of the 1,200 pixels changed, and none by more than 1. The other 638 already had the right last bit. The photo with the flag in it looks exactly like the photo without it.
  • The flag came back perfectly, every square of it, from nothing but the remainders.
  • The last bit of a photo is noise, so a secret there doesn’t stand out in the photo.

It works because the bits of a number are not equal: the top bit carries half of a color’s brightness, the last bit 1 part in 255.

Now try the opposite. Change one line of hide() so the flag goes into bit 8:

from nyatapola import photofrom flag import flagfrom pixels import show_grid def hide_on_top(photo, picture):    new_photo = []    for r in range(len(photo)):        new_row = []        for c in range(len(photo[r])):            color = photo[r][c]            if picture[r][c] == '#':                bit = 1            else:                bit = 0            new_row.append(color % 128 + bit * 128)        new_photo.append(new_row)    return new_photo show_grid(hide_on_top(photo, flag))

Picture

The photo with the flag forced into bit 8: the flag stands out plainly, bright white, over a temple and sky turned gray and dark.

color % 128 keeps bits 7 to 1 and drops bit 8; bit * 128 puts the secret there. Same flag, same photo, still one bit a color, but now the flag shouts. Where you hide a bit matters more than how many you hide.

How much could a real photo hold? A phone photo of 12 million pixels has three colors a pixel:

pixels = 12000000colors = pixels * 3letters = colors // 8print(colors, 'last bits to hide in')print(letters, 'letters, at 8 bits a letter')

Output

36000000 last bits to hide in4500000 letters, at 8 bits a letter

Four and a half million letters: more than a whole thick book, inside one holiday photo.

Paper seen through a microscope, its fibers lit bluish, with small yellow dots scattered across it and a ruler marked in millimeters along the bottom
Figure 22.1 Yellow tracking dots from a color laser printer, under a microscope beside a ruler marked in millimeters. Each dot is about 0.1 mm across. Photo: Anton, CC BY-SA 2.5

Going further still

Hide more, see more. Hide a secret in the last two bits instead of one. Each pixel can then keep a number from 0 to 3: four shades of gray, enough for a recognizable photo, not just black and white. How big is the biggest change now? Challenge ★★ asks you to write it. With four bits, you can hide a whole gray photo inside another; at what bit does the cover photo start to look spoiled?

Text in a photo. A letter is a number (Grade 7’s ASCII: H is 72), and a number is 8 bits, so 8 pixels can hold one letter. Challenge ★★★ reads a message hidden this way.

A secret is easy to break. We tried saving the widget’s photo, with the leopard in it, as a JPEG file at quality 90, the usual way a photo file is squeezed smaller (Grade 8). The JPEG keeps the photo looking the same, but it changes most colors by a few steps, and 48 out of every 100 hidden bits came back wrong. That is no better than tossing a coin: the leopard was gone. Saved as a PNG file, which keeps every number exactly, not one bit changed. So a hidden picture must travel as a file that is never squeezed.

Challenges

★ Read a hidden letter (blocks)

Eight color numbers are typed, one at a time. Their last bits, the first worth 128 and the last worth 1, make one letter’s code. Finish the program so it says the code. For 200, 151, 88, 64, 203, 90, 40, 18 it says 72, the letter H.

Try it

★ Read a hidden letter

What the program says and asks appears here.

code – no value
color – no value
TestShould sayIt said
H is hidden72
i is hidden105
All odd255
All even0

Press Run to start the program, or Step to do one block at a time.

Loading the blocks…

Hint
Answer

★★ Hide in two bits (Python, from Grade 8)

Write hide_two(photo, picture). photo is a list of rows of numbers from 0 to 255, and picture a list of rows of the same size whose numbers are 0, 1, 2 or 3. Give back a new photo with each number’s last two bits replaced by the picture’s number.

def hide_two(photo, picture):    # each picture number is 0 to 3    return photo print(hide_two([[200, 201, 202]], [[3, 0, 1]]))

Try it

★★ Hide in two bits

  1. A 2 × 3 photo hide_two([[200, 201, 202], [17, 18, 19]], [[0, 1, 2], [3, 3, 0]]) Should give [[200, 201, 202], [19, 19, 16]] Yours —
  2. All hidden as 3 hide_two([[10, 11], [254, 255]], [[3, 3], [3, 3]]) Should give [[11, 11], [255, 255]] Yours —
  3. All hidden as 0 hide_two([[10, 11], [254, 255]], [[0, 0], [0, 0]]) Should give [[8, 8], [252, 252]] Yours —
Tests passed
— / 6
Challenge
★★ solved

Write your hide_two in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Answer

★★★ Read the hidden message (Python, from Grade 8)

A message is hidden in a list of color numbers: each letter is 8 last bits in a row, the first worth 128, and the message ends where 8 bits make 0 (or where the numbers run out). Write read_message(colors) that gives back the message. chr(72) turns the code 72 into 'H'.

def read_message(colors):    # 8 last bits a letter; the code 0 ends the message    return '' print(read_message([200, 151, 88, 64, 203, 90, 40, 18]))

Try it

★★★ Read the hidden message

  1. Hi, hidden in the sky read_message([234, 235, 234, 234, 235, 234, 232, 230, 230, 229, 229, 226, 227, 226, 224, 225, 224, 224, 226, 226, 226, 224, 226, 226]) Should give 'Hi' Yours —
  2. NEPAL read_message([234, 235, 234, 234, 233, 233, 233, 232, 230, 229, 228, 226, 226, 227, 224, 225, 224, 225, 226, 227, 226, 222, 220, 226, 224, 225, 224, 224, 224, 224, 224, 225, 226, 227, 226, 226, 229, 233, 236, 234, 232, 234, 234, 234, 234, 234, 232, 232]) Should give 'NEPAL' Yours —
  3. Nothing hidden: the code 0 first read_message([234, 234, 234, 234, 234, 234, 232, 230]) Should give '' Yours —
Tests passed
— / 6
Challenge
★★★ solved

Write your read_message in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Another hint
Answer

Questions

Which bit of a color can you change without anyone seeing the difference?

Pick an answer to see why.

To hide the bit 0 in the color 151, what does the color become?

Pick an answer to see why.

a.

Explain the difference between hiding a message (steganography) and scrambling it so it can’t be read (encryption).

Answer
b.

A photo with a picture hidden in its last bits is sent through a messaging app that squeezes it into a smaller JPEG. The friend finds only noise. Why?

Answer
c.

A gray photo is 800 × 600 pixels. How many letters could be hidden in it, at one bit a pixel and 8 bits a letter?

Answer
d.

Name one good use and one harmful use of hiding data in pictures or printouts.

Answer

Where to go next