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.