Copyright 2021-2022 Omer Tarik Ilhan 314CA

	This simple image editor starts by entering a loop, which is only
broken by the EXIT command. Whenever a valid command is introduced,
a corresponding function is called, either waiting for further
command parameters or executing right away.

	Every command has both an effect on the image (except SELECT, which
affects further commands) and a stdout output, meaning that the
task is complete.

	Firstly, I shall explain the main construction foundation on which
the image editor is built: the "img" data structure. It contains every
needed information about the currently loaded image: type, width, height,
max value and the corresponding pixel-map. When a grayscale image is loaded,
only the GS double pointer is used to hold the pixel-map and when an RGB
image is loaded, the R, G and B pointers are used to hold their respective
color matrix. The col and format variables are both based on the image type
(explained when first used), and is_loaded represents whether an image is
currently loaded or not.

	I will now explain every command and the steps that are followed in order
to accomplish the desired task.

LOAD:
	The first and most important command is, of course, LOAD.
The command's parameter represents the relative path to the file in need
of "loading". It checks whether the file can be opened. If so, the header
is read, which contains the needed information in order to start reading
the pixel-map matrix. According to the image type, a specific function
that allocates and reads the matrix / matrices
is called (ascii / binary, grayscale / RGB).

EXIT:
	The exit command is simple. When the corresponding function
is called, all allocated memory is freed. Then, the loop is broken.

SELECT:
	When called, the given parameters are validated, then they are memorised
in the struct's coordinates. Valid parameters are either "ALL", which selects
the whole image, or 4 integers that are within the dimensions of the image,
which represent 2 points on a 2-axis coordinate system, with the origin in
the top-left corner of the image.

CROP:
	The logic behind the crop function is simple. Allocate 1-3 matrices
depending on the image type, with the dimension the same as the selection,
then fill those matrices with values from the main matrix / matrices,
which are within the selection. Free the memory occupied by the old
matrix and memorise the new matrix to the according struct matrices.
The new image dimension are the selection dimensions (height is y2 - y1
and width is x2 - x1).

ROTATE:
	One of the most difficult commands to complete. Depending on the image
type and the current selection, matrices are allocated. When the selection
is the whole image, a new matrix with the same sizes is allocated, but
they can also be inverted (if the selection is +/-90, +/-270). If the
selection is a square from within the image, the a smaller, square matrix
is allocated with the size of the selection. All values from withing the main
matrix are copied to the new auxiliary in such a way that it is rotated
to the angle given as a parameter. If the whole image is selected, the old
matrix is freed and then replaces by a pointer to the new, rotated matrix.
If there is only a part of the image selected, then rotated values are copied
back to the main matrix, in place, then the auxiliary matrix is freed.

APPLY:
	The apply function was not very difficult to create, but rather difficult
to correct small details, mostly regarding aproximation. Depending on the
command parameter (SHARPEN, EDGE, BLUR, GAUSSIAN BLUR), the corresponding
filter is applied on the selected portion of the pixel-map. First, we create
the 3 x 3 kernel matrix necessary for the mentioned filter. Then, we use
the apply_kernel() function to generate one pixel at a time, based on
itself and the other 8 pixels around it. Multiplying those 9 values with
the corresponding kernel values gives us the wanted pixel. We use the
apply_kernel_on_matrix() and apply_kernel_on_matrix_div() functions to
get the selection of pixels calculated and copy them to newly allocated
matrices, based on the original image. Finally, we use the apply_filter()
function to copy the filtered pixels on the main image, before freeing
the auxiliary matrices.

SAVE:
	The save command takes as a parameter the path to the desired saving
location and name. An optional parameter is the "ascii" string. Based on
the image type and whether the ascii parameter is present or not, a file
with the mentioned name is created, filled with the image header (type,
width, height, max value), then the pixel-map is written either in ascii
or in binary.

	I hope that I was thorough enough :)