WEBVTT

00:01.020 --> 00:04.800
Hello again! In this video, we are going to have a practical on binary files.

00:06.080 --> 00:09.720
We are going to use a bitmap as an example of how to work with a binary

00:09.720 --> 00:16.740
file. Bitmap is a simple format for images. It actually goes back to MS-DOS, before Windows.

00:16.980 --> 00:18.120
So it is quite basic.

00:18.390 --> 00:20.970
In fact, it even assumes you have a 16-bit computer.

00:21.420 --> 00:23.310
So we need to take account of that.

00:24.870 --> 00:27.540
As with all these file formats, there is a standard.

00:27.630 --> 00:33.000
In fact, there are actually several standards! We need to make sure that we write the data to the binary

00:33.000 --> 00:34.650
file in the correct format.

00:35.280 --> 00:40.860
If we make any mistakes, even ones which seem trivial, then the file is going to be invalid and we

00:40.860 --> 00:41.820
cannot display it.

00:43.980 --> 00:48.750
There are three parts to the bitmap formats. There is a file header, which is information for the operating

00:48.750 --> 00:49.080
system,

00:49.080 --> 00:55.680
I think. The info header, which has the properties of the image. And then the actual data, the 1's

00:55.680 --> 00:58.800
and 0's that will make the vector appear on the screen.

01:02.270 --> 01:07.880
The bitmap file header is designed for 16 bits, so we need to have everything with a 2-byte alignment.

01:08.720 --> 01:15.920
So we need to use this hash pragma pack instruction. And then use 2 for 2-byte alignment. 16

01:15.920 --> 01:16.640
bit intervals.

01:17.270 --> 01:21.770
We are using a struct because this is just a compound type for storing data.

01:22.190 --> 01:26.480
We are not doing anything object oriented. So there is no encapsulation, no member functions.

01:26.990 --> 01:29.240
So it is easier to make everything public.

01:31.080 --> 01:33.600
There are two characters which say what kind of file it is.

01:34.530 --> 01:39.780
There is an integer with the size of the file. So this is a fixed width integer, to make sure we get

01:39.780 --> 01:43.680
the same data, regardless of which system we are opening it with.

01:44.820 --> 01:49.860
This is going to contain the total size of the file. So that is the file header, plus the info header, plus

01:49.860 --> 01:50.490
all the data.

01:51.300 --> 01:53.700
Then there is this reserved field, which we do not use.

01:54.360 --> 01:55.890
And then there is the data offset.

01:56.250 --> 02:01.650
So this will say how far from the beginning of the file to where the actual image data starts.

02:05.140 --> 02:11.620
We are using these in-place initializers, so this means that "reserved" will have an initial value of 0.

02:12.160 --> 02:15.820
Unless there is a constructor which initialize it with something else.

02:17.500 --> 02:23.590
And then once we have declared this struct, we need to set the alignments back to the default. So we perform

02:23.590 --> 02:26.250
this pop operation that we saw in the last video.

02:29.380 --> 02:33.190
For the info header, these are practically all fields that we can default.

02:33.430 --> 02:37.000
The only ones that we might want to change are the width and the height of the image.

02:42.840 --> 02:48.120
For the actual image itself, this is made up of so-called "pixels" or vector elements.

02:48.690 --> 02:55.290
And each of these vector elements corresponds to a single dot on your computer screen. In the bitmap

02:55.290 --> 02:55.740
format,

02:55.770 --> 02:59.040
each of these pixels consists of three bytes.

02:59.850 --> 03:03.360
There is one byte for each of the primary colours: red, green and blue.

03:03.960 --> 03:06.480
And these can have values from 0 to 255.

03:07.590 --> 03:13.020
The value of each of these colour bytes will represent the contribution that that primary colour makes

03:13.020 --> 03:15.360
towards the final colour of the pixel.

03:16.200 --> 03:23.400
For example, if the red and green bytes are 0, and the blue byte is not 0, then you are going

03:23.400 --> 03:29.460
to get a blue of some shade. And the intensity of that blue will depend on the value of the byte.

03:31.110 --> 03:37.110
If blue is 0 and red and green are non-0, then you get some kind of yellow or orange dot on the

03:37.110 --> 03:37.470
screen.

03:38.130 --> 03:43.140
If all the bytes are 0 you get black; if they are all 255, you will get white.

03:43.770 --> 03:47.850
And if they all have the same value, and it is something in between, you will get a shade of grey.

03:48.960 --> 03:52.620
The normal convention in computer graphics is to have red first, then green, then blue. But

03:53.010 --> 03:56.670
this standard was developed by Microsoft, so they have blue and green, then red.

04:00.130 --> 04:03.190
How do we know where the pixel is going to appear on your screen?

04:03.880 --> 04:05.800
We have a system of coordinates.

04:06.280 --> 04:09.010
And you may recall this from doing maths at school.

04:10.840 --> 04:16.600
So we have Cartesian coordinates. We have this variable x, which measures how far across the screen

04:16.780 --> 04:22.090
the dot is. And this variable y will measure how far up or down the screen is.

04:23.320 --> 04:25.210
Again, this is not the normal convention.

04:25.480 --> 04:29.740
We are starting with 0 at the bottom and increasing y upwards.

04:30.520 --> 04:35.800
Normally you start with y equals 0 at the top and y increases as it goes downwards.

04:36.490 --> 04:41.980
The reason for that, is that that is how the electronics works. And that goes back to the old days of

04:41.980 --> 04:47.260
computer sets with cathode ray tubes, and they electron beams, which were scanning left to right,

04:47.260 --> 04:47.920
top to bottom.

04:50.540 --> 04:57.140
One thing we need to be able to work out is the offset the Pixel. So, to get from the start of the

04:57.140 --> 05:01.940
screen, in the bottom left hand corner, to the actual position x and y, how many pixels do we need to

05:01.940 --> 05:02.510
go past?

05:03.380 --> 05:05.240
Well, we need to go past all these rows.

05:05.420 --> 05:10.970
So that is 'y' rows. And each of these rows will have a number of pixels in it, which is equal to the

05:10.970 --> 05:11.810
width of the screen.

05:12.380 --> 05:14.440
So to get from there to there,

05:14.450 --> 05:19.070
we need to go 'y' times the width of the screen. And then to get from there to there,

05:19.550 --> 05:22.100
we need to go x pixels further.

05:23.150 --> 05:29.150
So the number of pixels, between the corner of the screen and that dot, is equal to y multiplied by

05:29.150 --> 05:33.110
the width of the screen plus x. All expressed in pixels.

05:36.620 --> 05:42.500
And this is the image that we are going to draw. And you can perhaps guess why I did not become a graphic

05:42.500 --> 05:49.160
designer! Bitmap is a 1980's format, so why not have a nice, blocky 1980's image?

05:49.710 --> 05:55.220
We're going to display C++ using blocks to represent the letters, the characters.

05:56.830 --> 05:58.480
All the blocks have the same thickness.

05:59.110 --> 06:03.310
So that is 'x' units across and 'y' units up.

06:05.160 --> 06:10.320
We also have these coordinates of the middle of the screen. So x_mid and y_mid will be the middle.

06:10.770 --> 06:12.090
So you can see that these all

06:12.120 --> 06:18.960
line up with the middle. And that these are all symmetric about the middle of the screen, vertically

06:18.960 --> 06:19.380
speaking.

06:20.700 --> 06:23.100
We start off by drawing this column.

06:24.240 --> 06:26.700
So this is going to be x units thick.

06:27.480 --> 06:30.210
It is going to go from y equals 0 all the way up to the top.

06:30.930 --> 06:33.360
So that is going to be up to the height of the screen.

06:34.200 --> 06:40.470
Then we have these two cross pieces, which start at the edge of this column. And they go all the way

06:40.470 --> 06:41.010
to the middle.

06:41.700 --> 06:43.590
This one goes from y equals 0.

06:44.130 --> 06:46.800
It is one thickness [high], so it goes up to y_unit.

06:47.730 --> 06:48.560
This one ends

06:48.570 --> 06:54.000
at the [top] of the screen, so it starts from height minus Y units.

06:55.710 --> 07:00.120
And then the pluses are just 2x1 rectangles superimposed on each other.

07:00.130 --> 07:03.140
So this is 2 units high.

07:03.150 --> 07:07.200
And 1 unit wide. This one is 2 units wide and 1 unit high.

07:08.070 --> 07:12.270
And this one is exactly the same, but it is offset by 3 thicknesses.

07:12.570 --> 07:17.700
So there is 1 unit of thickness there and then another 2 units for the thickness of the plus sign.

07:21.000 --> 07:26.000
If you are not too sure about how to work this out, you can always use trial and error. Some trial and

07:26.020 --> 07:28.560
error may have been involved when I created this program :)

07:33.800 --> 07:39.290
We are going to write a class to represents the bitmap in memory - or the image data part of it anyway.

07:40.190 --> 07:44.360
We are going to use a vector of pixels to store the image data.

07:44.630 --> 07:50.390
So this is going to be a vector of the vector elements, the pixel objects we defined earlier.

07:52.700 --> 07:57.380
And we also have the width and height of the screen, and the name of the file that we are going to write

07:57.380 --> 07:58.550
the bitmap file to.

08:01.210 --> 08:02.680
So this is going to store all the pixels.

08:03.610 --> 08:08.350
The first pixel in the vector will be the one in the bottom left of the screen.

08:08.770 --> 08:11.830
The next one will be the first pixel after that. And so on.

08:12.640 --> 08:18.190
So the pixels will be in the same order in this vector as they are on the screen.

08:18.430 --> 08:22.750
So this means that the index of the element is also going to be the offset of the pixel.

08:23.650 --> 08:29.230
So if we have a pixel at x and y and we want to retrieve it from the vector, we work out the offset.

08:29.710 --> 08:35.560
y times the width of the screen plus x. And then we look up the elements which has that index.

08:38.540 --> 08:43.310
And finally, we have some public member functions. We are going to have a constructor; a member function

08:43.310 --> 08:49.010
for writing out the file, which is the point of the exercise; and perhaps some member functions for

08:49.010 --> 08:50.810
writing blocks of pixels.

08:57.270 --> 09:02.370
And then for actually writing out the file, we need to write out the file header, the info header

09:02.370 --> 09:07.110
and the image data. For the file header and the info header,

09:07.530 --> 09:10.340
we can just do the same trick that we had in the last video.

09:10.350 --> 09:17.670
We take the address of the object, convert it to a pointer to char, and pass the size of the object

09:17.670 --> 09:18.690
as the second argument.

09:21.960 --> 09:27.070
With the image data, we need to convert the vector elements to an array of pixels.

09:27.480 --> 09:32.280
And if you remember the video, there is the data() member function, which will return the array.

09:33.420 --> 09:34.680
And then we cast that to char.

09:35.250 --> 09:41.070
The second argument will be the number of pixels in the vector multiplied by the size of each pixel.

09:42.780 --> 09:45.890
So here's our bitmap header, bitmap dot h.

09:46.350 --> 09:50.670
We have the bitmap file header, with its unusual alignment.

09:53.050 --> 09:57.100
We have our info header, with all these default initial values.

09:58.240 --> 10:04.780
We have the pixel struct to store the colours for each pixel, and then we have the bitmap class.

10:06.460 --> 10:13.990
With our data members and the member functions. For the constructor, we set the name of the file.

10:14.860 --> 10:18.880
And then we want to initialize the vector, so that it has the right size to store all the elements.

10:19.720 --> 10:22.720
The vector class has a constructor which takes the number of elements.

10:23.230 --> 10:27.880
So if we do width times height, that will give us the total number of pixels in the image.

10:28.540 --> 10:33.790
And then, if we pass that as the argument, then that will initialize the vector with enough elements

10:33.790 --> 10:34.990
to hold all the pixels.

10:35.920 --> 10:40.000
And then we do not need to do anything else. Because we have all these default initializers,

10:40.000 --> 10:43.500
We do not need to worry about initializing those

10:43.510 --> 10:43.930
headers.

10:46.910 --> 10:50.330
Then we have a member function for setting the colour of a pixel.

10:50.360 --> 10:57.260
So we give it a pixel object, which has the colours we want. Then we say x and y for the position of the

10:57.260 --> 10:59.300
pixel we want to adjust.

10:59.930 --> 11:06.140
And then this will find the pixel at those coordinates, and set it equal to the colours in that pixel.

11:06.980 --> 11:13.490
Then we have one for setting all the pixels in a row to the same values. So it will take the row

11:13.490 --> 11:13.790
number.

11:13.800 --> 11:20.090
So that will be the y coordinate, and it will set all the pixels in that row to have that value.

11:20.780 --> 11:24.260
And then, finally, one for setting all the pixels in the image to a single colour.

11:26.760 --> 11:31.830
And of course, we have the write function, which is the whole reason for doing this exercise.

11:36.010 --> 11:41.410
Here is this write function. So we need to write out the file header, the info header and the data.

11:41.980 --> 11:45.960
First of all, we need to fill in a couple of fields in these headers. In the file

11:46.070 --> 11:50.460
header, we need to provide the file size and the data offset. The size

11:50.480 --> 11:52.750
of the file is going to be equal to the size of the header,

11:53.380 --> 11:58.960
plus the size of the info header, plus the total memory used by the pixels.

11:59.530 --> 12:01.000
So that is going to be the number of pixels,

12:01.000 --> 12:05.290
the width times the height, Multiplied by the memory used by a single pixel.

12:06.400 --> 12:07.600
The offset of the data.

12:08.170 --> 12:11.980
To get to the data, we first have to go past the file header and then the info header.

12:12.430 --> 12:16.390
So once we have gone past that many bytes, we will be at the start of the data.

12:16.720 --> 12:19.240
So that is the value of the data offset in the file header.

12:20.710 --> 12:23.860
In the info header, we need to set the width and height of the image.

12:25.600 --> 12:30.420
Then we can open the file where we are going to write the bitmap. Using binary

12:30.420 --> 12:35.320
mode, of course! And we check that the file is successfully opened.

12:35.830 --> 12:39.100
If it is not, the caller will inform the user.

12:41.590 --> 12:46.120
And then we write out the data into the file, as we saw on the slides.

12:49.840 --> 12:55.690
And then we return true or false, and of course, we close the file if it was open.

13:00.380 --> 13:08.150
For the member functions. The set_pixel(). So this will take a pixel and set the pixel, which

13:08.150 --> 13:10.370
is at x and y, to be the same as that pixel.

13:11.150 --> 13:16.850
So we need to calculate the offset of the pixel at x and y, and then that will give us the index into

13:16.850 --> 13:19.400
the vector. And then we can just assign 

13:19.400 --> 13:24.470
that elements in the vector. So that pixel will now have the same value as the one that we passed

13:24.470 --> 13:25.100
to the function.

13:28.200 --> 13:30.240
And then, for setting all the pixels in a row.

13:30.810 --> 13:37.530
So this will take a pixel and then we iterate over all the pixels in a single rwo, so with the same

13:37.530 --> 13:38.370
y coordinate.

13:39.030 --> 13:41.400
And then we call set_pixel() for each of those.

13:43.990 --> 13:51.340
And, for setting all the pixels in the image, we iterate over all the rows in the image and then for each

13:51.340 --> 13:56.380
row, we set all the pixels in the row. Which will call this member function, and then that will set

13:56.380 --> 13:57.190
all the pixels.

13:57.700 --> 14:05.680
Finally, the main program, which is going to run all this code, we calculate the midpoint of the

14:05.680 --> 14:07.380
screen and also the thicknesses.

14:07.750 --> 14:10.240
I just used one 8th of the screen height and width.

14:10.240 --> 14:11.590
You can change that if you like.

14:12.850 --> 14:14.800
We create a bitmap object.

14:14.800 --> 14:17.860
We pass the name of the file that we are going to use as the argument.

14:19.030 --> 14:24.880
We create a pixel in which all the primary colours are 127. So that is 50 per cent.

14:25.510 --> 14:27.430
So that is going to be a mid-grey shade.

14:28.150 --> 14:30.490
And then we set all the pixels in the image to have this.

14:30.880 --> 14:36.280
So now, the representation of the image in memory is of a completely grey image.

14:38.500 --> 14:41.920
And then we create a pixel with the colour we are going to use for the blocks.

14:42.550 --> 14:48.150
So blue is 255, green is 255, which are both the maximum. Red is just 25,

14:48.160 --> 14:50.280
so that's just a little hint of red, maybe!

14:50.280 --> 14:57.760
This is going to produce a bluish green colour, which is cyan, or perhaps aqua, or maybe

14:57.760 --> 14:58.420
some other colour.

15:01.260 --> 15:06.390
To draw the stem of the 'C', we start at the bottom left hand corner: x equals 0, y equals 0.

15:06.900 --> 15:13.440
We go across, 1 unit of thickness, and we go all the way up the screen. And then we set all the pixels

15:13.830 --> 15:15.250
in that column to be this

15:15.270 --> 15:16.080
cyan colour.

15:18.070 --> 15:25.060
And then, for the cross pieces, we start one thickness in, from where we left off. The one for the bottom goes from y equals

15:25.060 --> 15:26.370
0, one thickness high.

15:27.610 --> 15:32.320
And then the one at the top of the screen goes up to the top of the screen, starting from one unit

15:32.320 --> 15:32.710
below.

15:34.520 --> 15:39.530
And then the rest is just a bit of algebra. Arithmetic, if you like.

15:41.240 --> 15:45.920
You have the source code as a downloadable resource, so you can just copy this. Or you could try to

15:45.920 --> 15:47.240
work it out for yourself, if you like.

15:49.550 --> 15:54.170
And then we have created this vector full of pixels with all the colours that we want. And we are now

15:54.230 --> 15:55.880
ready to write the file out.

15:56.330 --> 16:01.940
So we write it to disk. And then we can find out if it successfully wrote the file, or if something

16:01.940 --> 16:02.420
went wrong.

16:03.620 --> 16:05.000
So, does it work?

16:07.410 --> 16:10.050
So "Succeeded". So that means we managed to write the file.

16:10.290 --> 16:12.720
It does not necessarily mean that the bitmap is valid.

16:13.980 --> 16:17.740
And if you do have any problems, it means that you have made it a small mistake somewhere.

16:19.080 --> 16:24.360
I have seen cases where there was a loop with "less then" and someone put "less than equals" by mistake, and that

16:24.360 --> 16:25.470
made the image invalid.

16:25.950 --> 16:27.090
So it can be really trivial.

16:27.510 --> 16:30.360
So you do need to check everything very closely and double check it.

16:33.760 --> 16:35.860
So do we have a valid image?

16:40.010 --> 16:40.580
Yes, we do.

16:42.590 --> 16:49.310
So there it is, C++. Maybe not a great masterpiece, but it is a start! And in theory, now that you

16:49.310 --> 16:53.780
know how to work with binary files, you can go on to other formats, which are probably quite a bit more

16:53.780 --> 16:54.350
involved.

16:54.770 --> 16:56.690
But the principle is the same.

16:57.590 --> 17:04.070
So for multimedia files, saving the state of the game, creating your own document format, and so on.

17:04.460 --> 17:06.320
Okay, so that is all for this video.

17:06.740 --> 17:10.070
I will see you next time, but until then, keep coding!
