Question

Python Project

Assignment

Bitmap files map three 8-bit (1-byte) color channels per pixel. A pixel is a light-emitting "dot" on your screen. Whenever you buy a new monitor, you will see the pixel configuration by its width and height, such as 1920 x 1080 (1080p) or 3840x2160 (4K). This tells us that we have 1080 rows (height), and each row has 1920 (width) pixels.

The bitmap file format is fairly straight forward where we tell the file what our width and height are. When we write this file, we can open it with an image viewer on Windows, Mac, or Linux to view our rendered image.

Writing bitmap files requires us to open a file as a binary file for writing. We won't be inputting any data from a bitmap file, instead, we will just be writing our rendered image into a bitmap file so we can view it.

Step 1: Create a class called BitmapFile:

class BitmapFile:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        # There are 3 color channels per pixel
        # and there are width * height pixels.
        self.pixels = []
        for row in range(self.height):
            for col in range(self.width):
                self.pixels.append([0, 0, 0])

You can see our initialization function will take a width and a height and set up a list of pixels. Each pixel is a list of three values, as you can see when [0, 0, 0] is appended. Do not think you can do something like the repetition function with this ([0, 0, 0] * (width * height)). Remember that lists are references. This will cause all of your pixels to reflect one single color since each pixel refers to the same one!

    def set_pixel(self, row, col, pixel):
        # row and col are integers
        # pixel is a sub-list of three values: [red, green, blue]
        loc = row * self.width + col
        self.pixels[loc][0] = pixel[2]
        self.pixels[loc][1] = pixel[1]
        self.pixels[loc][2] = pixel[0]

Here we are using set_pixel to take a row and a column. Remember that rows run height-wise and columns run width-wise. The final parameter, pixel, is a list (or tuple) of three values. The bitmap file requires that the pixel colors be in the format blue first, green second, red last. However, we usually logically think of pixels in red first, green second, and blue last. This is why you can see that I flip the red and blue channels so that we can provide pixel in the RGB order, but internally, it stores it in the BGR order.

    def get_pixel(self, row, col):
        loc = row * self.width + col
        return self.pixels[loc]

Getting a pixel will be important when we write the file. Recall that the BMP file has a table of pixels, so we need to know what values to store. Since set_pixel already put these in the correct order for us, we don't have to mess around to make sure it is in the right order when we write to our BMP file.

    def write(self, file_name):
        bmpfile = open(file_name, "wb")
        # Write Bitmap File Header
        # Write Bitmap Info Header
        # Write pixels in BGR (blue, green red)
        # You can use self.get_pixel to get the
        # color values as a list.

I've put comments for the sequence of things we need. I will show you below what the "headers" are. For now, we can't do anything until we build these headers.

The BitmapFile class stores the width of the picture and the height. Then it stores a list of pixels. Each pixel is a sub-list that contains three color channel values: red, green, blue. These will also be integer values between 0 and 255. 0 is color off or black, whereas 255 is full color on. For example, 255, 0, 0 would be full red, no green, and no blue, so you will get a red pixel, whereas 255 255 255 would be full red, full green, and full blue, which will give you a white pixel.

The set_pixel function will take a row (recall that there are height number of rows) and a column (recall that there are width number of columns) and set the color value of that pixel to pixel. The pixel parameter is a sub-list that contains three values in order: red, green, then blue. Since the bitmap file stores in the order of blue, green, red, we have to swap the blue and red channels. We can either do this when we write to the bitmap file, or we can do this when we set the pixel. The shell code I show you above does it with set_pixel. That's why you see [0] = [2], and [pixel][2] = pixel[0].

Step 2: Understand the bitmap data type

The bitmap data type has two headers, which give any program wanting to read your bitmap file information about how to actually read the file. The bitmap file is laid out as shown below:

 

There is a lot of information here, but let's break it down into simpler components.

The file is laid out as follows: BitmapFileHeader followed by BitmapInfoHeader followed by the Pixel Array

The BitmapFileHeader contains 14 bytes of information in the following order:

  • signature - 2 bytes

  • size - 4 bytes

  • reserved - 4 bytes

  • offset - 4 bytes

The signature is a constant and is two characters 'BM'. Recall that one character is exactly 1 byte. You can write 'B' as integer value 66 and 'M' as integer 77.

The BitmapInfoHeader contains 40 bytes of information in the following order:

  • size - 4 bytes

  • width - 4 bytes

  • height - 4 bytes

  • planes - 2 bytes

  • bits per pixel - 2 bytes

  • compression - 4 bytes

  • bmp_size - 4 bytes

  • horizontal_resolution - 4 bytes

  • vertical_resolution - 4 bytes

  • colors - 4 bytes

  • colors_used - 4 bytes

You won't be using all of the fields above, and most of them will be set to 0, but you can see this is where we get the width and the height of our picture from!

The pixel array is stored from bottom to top. So, the first row that you will store in the pixel array is actually the bottom row in the picture.

PADDING: There is one issue with BMP files, and that is that the row must be a multiple of four bytes. Recall that we are storing three bytes per pixel. So, if our width * 3 bytes is not a multiple of four, we have to add what is called padding. Padding is unused data just to move the subsequent data further into the binary file.

Padding is fairly straightforward to calculate, and you only need to do it when you know the number of bytes per pixel and the number of pixels in a row. First, we multiply the width with the number of bytes per pixel, which in our case will be 3. So row_bytes = width * 3. Now, we need to determine if this is a multiple of four bytes before going any farther. So, we can use the mod operator (%) to see what the remainder is after division. So, row_bytes % 4 will give us one of four values: 0, 1, 2, or 3. If the value is 0, then width * 3 is a multiple of four, and our job is done. However, if the value is 1, 2, or 3, that means we need to add padding bytes. However, the number of bytes we need is the inverse of the result. For example, if our remainder is 1, that means there are three more bytes we need to add before we get to a multiple of four: 1 % 4 = 1, 2 % 4 = 2, 3 % 4 = 3, 4 % 4 = 0. So, we can do this by 4 - row_bytes % 4. Notice we only do this if our row_bytes % 4 gives us anything other than 0. If it is 0, we would get 4 - 0, which would add 4 padding bytes. WE DO NOT WANT THIS. The whole point is to make each row a multiple of four bytes.

Step 3: Writing the bytes

We can use the bytearray data type to help us get the bytes in the correct order. Recall that the header is 14 bytes for the file header + 40 bytes for the info header. So we need a byte array of 54 bytes. We can create one by writing ba = bytearray(54). This will give us an array of bytes that we can manipulate like a list. We also need to transform our data, such as the width and height into bytes. Luckily, there is a helpful function for an integer called to_bytes():

to_bytes.png

 

Create a class that stores the bitmap file header and bitmap info header to make it easier when you write to your file. For example,

class BitmapHeaders:
    def make_headers(self):
        ba = bytearray()
        # First the bitmap file header: signature (2 bytes), size (4 bytes), reserved (4 bytes), offset (4 bytes)
        # signature is always 'BM'
        ba += b'BM'
        # size is the total size of the file. We have 54 bytes in of header, followed by self.width * self.height pixels
        padding = 0
        row_bytes = (self.width * 3) % 4
        if row_bytes > 0:
            padding = 4 - row_bytes
        size = 54 + (self.width * 3 + padding) * self.height
        # We have to specify "little" byte order since BMP files require that the "little" end of
        # the four bytes go first.
        ba += size.to_bytes(length=4, byteorder='little')
        # 4 bytes of reserved. bytearray() automatically initializes these to 0, so we shouldn't have to do anything
        ba += int(0).to_bytes(length=4, byteorder='little')
        # Offset comes next. This is how far we have to jump to get to the pixel array, which means we have to
        # skip the file and info header, so we need to skip exactly 54 bytes
        offset = 54
        ba += offset.to_bytes(length=4, byteorder='little')

Everything we've done above was done to write the 14 byte file header. Notice that to_bytes is our helpful friend here! Also, notice that I use += for ba (bytearray). We could make the byte array 54 bytes, however, it gets a tiny bit more cumbersome to write into specific places in the byte array even with using slices. In the case above, we write in the proper order, and everything fills out. When we're done, we have a 14 byte bytearray.

Now, we have to write the info header. This comes directly after the file header and is the next 40 bytes to make a 54 byte header.

        # Now we get to the info header: 
        # size (4 bytes), width (4 bytes), height (4 bytes), planes (2 bytes), bits per pixel (2 bytes),
        # compression (4 bytes), bmp_size (4 bytes), horizontal resolution (4 bytes), vertical resolution (4 bytes),
        # colors (4 bytes), colors used (4 bytes)
        size = 40 # The info header is 40 bytes
        ba += size.to_bytes(length=4, byteorder='little')

        # Width
        ba += self.width.to_bytes(length=4, byteorder='little')
        # Height
        ba += self.height.to_bytes(length=4, byteorder='little')
        # Planes is 1
        ba += int(1).to_bytes(length=2, byteorder='little')
        # Bits per pixel is 24, we use 3 bytes per pixel which is 3 * 8 = 24 bits
        bpp = 3 * 8
        ba += bpp.to_bytes(length=2, byteorder='little')
        # Compression is 0
        ba += int(0).to_bytes(length=4, byteorder='little')
        # BMP size we can set to 0. This tells programs to figure it out with the width and height
        ba += int(0).to_bytes(length=4, byteorder='little')
        # horizontal and vertical resolutions can be set to 1
        hres = 96
        ba += hres.to_bytes(length=4, byteorder='little')
        vres = 96
        ba += vres.to_bytes(length=4, byteorder='little')
        # Colors and colors used can be set to 0 to signify the program should "figure it out"
        ba += int(0).to_bytes(length=4, byteorder='little')
        ba += int(0).to_bytes(length=4, byteorder='little')

        # We can only write the immutable bytes() version to a file, so we can convert the bytes
        # using the bytes() type caster.
        return bytes(ba)

The info header has now been added to the byte array. I use return bytes(ba) because we don't want the bytes to change when it leaves our make_headers() function. Plus it is better for binary files to be written.

Now that we have the headers being produced, we can finish the write function in the BitmapFile class:

        padding = 0
        if (self.width * 3) % 4 != 0:
            padding = 4 - (self.width * 3) % 4
        headers = BitmapHeaders(self.width, self.height)
        bmpfile.write(headers.make_headers())
        # Recall we count backwards for the rows
        for row in range(self.height-1, -1, -1):
            for col in range(self.width):
                px = self.get_pixel(row, col)
                bmpfile.write(bytes(px))
            bmpfile.write(bytes(padding))
        bmpfile.close()

As you can see, by making the headers class, we simply call it and build the headers for us. After both headers are written, we are 54 bytes into the file. Here is where the pixel table comes into play. The pixel table starts bottom to top, but from left to right. This means that the row needs to count backwards whereas the columns count forwards. As you can see, I use our get_pixel() function to get the value, then convert those pixel values into bytes before writing them to the file. Finally, don't forget about the padding!

Step 4: Test

You can test by writing a main() function that puts in RGB values based on width and height to see if they are writing correctly. For example,

def main():
    width = 255
    height = 255
    bmf = BitmapFile(width, height)
    for row in range(height):
        for col in range(width):
            px = [row % 256, row % 256, col % 256]
            bmf.set_pixel(row, col, px)

    bmf.write("test.bmp")

if __name__ == "__main__":
    main()

this should produce a small picture that looks like the following:

bitmap_writer_test.png

0 0
Add a comment Improve this question Transcribed image text
Request Professional Answer

Request Answer!

We need at least 10 more requests to produce the answer.

0 / 10 have requested this problem solution

The more requests, the faster the answer.

Request! (Login Required)


All students who have requested the answer will be notified once they are available.
Know the answer?
Add Answer to:
Python Project
Your Answer:

Post as a guest

Your Name:

What's your source?

Earn Coins

Coins can be redeemed for fabulous gifts.

Similar Homework Help Questions
  • Problem1: BMPmain.c, BMPfns.c, BMPfns.h, Makefile The file 'BMPmain.c' contains a main() function which is already written...

    Problem1: BMPmain.c, BMPfns.c, BMPfns.h, Makefile The file 'BMPmain.c' contains a main() function which is already written for you and attached with this homework. When appropriate functions are provided, main() will create a .bmp image file. Your job is to write 'BMPfns.c' which contains the functions which main() uses to create .bmp image files. You must also write the header file 'BMPfns.h' to #include in BMPmain.c and which contains prototypes of the functions defined in BMPfns.c . Problem2: BMPcheckerboard.c, BMPfns.c, BMPfns.h,...

  • the following python code edits BMP image file to negative but I need help with modifying...

    the following python code edits BMP image file to negative but I need help with modifying this code so it turns it the same BMP image file into a black and white instead of a negative. heres python code I have so far [pasted below] ## # This program processes a digital image by creating a negative of a BMP image. # from io import SEEK_CUR from sys import exit def main() : filename = input("Please enter the file name:...

  • 4. Write a function extract(pixels, rmin, rmax, cmin, cmax) that takes the 2-D list pixels contai...

    Please design the function in version 3 of python 4. Write a function extract(pixels, rmin, rmax, cmin, cmax) that takes the 2-D list pixels containing pixels for an image, and that creates and returns a new 2-D list that represents the portion of the original image that is specified by the other four parameters. The extracted portion of the image should consist of the pixels that fall in the intersection of the rows of pixels that begin with row rmin...

  • Encoding and Decoding PPM image 3 & 4 are parts of question 2 Code for a09q1:...

    Encoding and Decoding PPM image 3 & 4 are parts of question 2 Code for a09q1: We were unable to transcribe this image3 Encoding an Image in a09q2.py In the next four sections, you will no longer write class methods but rather functions. In each of the next four parts, you should be importing the PPM class from a09q1.py to use with the command from a09q1 import PPM Note that this is similar to how the check module was imported...

  • WRITE A C++ PROGRAM TO ANSWER THE QUESTIONS BELOW. You are provided with an image in...

    WRITE A C++ PROGRAM TO ANSWER THE QUESTIONS BELOW. You are provided with an image in a new file format called JBC Here is an example of the format: 2 3 100 50 40 200 66 56 12 45 65 23 67 210 0 255 100 45 32 123 The first line means that this is a 2 (width) by 3 (height) image of pixels. Then, each line represents the color of a pixel. The numbers are from 0 to...

  • The ACME Manufacturing Company has hired you to help automate their production assembly line. Cameras have...

    The ACME Manufacturing Company has hired you to help automate their production assembly line. Cameras have been placed above a conveyer belt to enables parts on the belt to be photographed and analyzed. You are to augment the system that has been put in place by writing C code to detect the number of parts on the belt, and the positions of each object. The process by which you will do this is called Connected Component Labeling (CCL). These positions...

  • Need help starting from question 9. I have tried multiple codes but the program says it is incorrect. Case Problem 1 Da...

    Need help starting from question 9. I have tried multiple codes but the program says it is incorrect. Case Problem 1 Data Files needed for this Case Problem: mi pricing_txt.html, mi_tables_txt.css, 2 CSS files, 3 PNG files, 1 TXT file, 1 TTF file, 1 WOFF file 0 Marlin Internet Luis Amador manages the website for Marlin Internet, an Internet service provider located in Crystal River, Florida. You have recently been hired to assist in the redesign of the company's website....

  • Can you please assist me with this HTML? Below is what I have but it isn't...

    Can you please assist me with this HTML? Below is what I have but it isn't working. Filename: jpf_sudoku.css /* Table Styles */ table.spuzzle { border-collapse: collapse; margin-top: 0px; margin-bottom: 0px; margin-left: auto; margin-right: auto; width: 90%; } table.spuzzle td { border: 5px outset gray; width: 33.3%; } table.spuzzle th { font color: gray; padding-right: 10px; padding-bottom: 10px; } /* Inner Table Styles */ table.subTable { border-collapse: collapse; width: 100%; } table.subTable td { box-shadow: 0px 0px 15px inset; border:...

  • Psuedocode works! DP is dynamic programming for this algorithm Problem 4.2. (Difficulty 3) Seam carving is...

    Psuedocode works! DP is dynamic programming for this algorithm Problem 4.2. (Difficulty 3) Seam carving is a real-world application of DP for content- aware image resizing. The simplest way to reduce the size of an image is cropping and scaling, i.e. cutting out parts of the image and scaling down the size. However, cropping leaves visible crop lines of incontinuity while scaling reduces the details of the image. We would like to intelligently reducing the size while accounting for the...

  • Error: Unable to save the file in the folder...... Whats is wrong with this code.? The...

    Error: Unable to save the file in the folder...... Whats is wrong with this code.? The below code checks for the HTTP/1.1 200 OK. and if its true it divides tha pathname and hostname and after first slash in the pathname, it saves as the downloading link and have to save in the folder. but it 's not getting saved... int bytes sent; bytes sent send(sockfd, request strlen(request),0) int byte received; char buffer 10000J; bzero buffer 10000); //to make sure...

ADVERTISEMENT
Free Homework Help App
Download From Google Play
Scan Your Homework
to Get Instant Free Answers
Need Online Homework Help?
Ask a Question
Get Answers For Free
Most questions answered within 3 hours.
ADVERTISEMENT
ADVERTISEMENT
ADVERTISEMENT