>> [MUSIC PLAYING]
>> ZAMYLA CHAN: It was Miss Scarlett with the candlestick.
Whodunit?
Well, we're going to find out.
In the board game Clue, you might be given a physical red image.
And that image is very red and spotty, and your job is to
reveal the hidden message.
And usually you're provided with a red magnifying glass, or a red screen to
reveal that hidden message.
Well, we're going to mimic that.
>> In whodunit, you're given a bitmap image that looks very spotty and red,
and then run the whodunit program to reveal a hidden message.
>> So let's break this into steps.
First, you want to open the file-- the clue that you've been given.
And then also create a verdict bitmap file.
Then you want to update the bitmap header info for the verdict outfile.
More on that later.
And then you're going to read into the clue, scanline, pixel by pixel,
changing the pixel colors as necessary, and writing
those into the verdict--
pixel by pixel into the verdict scanline.
>> How do we start going about this?
Well, luckily, we have copy.c in the distribution code.
And this is going to prove quite useful to us.
Copy.c opens a file, reads in that infile's header, and then updates the
outfile's header.
And then it reads each pixel in the scanline, pixel by pixel, and then
writes that pixel into the outfile.
>> So, your first step might be to run the following
command in the terminal--
cp copy.c whodunit.c.
This will create a copy of copy.c named whodunit.c.
So our first step to open the file, well, there's an exact
replica of that in copy.c.
So I'll leave you to look at that.
>> What we're dealing with in this PSET is file I/O, basically taking files,
reading, writing, editing them.
How do you first open a file?
Well, you're going to declare a file pointer, and then you call the
function fopen.
Pass in the path, or the name of that file, and then the mode that you want
to open that file in.
Passing in an r will open foo.bmp for reading.
Whereas fopen with passing in a w will open bar.bmp, for writing the file and
actually editing it.
>> So now that we've opened the file, our next step is to update the header info
for the outfile.
What's a header info?
Well, first we need to know what a bitmap is.
A bitmap is just a simple arrangement of bytes.
And they're declared in this file here, bmp.h, with a bunch of
information of what a bitmap is actually made out of.
But what we really care about is the bitmap file header, right here, and
the bitmap info header, over here.
The header is composed of a couple of variables that will prove very useful.
There is biSizeImage, which is the total size of the image in bytes.
And this includes pixels and padding.
Padding is very important, but we'll get to that later.
>> BiWidth represents the width of the image in pixels minus the padding.
BiHeight is then also the height of the image in pixels.
And then the BITMAPFILEHEADER and the BITMAPINFOHEADER, as I mentioned
earlier, those are represented as structs.
So, you can't access the file header itself, but you'll want to get to
those variables inside.
>> OK.
So how do we update the header info?
Well, first we have to see whether we need to change any information from
the infile, the clue, to the outfile, the verdict.
Is anything changing in this case?
Well, not actually, because we're going to be just changing the colors.
We're not going to be changing the file size, the image size, the width,
or the height.
So you're all right for now by just copying each pixel.
>> OK.
So now let's look at how we actually can read every pixel from the file.
Another file I/O function will come into play--
fread.
It takes in a pointer to the struct that will contain the bytes that
you're reading.
So you're reading into that.
And then you pass in a size, which is the size of every element that you
want to read.
Here, the function sizeof will come in handy.
Then you pass in number, which represents the number of elements of
size to read.
And then finally, inptr, which is the file pointer that you're
going to read from.
So all of those elements are inside inptr and they're going to data.
>> Let's look at a little example.
If I want to read into data two dogs, well, I can do it one of two ways.
I can either read in two objects of size dog from my inptr, or I can read
in one object the size of two dogs.
So you see that depending on the way that you arrange size and number, you
can read in the same number of bytes.
>> So now, let's change the pixel color as we need.
If you look at bmp.h again, then you'll see that at the bottom
RGBTRIPLEs are another struct, where they are comprised of three bytes.
One, rgbtBlue, rgbtGreen, and rgbtRed.
So each of these represents the amount of blue, the amount of green, and the
amount of red inside this pixel, where each amount is represented by a
hexadecimal number.
>> So ff0000 will be a blue color, because it goes from blue,
to green, to red.
And then all f's will be white.
Let's take a look at smiley.bmp, which you have in your distribution code.
If you open it in just an image viewer, then you'll
just see a red smiley.
But taking a deeper dive in, we'll see that the structure
of it is just pixels.
We have white pixels, and then red pixels.
The white, ffffff, and then all of the red pixels I've colored in for you
here, and you see that they're 0000ff.
Zero blue, zero green, and full red.
And since smiley is eight pixels wide, we don't have any padding.
All right.
>> So if I were to assign different values to an RGBTRIPLE and I wanted to
make it green, then what I would do is I would declare an RGBTRIPLE, named
triple, and then to access every byte within that struct I
would use the dot operator.
So triple.rgbtBlue, I can assign that to 0.
Green I can assign it to full-- any number, really, between 0 and ff.
And then red, I'm also going to say 0.
So then that gives me a green pixel.
>> Next, what if I want to check the value of something?
I could have something that checks whether the triple's rgbtBlue value is
ff and then print, "I'm feeling blue!", as a result.
Now, that doesn't necessarily mean that the pixel is blue, right?
Because the pixel's green and red values could also have non-0 values.
All that this means, and all that this is checking for is
for a full blue color.
But all pixels could also have partial color values, like this
next example here.
>> It's a little harder to see what this image is now.
This looks a little bit more like the clue.bmp that you'll be given.
Now, physically, you might solve this, because there's a lot of red, by
holding up a red screen to the image so that the other colors can appear.
So how do we mimic this with c?
Well, we might remove all red from the image entirely.
And so to do that we'd set every pixel's red value to 0.
And so the image would look a little bit like this, where we have no red
whatsoever.
>> We can see the hidden message a little bit more clearly now.
It's another smiley face.
Or maybe we could use another method.
Maybe, we could identify all of the red pixels--
that is, all of the pixels with 0 blue, 0 green, and 0 red--
and change those to white.
And our image might look something like this.
A little bit easier to see.
>> There are lots of other ways to uncover the secret message as well,
dealing with the color manipulation.
Maybe you might use one of the methods that I mentioned above.
And additionally, you might want to enhance some colors
and bring those out.
>> So now that we've changed the pixel color, next we just need to write them
in to the scanline, pixel by pixel.
And yet again, you'll want to look back to copy.c, if you haven't copied
it already, and look at the fwrite function, which takes data, a pointer
to the struct that contains the bytes that you're reading from, the size of
the items, the number of items, and then the outptr--
the destination of those files.
>> After you write in the pixels, you'll also have to write in the padding.
What is padding?
Well, every rgbt pixel is three bytes long.
But, the scanline for a bitmap image has to be a multiple of four bytes.
And if the number of pixels isn't a multiple of four, then we need to add
this padding.
Padding is just represented by 0s.
So, how do we write, or read this?
Well, it turns out that you can't actually fread padding, but you can
calculate it.
>> In this case, the clue and the verdict have the same width, so the
padding is the same.
And the padding, as you'll see in copy.c, is calculated
with the below formula--
bi.biWidth times sizeof(RGBTRIPLE) will give us how many bytes the bmp
has in every row.
From there, the modulos and subtractions with 4 can calculate how
many bytes must be added so that the multiple of bytes on
every row is four.
>> Now that we have the formula for how much padding we need, now
we can write it.
Now, I mentioned before, padding is just 0s.
So in that case, we're just putting a char, in this case a 0, into our
outptr-- our outfile.
So that can just be fputc 0, comma outptr.
>> So, while we've been reading into our file, file I/O has kept track of our
position in those files with something called the file position indicator.
Think of it as a cursor.
Basically, it advances every time that we fread, but we have
control over it, too.
>> To move the file position indicator, you can use the function fseek.
Where the inptr represents the file pointer that you're seeking in, the
amount is the number of bytes that you want to move the cursor, and then from
relates to the reference point from where your cursor is.
If you pass in SEEK_CUR, that represents the current
position in the file.
Or you can use some other parameters.
So, we might want to use fseek to skip over the padding of the in file.
And again, if you're stuck, there's an example of that in copy.c.
>> So now we've opened the file, the clue, and the verdict.
We've updated the header info for our verdict, because every
bitmap needs a header.
We've then read into the clue's scanline, pixel by pixel, changing
every color as necessary, and writing those into the
verdict, pixel by pixel.
Once you open verdict, you can see who the culprit, or what the secret
message is.
My name is Zamyla, and this was whodunit.