20151005:Practical:C:SDL:Arrays
Sommaire
Introduction
This session will introduce some array manipulations and simple image loading and manipulation using the SDL library.
GIT set-up
GIT provides mechanism to keep track of versions of files and synchronization between copies of the same source tree. Basic operations you need to know are how to submit (commit) your changes, how to push your changes to a server and how to pull new version from a server.
Setting the environment
There's a file containing global configuration for GIT in your home directory. The file ~/.gitconfig will hold basic settings like your name, email address and global options. Here is an example of this configuration file:
[user] name = John Simth email = john.simth@epita.fr [color] ui = true [push] default = simple
SSH Key
One way to authenticate on a git server is to use ssh, most of the time using a public/private keys pair. If you haven't generate such a keys pair, here is a simple way to do it:
> ssh-keygen -t rsa -b 4096 -f ~/.ssh/my_git_key_file Generating public/private rsa key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/login/.ssh/my_git_key_file. Your public key has been saved in /home/login/.ssh/my_git_key_file.pub. The key fingerprint is: [ ... snip ... ]
Be sure that the directory ~/.ssh has been created before.
The public key that you will provides for the ACU intranet (providing authentification for git) will be my_git_key_file.pub.
You can add login information for the ACU's GIT services inside the file ~/.ssh/config:
Host git.acu.epita.fr User git IdentityFile ~/.ssh/my_git_key_file
You can now upload the key to the ACU's intranet [1] (deploy the menu under the login, and use the link related to ssh keys and follow the instructions.)
Cloning a repository
Now, you can clone praticals' repository to start-up working, go to a working directory of your own, and then use git clone:
> git clone git@git.acu.epita.fr:s3-tuto-2019/login-s3-tuto.git
You have two repositories:
- one for all the practical session (git@git.acu.epita.fr:s3-tuto-2019/login-s3-tuto)
- one for the project (git@git.acu.epita.fr:s3-projects-2019/login-s3-projects) where login is the login of the group leader
Remarque: any members of the group is able to clone and/or update the project repository, you don't need to be the project leader.
Now we can do simple manipulations:
> cd login-s3-tuto > mkdir 20151005_practical > cd 20151005_practical > echo '* login' > AUTHORS > git add AUTHORS > git commit -m "Initial commit" > git push -u origin master
OK, first lines are obvious. In the fourth line, we add AUTHORS to the list of tracked files, then in the fourth line we commit the change with a simple message and finally we push the change to the server (specifying that we want the distant server called origin to be the default for push/pull.)
Basic Commands
Now you can try to edit in an other place the repository and synchronize your first version.
For example, if you have already cloned the repository and made pushed some modification from another copy, you can update the local version:
> cd login-s3-tuto > git pull --rebase
You can check the status of tracked files and unadded files using git status and then add modified or newly created files:
> git status status output > git add my_modified_file.c my_new_file.c > git commit -m "some message explaining what I've done." > git push
If you need help on git, you can use inline docs:
> git help ... > git help add ... > man 7 giteveryday ... > man 7 gitcore-tutorial ...
Or dig a little bit in git documentations here:
- http://git-scm.com/documentation
- http://git-scm.com/docs/gittutorial
- https://www.atlassian.com/git/tutorials/
Ignore binary files
You should avoid pushing binary files in your repository, in particular you must not add .o files.
One of the simplest way to avoid adding files you don't want is to set-up a file called .gitignore. This file can live in the root of your repo or in any sub-directories (in this case, its effect is limited to the corresponding sub-dirs.) The format of the file is pretty simple: each line is a pattern for reject files (using classical regexp), here is a simple example:
*.o *~ *.tar *.tar.gz *.tar.bz2 *.tgz
The first pattern *.o reject all object code produced by the compiler, the second one reject emacs' backup files, and the remaining lines reject several form of tar archives (compressed or not.)
You can set-up an ignore file for all your practical sessions:
> cd login-s3-tuto > cat .gitignore *.o *~ *.tar *.tar.gz *.tar.bz2 *.tgz > git add .gitignore > git commit -m "Adding a gitignore file." > git push
Arrays
The following serie of exercises introduces basic array manipulation.
Your goal is to complete the following litle program:
# define _XOPEN_SOURCE 500 # include <assert.h> # include <err.h> # include <stdio.h> # include <stdlib.h> /* Provided code for testing and inspiration */ static inline void swap(int *a, int *b) { int c = *a; *a = *b; *b = c; } static void fill_array(int arr[], size_t len) { for (size_t i = 0; i < len; i++) arr[i] = random() % 1000; } static void print_array(int arr[], size_t len) { for (size_t i = 0; i < len; i++) { printf("| %3d ", arr[i]); } printf("|\n"); } /**********************************/ /* Code to be completed */ /* See subject for specifications */ /**********************************/ int* min_pos(int arr[], size_t len) { /* FIX ME */ } void select_sort(int arr[], size_t len) { /* FIX ME */ } int is_sorted(int arr[], size_t len) { /* FIX ME */ } /* entry point */ int main(int argc, char *argv[]) { // Need a length if (argc < 2) errx(1, "missing array len"); size_t len = strtoul(argv[1], NULL, 10); // Create and fill array int *arr = malloc(len * sizeof (int)); fill_array(arr, len); // Print the array print_array(arr, len); // Sort array using your code select_sort(arr, len); // Print it again print_array(arr, len); // Assertion: verify that array is sorted assert(is_sorted(arr, len)); return 0; }
Finding minimal element
- Implement the following function:
int* min_pos(int arr[], size_t len);
This function looks for the cell containing the minimal value in the array arr of length len and returns a pointer to the cell.
You can get the address of a cell using either &(arr[i]) or arr + i.
Selection sort
- Implement the following function:
void select_sort(int arr[], size_t len);
This function implements the selection sort algorithm, that can be described as follow:
select_sort(tab, len): for i = 0 to len - 2 do min = find position of minimal in tab swap tab[i] and tab[min] done
Of course, you can use the functions swap (provided) and the function min_pos of the previous function.
Testing sorted array
- Implement the following function:
int is_sorted(int arr[], size_t len);
This function returns true (not 0), if the array arr of length len is sorted in increasing order and false (0) otherwise.
Using SDL
Checking for the libs
Check if you got all you need: pkg-config give you option for your compiler, among which you'll find the installation path of the lib. You should get similar input to mine:
> pkg-config --cflags sdl -D_GNU_SOURCE=1 -D_REENTRANT -I/usr/include/SDL > ls /usr/include/SDL/SDL.h /usr/include/SDL/SDL_image.h /usr/include/SDL/SDL.h /usr/include/SDL/SDL_image.h
Simple Makefile
Here is a simple Makefile for the exercises using SDL:
## Simple SDL mini code CC=clang CPPFLAGS= `pkg-config --cflags sdl` CFLAGS= -Wall -Wextra -Werror -std=c99 -O3 LDFLAGS= LDLIBS= `pkg-config --libs sdl` -lSDL_image SRC= pixel_operations.c main.c OBJ= ${SRC:.c=.o} all: main main: ${OBJ} clean: rm -f *~ *.o rm -f main # END
Start-up code
First, we need to init SDL, open a window, load an image, display it and wait for a pressed key. This part of the code is not really interesting, I'll just give the code with some comments.
Waiting for a key:
void wait_for_keypressed(void) { SDL_Event event; // Infinite loop, waiting for event for (;;) { // Take an event SDL_PollEvent( &event ); // Switch on event type switch (event.type) { // Someone pressed a key -> leave the function case SDL_KEYDOWN: return; default: break; } // Loop until we got the expected event } }
Initializing SDL:
void init_sdl(void) { // Init only the video part if( SDL_Init(SDL_INIT_VIDEO)==-1 ) { // If it fails, die with an error message errx(1,"Could not initialize SDL: %s.\n", SDL_GetError()); } // We don't really need a function for that ... }
Loading an image from a file:
SDL_Surface* load_image(char *path) { SDL_Surface *img; // Load an image using SDL_image with format detection img = IMG_Load(path); if (!img) // If it fails, die with an error message errx(3, "can't load %s: %s", path, IMG_GetError()); return img; }
Now, we can write a function that take the surface corresponding to a loaded image and open a window with the same dimension, display the image on it and wait for a key:
SDL_Surface* display_image(SDL_Surface *img) { SDL_Surface *screen; // Set the window to the same size as the image screen = SDL_SetVideoMode(img->w, img->h, 0, SDL_SWSURFACE|SDL_ANYFORMAT); if ( screen == NULL ) { // error management errx(1, "Couldn't set %dx%d video mode: %s\n", img->w, img->h, SDL_GetError()); } /* Blit onto the screen surface */ if(SDL_BlitSurface(img, NULL, screen, NULL) < 0) warnx("BlitSurface error: %s\n", SDL_GetError()); // Update the screen SDL_UpdateRect(screen, 0, 0, img->w, img->h); // wait for a key wait_for_keypressed(); // return the screen for further uses return screen; }
Once you're finished with your image, you should free it using SDL_FreeSurface().
- Put everything into a file main.c add the headers SDL/SDL.h and SDL/SDL_image.h and write the main function.
Pixel operations
These functions, derived from SDL documentation, are used to get a pixel value or to set it in a surface. They manage all image formats and return a unified type for a pixel that can be used with SDL_GetRGB and SDL_MapRGB to map the pixel to RGB component.
First, the header:
// pixel_operations.h # ifndef PIXEL_OPERATIONS_H_ # define PIXEL_OPERATIONS_H_ # include <stdlib.h> # include <SDL.h> Uint32 getpixel(SDL_Surface *surface, unsigned x, unsigned y); void putpixel(SDL_Surface *surface, unsigned x, unsigned y, Uint32 pixel); # endif
Then the code:
// pixel_operations.c // Simple get/put pixel for SDL // Inspired by code from SDL documentation // (http://www.libsdl.org/release/SDL-1.2.15/docs/html/guidevideo.html) # include "pixel_operations.h" static inline Uint8* pixelref(SDL_Surface *surf, unsigned x, unsigned y) { int bpp = surf->format->BytesPerPixel; return (Uint8*)surf->pixels + y * surf->pitch + x * bpp; } Uint32 getpixel(SDL_Surface *surface, unsigned x, unsigned y) { Uint8 *p = pixelref(surface, x, y); switch(surface->format->BytesPerPixel) { case 1: return *p; case 2: return *(Uint16 *)p; case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) return p[0] << 16 | p[1] << 8 | p[2]; else return p[0] | p[1] << 8 | p[2] << 16; case 4: return *(Uint32 *)p; } return 0; } void putpixel(SDL_Surface *surface, unsigned x, unsigned y, Uint32 pixel) { Uint8 *p = pixelref(surface, x, y); switch(surface->format->BytesPerPixel) { case 1: *p = pixel; break; case 2: *(Uint16 *)p = pixel; break; case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) { p[0] = (pixel >> 16) & 0xff; p[1] = (pixel >> 8) & 0xff; p[2] = pixel & 0xff; } else { p[0] = pixel & 0xff; p[1] = (pixel >> 8) & 0xff; p[2] = (pixel >> 16) & 0xff; } break; case 4: *(Uint32 *)p = pixel; break; } }
Converting an image to grey level
As an application will now iterate over the image in order to transform it into a grey image. Our conversion will use one of the classical conversion based on luminance definition. The basic version compute the average of the RGB components and then set all component to this value. While this is supposed to be correct, human perception of colors are slightly different: green appears brighter than red which is also brighter than blue. Thus, we'll compute a pondered average using the following coefficient: 0.3 for red, 0.59 for green and 0.11 for blue.
In order to convert the image you first need to be able to convert a given pixel:
- from the Uint32 pixel value, you can obtain three Uint8 value using SDL_GetRGB(pixel, img->format, &r, &g, &b) where pixel is the pixel value, img is the surface corresponding to your image and r, g, b are three Uint8 variable that will contains the RGB components.
- Using the previous coefficients compute the average value for the luminance (using a floating point variable to store the result)
- Set r, g, b to the luminance
- Get the new pixel value with SDL_MapRGB(img->format, r, g, b)
Once ready, you need to iterate over the pixel of the image: the surface provides img->w which is the width of the image and img->h which the height, just need a double for loop, the get and put pixel operations and this is it.
- Extend the previous code (in main.c) so that after displaying the original image, it transforms the surface in grey level and blit it (take a look at the provided code) to the screen once again, then wait for a key and leave.