Due Date: Sunday, March 8, 2026, 11:59 PM PST
CSE8AImage.py, steganography.py, M_M.bmp, CookieMonster.bmp
steganography.py (details on how to submit your file can be found below)
In this PA, we will try to hide a message image within a context image. Then we will also implement a function to recover that message image from a combined image.
CSE8AImage.py provides image loading/saving and some other supports. Do not change anything in it.
steganography.py contains starter code for this PA. Complete the functions with #TODO comments by following the instructions below.
Every pixel in a digital image is made up of three color values: Red, Green, and Blue (RGB). Each value is an integer from 0 to 255, which can be represented as an 8-bit binary number. For example:
| Decimal | Binary |
|---|---|
| 255 | 11111111 |
| 200 | 11001000 |
| 63 | 00111111 |
| 0 | 00000000 |
The most significant bits are the leftmost bits (they contribute the most to the value), and the least significant bits are the rightmost bits (they contribute the least).
The key idea behind steganography is that small changes to the least significant bits of a pixel are nearly invisible to the human eye. We can exploit this by hiding data in those bits.
Implement the following functions in steganography.py. Each function has a #TODO comment marking where you should write your code.
This function takes in an integer and returns the two least significant bits (rightmost two) as an integer.
- You can assume
numwill always be an integer between 0 and 255 inclusive. - For example:
get_least_significant2(255)should return3(since the rightmost two bits of11111111are11, which is 3)get_least_significant2(253)should return1(since the rightmost two bits of11111101are01, which is 1)
This function takes in an integer and returns the two most significant bits (leftmost two) as an integer.
- You can assume
numwill always be an integer between 0 and 255 inclusive. - For example:
get_most_significant2(200)should return3(since the leftmost two bits of11001000are11, which is 3)get_most_significant2(150)should return2(since the leftmost two bits of10010110are10, which is 2)
This function takes in two integers and returns the result of replacing the two least significant bits (rightmost two) of context_val with the value of message_val.
-
You can assume
context_valwill always be an integer between 0 and 255 inclusive (an 8-bit integer) andmessage_valwill always be an integer between 0 and 3 inclusive (a 2-bit integer). -
For example:
embed_digits2(255, 0)should return252— the two rightmost bits of11111111are replaced with00, giving11111100= 252embed_digits2(0, 3)should return3— the two rightmost bits of00000000are replaced with11, giving00000011= 3
-
Hint: One approach is to first "clear" the two least significant bits of
context_val(set them to 0) and then addmessage_val. A quick way to clear two bits is to shift right by 2 and then shift left by 2. For example:00111111shift right 2 →0000111100001111shift left 2 →00111100(the two rightmost bits are now 0)
This function takes in two 2D arrays of tuples and returns a new image with message_img hidden in the least significant 2 bits of context_img.
- You need to hide the most significant 2 bits of each RGB value in
message_imginto the least significant 2 bits of the corresponding RGB values incontext_img. - You can assume
message_imgis always the same size ascontext_img. - This function must make a copy of
context_imgand return that modified copy. It must not modify the originalcontext_img. - Hint: Make use of functions that you already implemented.
Given an image (a 2D array of tuples), this function returns a 2D array of tuples that is the hidden message image recovered from img_with_message.
- You can assume the message is hidden in the least significant 2 bits of the image.
- Your function should go through the entire
img_with_messageimage. The returned image should have the same size asimg_with_message. - This function must not modify the input
img_with_messageimage. - Hint: Make use of functions that you already implemented.
After implementing all the functions above, use them to hide the CookieMonster.bmp image inside the M_M.bmp image.
In terminal, do the following:
- Load
M_M.bmpas the context image andCookieMonster.bmpas the message image usingload_img. - Call
hide_secret_message_2bitsto produce the combined image. - Save the result to a file called
M_MWithMessage.bmpusingsave_img. - Call
recover_secret_message_2bitson the combined image. - Save the recovered image to a file called
recMessage.bmp.
When finished, two additional files — M_MWithMessage.bmp and recMessage.bmp — should be created in your working directory.
For your reference, the created images should look like below:
M_MWithMessage.bmp
recMessage.bmp
Once you are confident that your program is correct, submit steganography.py to Gradescope.
The file name must be exactly steganography.py to pass the autograder.
If you get any test cases wrong, you may have feedback that tells you:
- The function being tested
- The input values used
- The expected output vs. your actual output
Use this feedback to debug and fix your code. You may submit multiple times. Your latest submission will be graded.
For this PA, we have test cases across all five functions. There are 100 total points: 85 points come from public tests you can see, and 15 points are from hidden tests which will only be revealed after the deadline. Make sure your implementation handles all edge cases to maximize your score.
-
Correctness (100 points)
You will earn points based on the autograder tests that your code passes. If the autograder is not able to run your code (e.g., your file has a syntax error or does not match the specifications in this writeup), you may not earn credit. Your latest submission will be graded.

