0% found this document useful (0 votes)
8 views14 pages

FinalProject Checkpoint4

Uploaded by

Bo Stevens
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views14 pages

FinalProject Checkpoint4

Uploaded by

Bo Stevens
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 14

CS-2810 Final Project

- Checkpoint 4

Checkpoint Description
So far, this project has been very heavy on concepts revolving around graphics, and object-oriented programming. This is the checkpoint
where we are going to start applying knowledge from this particular course. This time, we are going to be focusing in pretty hard on two
particular subjects –

Memory Hierarchy & Binary Representation

We’re going to rely on these topics to add the ability to both parse and load bitmap images into our program.

By the end, you too will be able to have your own Cactus Jack in your Edit Window

Objectives
1. Objective 1
2. Objective 2
3. Objective 3

Objective 1

Objective checklist:
• Fix up our ability to draw doodles
o Get rid of the pixel-gaps in our doodles
o Make the lines that we draw thicker
1.0 Fixing our Doodles
If we run our doodling program right now, and try to create a doodle inside of it, we’ll likely end up a little disappointed by the results we get.
My best attempt at drawing a smiley face

So what’s causing this to happen?

Remember back at the beginning of this project when I said that all programs can only “refresh” themselves so often? In our case, our program only
“refreshes” 60 times per second. We know this because of this line in DesktopLauncher.java

public class DesktopLauncher {


public static void main (String[] arg) {
Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
config.setForegroundFPS(60);
setForegroundFPS sets our “refresh” rate to 60 times per second

We can get BETTER results by cranking this number as high as we can.

Refresh rate set to 8,000

But we’re still not getting “perfect” results. There are still lots of annoying and gross looking gaps between all of our pixels. This is happening
because our mouse is capable of moving far faster than our program can ever “refresh”. The image below illustrates the problem

Our mouse moving over multiple pixels before the program has a chance to refresh

Because our program only colors in whatever pixel is CURRENTLY moused over, we can end up skipping a lot of pixels along the way. We could fix
this ourselves, but LibGDX has a nice way of doing this for us. We can use a new method to help us out here.
Hop into EditWindow.Java and change the following code.
private void paintAtPosition(Vector2 worldPosition) {
_doodleMap.drawLine(0, 0, 300, 300);
_doodleMap.drawPixel((int) (worldPosition.x - Position.x), (int) (Scale.y - worldPosition.y));

Now, when we click, we won’t be drawing where our mouse is pointing just yet, but notice how we draw a perfect FULL line across the screen
A perfect line with no skipped or missing pixels

Let’s use .drawLine() to make our program fill in the gaps between the pixel we were JUST BARELY mousing over, and the pixel we are CURRENTLY
mousing over.
public class EditWindow extends Rec2D implements IClickable{
private Vector2 _previousPaintPosition;
private void paintAtPosition(Vector2 worldPosition) {
_doodleMap.drawLine(0, 0, 300, 300);
Vector2 paintPosition = new Vector2(worldPosition.x - Position.x,Scale.y - worldPosition.y);
_doodleMap.drawLine(
(int) _previousPaintPosition.x, (int) _previousPaintPosition.y,
(int) paintPosition.x, (int) paintPosition.y);
_previousPaintPosition = paintPosition;
DoodleTexture = new Texture(_doodleMap);
}

If we run this code as-is though, it’ll crash due to an annoying error where _previousPaintPosition is null at the start of our program. Let’s fix this
really quick before testing.
public void onClickDown(Vector2 clickPosition) {
if(_previousPaintPosition == null)
_previousPaintPosition = new Vector2(clickPosition.x - Position.x,Scale.y - clickPosition.y);
paintAtPosition(clickPosition);
}
Making sure _previousPaintPosition ALWAYS has a value

And if we now run our code –

Perfect FULL lines!

However, we do have on small issue still. If we let go of our mouse, then click somewhere else, our line will JUMP from where we “unclicked” to
where we “clicked”

Drawing a line, unclicking, then drawing another line starting from the red circle
To fix this, all we have to do is add a bit of code to our onClickUp() method

public void onClickUp(Vector2 clickPosition) { _previousPaintPosition = null;}


If you can draw the above image, you are good to move on
1.1 Better Lines
Right now, doodling can be a little difficult due to the fact that we’re only drawing lines that are 1 pixel thick. Let’s make some quick changes to
make our lines thicker, easier to see, and easier to work with.

Instead of ONE click drawing ONE line, a pixel thick, we’ll make ONE click draw a VARIETY of lines all offset from each other.
private void paintAtPosition(Vector2 worldPosition) {
Vector2 paintPosition = new Vector2(worldPosition.x - Position.x,Scale.y - worldPosition.y);
int startX = (int) _previousPaintPosition.x;
int startY = (int)_previousPaintPosition.y;
int endX = (int) paintPosition.x;
int endY = (int) paintPosition.y;
_doodleMap.drawLine(startX, startY, endX, endY);
_doodleMap.drawLine(startX + 1, startY, endX + 1, endY);
_doodleMap.drawLine(startX - 1, startY, endX - 1, endY);
_doodleMap.drawLine(
(int) _previousPaintPosition.x, (int) _previousPaintPosition.y,
(int) paintPosition.x, (int) paintPosition.y);

Our lines are thicker because we draw to the left and right of the clicked pixels

Perform the following –


Draw lines to the top and bottom of the clicked pixels as well
1.2 Testing
You should be able to draw the following WITHOUT any gaps between pixels

Objective 2

Checklist:
• Understand the motivation behind us choosing to use the .bmp image format
2.0 Introduction to Image Formats
Our application’s final purpose is to load images, let us edit them, and then save them back to a file with all of our changes. Doing this will require
an understanding of image file structure. Think for a moment on the multitude of different image file-formats that you’ve seen in your life.

png, jpg, gif, raw, pdf, etc.

If all of these formats solve the same task, storing image data, why are there so many different types?

The simple answer is that image data is MASSIVE.

Consider that the latest IPhone has a camera that shoots at 48-Megapixels. A Megapixel is a term used to represent 1,000,000 (One MILLION) pixels.
That means, an IPhone can take an image that contains 48 MILLION pixels. Recall that a pixel is just a container for color data, and colors are
represented by their red, green, blue, and alpha components.

If we use integers to represent red, green, blue , and alpha that means four ints make up a single pixel. In other terms, that would mean -

1 Pixel = 16 Bytes

And how large would a megapixel be?

1 MP = 16 Million Bytes
Or
1 MP = 16 Megabytes

And 48 Megapixels would therefore be –

768 Megabytes

That’s almost an entire Gigabyte! For reference, most images range from 1-30 Megabytes, DRAMATICALLY less than the number we just came up
with.

The reason why so many image formats exist is primarily to provide a sliding scale in terms of quality and storage space for users.
A png for example, applies heavy compression to an image in order to store it in a relatively small amount of space
A RAW image however, will store data without attempting to compress it, providing higher visual quality, at the cost of MASSIVELY increased storage
requirements.

2.2 Introduction to Bitmaps


Because there are simply so many image formats in the world, we won’t be able to program in support for ALL of them. Instead, we’ll focus on
supporting just one.

Isn’t there like, some way around having to do this manually?


The answer to this general question is almost always YES, regardless of what field or subject you’re referring to. However, doing this
manually will expose a lot of things that pertain heavily to this class, and will also give us easy access to some data that we’ll NEED later
on.

For this project, we’ll be working with .bmp files. This format was chosen primarily due to the fact that it applies ZERO compression to images
(which will make our job MUCH easier) and because it’s a moderately simple and popular format to work with.
In the Checkpoint 4 assignment, there are a number of image files. If you haven’t already, download those now.

Bitmap images work generally as follows


• Color data is stored in 3 values that represent red, green, and blue
o Bitmaps do not use Alpha or transparency
• Each piece of color data is (Generally!) stored as a byte of information
o 8 1’s or 0’s
o Stored as a value from 0-256
o Rgb components are stored in REVERSE order bgr
▪ 256, 0, 0 – Pure blue
▪ 0, 0, 256 – Pure red
▪ 256, 256, 256 – Pure white
• Each Bitmap has a file-header that describes basic file information
• Each Bitmap also has an information header that describes how data is formatted in the bitmap

Every file format in existence, holds some kind of file header. If you’ve never seen one before, the header for a bmp might look scary at first, but for
our purposes, will be much simpler than it might seem on the surface.
Chart can be found here -
https://www.ece.ualberta.ca/~elliott/ee552/studentAppNotes/2003_w/misc/bmp_file_format/bmp_file_format.htm

We’ll be referring to this table regularly, so feel free to save it or open it up on another monitor (if you have one)

Objective 3

Checklist:
• Load a bitmap image into our application so we can doodle over it
• Create utility methods that allow us to easily work around Java’s limited byte type
3.0 ImageInputOutput.java
Loading images into (and eventually back out of) our program will be a bit of a complicated task. To do this, let’s create a new class
ImageInputOutput.java and start by turning it into a Singleton.
public class ImageInputOutput {
public static ImageInputOutput Instance;
public ImageInputOutput() {
Instance = this;
}
}

Let’s also add a method to this class called loadImage() that takes a file-path (A String) as an argument.
public void loadImage(String filePath) {
System.out.println("I'm going to load " + filePath);
}

Let’s also make sure to call loadImage from somewhere in our program. The top of our create method inside seems like a fine enough place for now.
public void create () {
Instance = this;
new ImageInputOutput();
ImageInputOutput.Instance.loadImage("FakeFilepath");
The third line looks funny, but is technically valid code. Maybe don’t use this in your own programs going forwards, but I think it’s fun just
to know that you can get away with this sort of thing, so I’m keeping it for pure novelty

Run your code, and you should see a print statement verifying that our method is being called.
Woo!

3.1 Reading From a File


Alright, time to roll up our sleeves and get to work. Start by taking blackbuck.bmp, and putting it into our Image-Editor -> assets folder.

File can be found in the assignment for Checkpoint 4

This image is, as advertised, a picture of a deer with a black background

We’ll start by setting our file-path for loadImage to “blackbuck.bmp”

ImageInputOutput.Instance.loadImage("blackbuck.bmp");

We can now attempt to read in our file with the following code given to us by LibGDX
public void loadImage(String filePath) {
byte[] bytes = Gdx.files.internal(filePath).readBytes();
System.out.println("Loading file of size " + bytes.length);
}
Reading in the file, saving the contents to an array, then printing the length of that array

We can confirm that this worked by running and checking the console output against the size of the file, as read by our operating system.

Now, before moving forwards, I want to point out a couple of interesting and important items.

ONE: We’re using a file reading library given to us by LibGDX


TWO: We’re reading everything into an array of bytes (8 bits)
THREE: Some pieces of information in our file-header are bytes
(color data)
FOUR: Some pieces are given as ints (fileSize)

We’ll come back to these points as we go through the project, but I want to expand a little on them here.

Why are we using some library from LibGDX instead of a standard Java library?
LibGDX actually uses an InputStream itself, but by using the LibGDX version of FileIO, we can easily talk between projects. R ecall that
ImageEditor-core and ImageEditor are two entirely separate projects. We also have a vague, but general assurance that however libGDX
accomplishes this task, it will do it in a relatively speedy manner

I also want to pose the question – If some of the information in our file is actually made of integers (4 bytes), why are we reading everything in as
just bytes? Why not use a nicer library like Scanner, where we can differentiate the types of data we’re reading in?

Because these approaches make POOR USE of our CPU and memory hardware!
Recall that small buffer sizes, and “magical” data parsing are both incredibly expensive operations! We performed a demonstration showcasing this
in LTSDemo.java back in Section 7.

If the theme of this class is hardware utilization, we can MUCH BETTER utilize our hardware by reading everything in as simple bytes, then piecing
back together data as needed.

3.2 Reading the File Header


Alright, with all this talking out of the way (for now), let’s actually get started. We’ll begin by parsing the file we’ve been given to see if it is, in-fact a
bitmap image.

We can do this super easily by checking each of the first two bytes in our array
public void loadImage(String filePath) {
byte[] bytes = Gdx.files.internal(filePath).readBytes();
if(bytes[0] != 'B' || bytes[1] != 'M') {
System.out.println(filePath + " is NOT a bitmap image");
return;
}

If you now try to load another file type, your code will refuse to parse the image, and exit the loadImage method.

The next piece of data that we’ll need however, will prove to be a bit more complicated to parse

This is because it is given as a series of 4 bytes, that make up an integer value. Before moving forwards, let’s try to confirm what the value of FileSize
is supposed to be, before attempting to parse it out.

To do this, we are going to open blackbuck.bmp in a hex-editor.


For those that don’t know, hex editors are programs that display the raw byte data of files. Using one of these, we can check vis ually what
the contents of our file are. If you don’t have a hex -editor on your computer, I highly recommend the following website

https://hexed.it/

If using the above link, we can simply drag and drop our image into our webpage, and the center view should populate with data from our image. It
might look a bit intimidating, but I’ll break down the section that will be important to us going forwards.

1. This section showcases basic file information


2. Attempts to parse out the selected bytes in different formats. We’ll typically be looking at the 8-bit, 16-bit, and 32-bit integer sections
3. The number highlighted in blue is the “Currently Selected” byte of information. The Data Inspector uses the selected byte to parse from
4. This section attempts to parse the byte data of the file as ascii values

If we take a look, we should be able to see that the first TWO BYTES of our file represent the ascii characters for BM by taking a look at section 4. If
we now select the third byte in our file (by clicking), and read from section 2, we can try to figure out how big the file is supposed to be.
And, as expected, this number matches the expected result! However, note that the values for 32-bit and 24-bit remain the same. The exact details
of this go slightly beyond the scope of this project, but the reason for this is that byte-data in a bitmap image is stored in LITTLE-ENDIAN form. The
curious minded can read below for what that means.
https://en.wikipedia.org/wiki/Endianness

Also note that our first byte can also be parsed as the number 54.

Alright, now we can read in the size of the file very simply, using the following code.
public void loadImage(String filePath) {
byte[] bytes = Gdx.files.internal(filePath).readBytes();
if(bytes[0] != 'B' || bytes[1] != 'M') {
System.out.println(filePath + " is NOT a bitmap image");
return;
}
byte[] fileSize = {bytes[2], bytes[3], bytes[4], bytes[5]};
System.out.println("The size of the file is " +
fileSize[0] + " " + fileSize[1] + " " + fileSize[2] + " " + fileSize[3]);

And we should get the following output.

We can confirm that each of these individual bytes is correct by checking them against the values in our hex-editor.

This however, raises an INCREDIBLY important question.

We KNOW that our image is 786,486 bytes long


So HOW does 54, 0, 12, 0 turn into 786,486?

Consider the binary representation of each of these numbers


786,486 = 00000000 00001100 00000000 00110110
54 = 00110110
0 = 00000000
12 = 00001100
0 = 00000000

If we reverse the order of our individual bytes, and concatenate (smash) them together, we get the 4-byte binary number that represents 786,486!
So, all we have to do now is do that!

…..somehow

3.3 Bytes to Ints


We’re going to give our code the ability to turn any LITTLE-ENDIAN list of 4 or less bytes into an integer. We’ll do this by creating a new method
called bytesToInt. Right now, our codebase doesn’t have anywhere that a method like this would seem at-place, so let’s create a new class called
Util.java

public class Util {


public static int bytesToInt(byte[] bytes) {
return 0;
}
}

This time, just for the purpose of demonstration, instead of making Util a Singleton, like we have been, we’ll just manually mark our methods inside
as static.

DO NOT FORGET! MARK ALL METHODS INSIDE OF UTIL.JAVA AS STATIC!

Let’s also call our method, and send it some dummy data to work with so we can test our code as we go.
byte[] fileSize = {bytes[2], bytes[3], bytes[4], bytes[5]};
byte[] dummyData = {5, 10, 15, 20};
System.out.println("The size of the file is " + Util.bytesToInt(dummyData));
System.out.println("The size of the file is " +
fileSize[0] + " " + fileSize[1] + " " + fileSize[2] + " " + fileSize[3]);
Calling bytesToInt

Based on our current understanding, the values in dummyData have the following values.
5 = 00000101
10 = 00001010
15 = 00001111
20 = 00010100

If we reconstruct this binary data backwards, we can find the expected integer that these bytes represent.

00010100 00001111 00001010 00000101

Throwing this data into a simple binary calculator tells us that our array of bytes represents the integer value of –

336,529,925

Let’s hop into bytesToInt and start workshopping some code!


It might sound intuitive to say that all we need to do is add together our binary values in reverse order, and we’ll get the correct answer, but if we
perform the following-
public static int bytesToInt(byte[] bytes) {
int result = 0;
result += bytes[bytes.length - 1];
result += bytes[bytes.length - 2];
result += bytes[bytes.length - 3];
result += bytes[bytes.length - 4];
return result;
}

Then we’ll get this answer-

Which we know is incorrect. So, what’s going wrong here?


The answer is that the operation we performed previously WASN’T an addition, it was a concatenation. That being the case, it might then be
intuitive to try something like the following-
public static int bytesToInt(byte[] bytes) {
String byteString = bytes[3] + "" + bytes[2] + "" + bytes[1] + "" + bytes[0];
return Integer.parseInt(byteString);
}

However, this approach is WILDY unperformant, as it requires a large number of String operations.

AND it still doesn’t even give us the right answer!

This is because our bytes don’t have their BINARY data stored into the string, but their DECIMAL NUMERAL data.

So how do we do it?

This is where we use an operation that many of you may never have used before, the bit-shift operation “<<”
Let’s start by first casting all of our bytes to ints
public static int bytesToInt(byte[] bytes) {
int first = bytes[0];
int second = bytes[1];
int third = bytes[2];
int fourth = bytes[3];
int result = 0;
It is now recommended that you begin following along again

Recall that “concatenating” our binary data would look as follows


00010100 00001111 00001010 00000101

However, it’s not immediately obvious how we might be able to achieve this operation ourselves. Instead, consider that we can “recreate” this
general behavior by using arithmetic instead by performing the following operations.
00010100 00000000 00000000 00000000
00000000 00001111 00000000 00000000
00000000 00000000 00001010 00000000
+00000000 00000000 00000000 00000101
= 00010100 00001111 00001010 00000101

That’s cool and all, but how am I supposed to get my numbers to look like that?

This is where bit shifting comes into play! Let’s take our variable last for example.

As-is, it looks like this


00000000 00000000 00000000 00010100= 20

But if we perform the following operation-

first = first << 1;


Shifting left by one bit

We’ll get the following number


00000000 00000000 00000000 00101000 = 40

And if we shift by an even greater number of bits, say for example, 24 bits

first = first << 24;

Well then, we’d have something that looks EXACTLY like what we wanted!
00101000 00000000 00000000 00000000

And then, if we bit shift second by 16 bits, we get-


00000000 00001111 00000000 00000000

And shift third by 8 bits-


00000000 00000000 00001010 00000000

Then, all we’d have to do is add these numbers together, and we’ll have completed our conversion! Below is a scalable version of the code that can
do this, in case we’re ever given 1, 2, 3, or 4 bytes in our array.

public static int bytesToInt(byte[] bytes) {


int result = 0;
for(int i = 0; i < bytes.length; i++) {
result += (int) bytes[i] << (8 * i);
}
return result;
}
That’s one TIDY piece of code right there!

Our dummy data now gives us-

Now we can get rid of our dummy data, and try running this on our actual fileSize array
byte[] fileSize = {bytes[2], bytes[3], bytes[4], bytes[5]};
byte[] dummyData = {5, 10, 15, 20};
System.out.println("The size of the file is " + Util.bytesToInt(dummyData));
System.out.println("The size of the file is " + Util.bytesToInt(fileSize));

This now gives us-

Which is exactly the same thing our Hex editor tells us

WOOHOO!
3.4 Reading Color Data
With this hurdle out of the way, now we can go ahead and attempt to parse out any other relevant information from the file header so that we can
start loading in actual Color data!

Now, to avoid getting a little TOO long in the tooth again, here are the following pieces of data that we’ll need.

• Starting point for color data


• Width and Height of the file
• Number of bits per pixel
The following code will read those items from the file-header and parse them as integers
byte[] fileSize = {bytes[2], bytes[3], bytes[4], bytes[5]};
byte[] start = {bytes[10], bytes[11], bytes[12], bytes[13]};
byte[] widthBytes = {bytes[18], bytes[19], bytes[20], bytes[21]};
byte[] heightBytes = {bytes[22], bytes[23], bytes[24], bytes[25]};
byte[] bitsPerPixel = {bytes[28], bytes[29]};
int startPoint = Util.bytesToInt(start);
int width = Util.bytesToInt(widthBytes);
int height = Util.bytesToInt(heightBytes);
int bytesPerPixel = Util.bytesToInt(bitsPerPixel) / 8;
if(bytesPerPixel != 3) {System.out.println("Unsupported image pixel format. Incorrect bits per pixel"); return;}
System.out.println("The size of the file is " + Util.bytesToInt(fileSize));
System.out.println("Loading file of size " + bytes.length);
Offsets pulled from the chart listing file-header information

We can now use these variables to create a new Pixmap with our width and height variables. We can also loop through our array of byte
information (starting from an offset at startPoint, and going until we hit fileSize)

Pixmap pixels = new Pixmap(width, height, Format.RGBA8888);


byte r,g,b;
for(int i = startPoint; i < bytes.length - 3; i += 3) {

}
A loop that will go through every pixel in our file

With this structure set up, perform the following –


• Each iteration of the loop, read in and save the r, g, and b components from bytes
• Each loop, set the active color of your pixmap to your r, g, and b components
o These are given as values from 0-256 from our file
o LibGDX expects color values from 0-1
o So divide each component by 256 when doing this
• Create variables x and y outside of the loop
o x starts at 0, y at 0
• Each iteration of the loop, call pimap.DrawPixel at x and y
• At the end of each loop, increment x by one
• If x is greater than the width of the image, set x to 0, and increment y by 1
• Change the return type of loadImage to Pixmap
• Return our Pixmap from loadImage

We can then perform the following in ImageEditor.java to see if we performed the correct operations.
Pixmap editMap = ImageInputOutput.Instance.loadImage("blackbuck.bmp");

Vector2 editWindowSize = new Vector2(500, ScreenSize.y - 50);
_editWindow = new EditWindow(editWindowSize, new Vector2(ScreenSize.x - editWindowSize.x, 0), Color.GRAY);

_editWindow.DoodleTexture = new Texture(editMap);

And we should see-

A really messed up image…

RATS!

Not only are our colors completely messed up, our image is also UPSIDE DOWN

3.5 Fixing our Image


Let’s start by flipping our picture right-side up. Luckily, this is simple to do. The culprit here is another issue of coordinate-systems not matching up
to our initial expectations. To fix it, perform the following-
• Go back into ImageInputOutput
• Start y at height
• Subtract y by 1 everything x is about to go beyond our width

If all goes well, our buck should now be right-side up.


So what’s causing our colors to be all messed up?
This part is actually…kind of infuriating.

I previously mentioned that each byte in our file has a value ranging from 0-256. This makes logical, intuitive sense, as 256 is the largest number you
can represent with 8-bits.

11111111 = 256

HOWEVER, let’s just try a little experiment real quick.


byte someByte = (byte) 150;
System.out.println("The value of someByte is " + someByte);

Okay….so it prints out 150, what’s the big idea here?


AHA! You see, it actually does something COMPLETELY different

This code ACTUALLY provides the following output-

This is because byte in Java is actually a SIGNED byte


And here’s the kicker-

There is NO UNSIGNED BYTE in Java, period.

Sigh…

This means that all of that awesome color data inside our bitmap, that ranges from 0-256, is getting completely misinterpreted and mangled by our
program.

So what do we do?
We manually unsign every byte we read from our file.

There are multiple ways of doing this, but I’m going to just turn our byte[] into an int[] because frankly, I’m rather annoyed. Hop into Util.java, and
add the following method.

public static int[] unsignBytes(byte[] bytes) {


int[] ints = new int[bytes.length];
for(int i = 0; i < bytes.length; i++) {

}
return ints;
}

Now, if we are willing to work with the fact that everything is an integer now, we can do some simple math to “correct” all of our negative numbers.

Anyone remember how overflow works?

Our bytes only become negative once they have run out of space, and overflow. With some simple research, we can find out at which point this
happens.

So, if a byte is between the range of 0-127, then it was interpreted properly. This means a value of -128 should actually be 128
-127 should be 129
-126 should be 130
Et cetera.

We can perform this conversion by performing the following-


• Take a byte from our array
• If it’s 0 or greater, add it to our int array
• If it’s negative
o Find out how far away it is from -129
o Take that distance, and add it to 127
o Add this new number to our int array
• Hop over to bytesToInt()
o Call unsignBytes() on bytes to make sure none of our
header data is corrupted
o Make bytesToInt use an int array instead of bytes

One last example:


Bytes[i] is -100
Distance = (-100) - (-129) = 29
Ints[i] = 127 + 29

Once you’ve filled out your method, perform the following in loadImage
• Create a new array of type int, called ints by calling unsignBytes on bytes at the top of your
method
• Instead of looping through bytes at the end, loop through ints
o Replace all references to bytes in your loop with your integer array
• Change the type of r,g, and b to int
• Ints and bytes are a little bit undescriptive
o Let’s rename them to fileBytes and fileIntData
o You can do this easily by right-clicking on the current variable name -> Refactor -> Rename
• Lastly, the rgb color values in our Bitmap are actually stored in backwards order bgr
o Rework the assignment of your rgb values to go in reverse order

3.6 Testing
If you open your program and see the following, then your program is correct

Attempting to draw over the image will cause our image to disappear, but THIS IS FINE FOR NOW

If you can see our buck, then-

You completed another checkpoint!!!

Whew! While this checkpoint was a little more hand-holding than the others, it presented some very challenging technical problems. By making it
this far, you might be surprised to know that we have cleared the last-largest hurdle in creating our image editor.
Next checkpoint, we will work on fixing the current issue that’s causing our buck to disappear once we start drawing, and we’ll also work in the
ability to save images back out to long-term storage.

And once that’s done, we can go on a final victory lap, and use some of the forgotten features we’ve built (I’m looking at you, Button), to kit out and
expand the functionality of our editor massively, without having to write a significant amount of code.

Remember to upload your changes to Github

You might also like