00:00:00,500 --> 00:00:03,040 Let's crack open Whodunit. In Whodunit, our job is to take a clue, a bitmap image, and create a verdict, another bitmap image that will reveal the hidden message or hidden image within that bitmap. So how do we do this? Well, first, after open the clue file, then we want to update the headers info for the verdict file, the outfile. We want to read into the clue scanline pixel by pixel and change that pixel's color as necessary in order for us to reveal the hidden clue. Don't worry, more on this later. And then finally, we want to write the verdict scanline pixel by pixel. Now, this may seem like a lot to do, but don't worry, you don't need to start from scratch. If we open the distribution code, take a look at copy.c. copy.c does a lot of the things that we need to do for Whodunnit. It opens an infile and creates an outfile, updating the header info for that outfile, then it reads into the scanline for the infile pixel by pixel. And each pixel it writes into the output file scanline, creating an identical copy of the bitmap. So it's not exactly what we need, but it's a great framework to start with. So let's start by copying copy.co into our whodunit.c file, making sure that we understand what each line does. The first thing that we have to do is to open the infile and open and create the outfile. copy.c does this using file I/O, creating a file pointer for either reading for the infile or writing for the outfile. The next thing to do is to update the headers info for the outfile. Bitmaps are simply an arrangement of bytes, it's just a matter of how we interpret this arrangement. Take a peek inside the bmp.h for a little bit more information. A bitmap is comprised of an info header, a file header, and then the actual pixels and padding. More on padding in a second, but let's just look at the variables within the bitmap info header. So bi, bitmap info, width contains the width of the image in pixels not including padding. Then the height contains the height of the image in pixels. and biSizeImage image contains the total size of the image in bytes, including the pixels and the padding. Now let's go to the bitmap file header, where bfSize contains the total size of the file in bytes, including the pixels, the padding, and the headers. So the bfSize can be calculated by taking the biSize image plus the size of the bitmap file header and the size of the bitmap info header. So how do we read into a file? If you follow along in copy.c, then you'll come across the fread function, where fread takes in four parameters. The first is a pointer to a struct that will contain the bytes that you're reading. Second, the size of each element to read, then the number of elements to read, and finally, the file pointer that you're reading from. So taking from that in pointer, you're going to put those bytes into that first parameter, data. Now that we've read into the file, let's talk about pixels. Pixels are represented by three bytes, where each byte represents the amount of blue, green, and red, respectively in that pixel. So if I were to, say, have the maximum amount of blue in a pixel then, I would have ff in the blue section. And if it were a pure blue pixel, then I'd have 0 green and 0 red. If, say, I had the maximum amount for a blue, green, and red, then I'd have a white pixel. Here's a simplified example of a bitmap, where I'm representing the pixels by their hexadecimal pixel notation. Now, I know that I just told you that ffffff indicates a white pixel, but that wouldn't really show up nicely on this slide, so bear with me, and let's pretend that these are white. So we have our white pixels, and then we have our full red pixels, 0 blue, 0 green, and full read. Together with those, we can use them as blocks, let's say, to construct an image. For this image, if you squint a little, then you might see a smiley face. All right. So let's take this one step further, and let's talk about the RGBTRIPLE struct. This struct will represent our pixels. So if I were to, say, make a green pixel, then I'd declare my RGBTRIPLE called triple and then access the parameters within. rgbtBlue, I'll set that to 0. rgbtGreen, setting that to full green. And rgbtRed, set to 0. And there I have a green pixel. You can access and change the RGBTRIPLE colors just as you do for any other variable. For instance, this code here asks if a triple's blue component is full and if so, prints, I'm feeling blue. Now, remember that even though a pixel might have the full amount of blue in it, it's not necessarily going to be a blue pixel, because you still have a contribution from the red and from the green. So in order to ensure that you have a blue pixel, you might execute the following, setting the triples rgbtGreen and red values to 0. Let's go back to our smiley, except in this case it looks a lot more like the clue input that you'll get in Whodunit. There's a lot of red, and it's a bit harder to see. So let's talk about how we might modify the colors to make the image more clear. What if we remove the red from each pixel, going through each RGBTRIPLE and setting the red value to 0? Well, then it would look something like this. Still a little bit hard to see, but we can see the smiley a little bit more clearly. Or we might try this, changing only pure red pixels to white. And here we have a much clearer image, no need to squint or lean back for this one. Now, these are just two options for how we might make the clue image a little bit more clear. It's certainly up to you to try other ways. You might want to do a combination of removing some red, perhaps boosting some blue. Up to you, as long as the hidden message becomes clear. So now that we've modified those pixels, let's now write those into the verdict outfile scanline. We do this with the fwrite function, where again, similar to fread, takes in four parameters. The pointer to the struct that contains the bytes that we're writing, the size, the number, and then the out pointer, the file pointer that we're writing to. So again, check out copy.c and see if you can find the section that does this. Last but not least, we need to talk about padding. In a bitmap, each pixel is 3 bytes, but a requirement for the bitmap file format is that the length of each scanline must be a multiple of 4 bytes. So that means that, if the number of pixels isn't a multiple of 4, then we need to add some padding. And this padding is just 0s. Say I have 4 RGBTRIPLEs, well, each triple is comprised of three bytes. So since I have four of them, 4 times 3 is 12, and 12 is a multiple of 4, so I don't need to add any padding in this case. Now, instead, say in my scanline, I had 5 RGBTRIPLEs, then 5 times 3 equals 15, so that's not a multiple of 4. In order to get to 16, the next multiple of 4, I need to add 1 byte of padding. Then if I have, say, 6 RGBTRIPLEs, then the amount of padding that I have to add in order to get to a multiple of 4 bytes is 2. Lastly, in this example, if I have 3 RGBTRIPLEs, then that's equivalent to 9 bytes, So. To bump it up, I have to add 3 more bytes of padding. It's important that we have a firm grasp on the concept of padding, but we don't need to come up with the equation, because it's provided for us at the bottom of the slide in the specification and in the distribution code. If we look at the formula, then we see that the two parameters are the biWidth and the size of the RGBTRIPLE. Now, the size of an RGBTRIPLE is always going to be 3 bytes. So the only thing that will ever change is the biWidth. Now, in this case, the clue and the verdict have the same width, because all we're doing is changing the colors of the pixels. So the padding is going to be the same. Now, padding isn't an RGBTRIPLE, so we won't be freading or fwriting it, instead, we use the fputc function, which takes in the character to write and the file pointer to write it to. So in this case for padding, we'll be putting the zeros into our verdict outfile. The last thing that we need to talk about is the file position indicator. Whenever we're reading into a file, there's the file position indicator, basically like our cursor, that keeps track of where in the file we are. So if we ever want to move that cursor, then we use the fseek function, which takes the file pointer that we're seeking over, the number of bytes to move that cursor, and then whether we're moving relative to the current position in the file, the beginning of the file, or the end. So again, very useful to understand this function by going into copy.c, seeing where that function is called, and understanding why. All right. We're ready to solve the mystery. We've opened the infile, updated the headers in for the outfile, read into the clue scanline pixel by pixel, changing those colors as necessary and writing them in to the verdict scanline. And with that, you've completed the problem. My name is Amaila, and this was Whodunit.