Ce mois-ci, nous vous proposons un exercice un peu plus compliqué. Il consistera à créer son propre format *.bmp, et à développer une bibliothèque pour pouvoir le manipuler.
Dans un premier temps, vous mettrez en place le format, puis vous créerez quelques fonctions pour pouvoir y dessiner (traçage de segments, cercles, etc.). Dans un deuxième temps, vous développerez un système d'exportation vers de vrais formats *.bmp (il y en a plusieurs). Dans un troisième temps, vous créerez un lecteur de votre format, à l'aide de la bibliothèque graphique SDL par exemple.
Si vous souhaitez plus de renseignements sur les défis, rendez vous sur le topic de recensement des défis, vous y trouverez également les règles principales.
zDessin
Nous allons donc commencer par définir notre format bmp : le zBMP.
Header : entête contenant des informations utiles
Palette : couleurs utilisées par les pixels
Pixels : pixels utilisés pour le dessin
OFFSET TAILLE DESCRIPTION
0 4 Nombre magique du fichier à définir. (zBMP)
4 2 Nombre de pixels en largeur.
6 2 Nombre de pixels en hauteur.
8 2 Nombre de bits par pixels (position dans la palette).
10 2 Nombre de bits pour une composante de couleur.
12 4 Nombre de couleurs dans la palette
La palette est un ensemble de couleurs.
Une couleur est un ensemble de 4 composantes : Red, Green, Blue et Alpha
(rouge, vert, bleu et opacité)
La partie pixels va donc indiquer, pour chaque pixel, la position de leur couleur dans la palette
Voici donc un exemple de fichier résultat :
Si je veux faire une image de 16x14 avec pour couleurs :
Couleur
R
G
B
A
Transparent
255
255
255
0
Rouge
198
0
0
255
Marron
143
60
31
255
Bleu
102
102
243
255
Mauve
247
214
181
255
Jaune
248
251
30
255
Noir
0
0
0
255
Alors, je pourrais avoir une entête/palette, qui ressemble à ça :
Image finale (agrandie : un rectangle pour chaque pixel)
NB : En retouchant le fichier, on peut avoir une jolie surprise
Rechercher/Remplacer :
Couleur
numero
caractere
Rouge
1
;
Marron
2
X
Bleu
3
=
Mauve
4
.
Jaune
5
-
Noir
6
#
Transparent
0
(espace)
_______________________
; ; ; ; ; . . .
; ; ; ; ; ; ; ; ; . .
X X X . . # . ; ; ;
X . X . . . # . . . ; ;
X . X X . . . # . . . ;
X X . . . . # # # # ;
. . . . . . . ; ;
; ; ; ; = ; ; ; = ; ; X
. . ; ; ; ; ; = ; ; ; = X X
. . . ; ; ; ; = = = = - = = X X
. = ; = = - = = = = = X X
X X X = = = = = = = = = X X
X X X = = = = = =
X X
_______________________
Étape 1 : Lecture/Écriture du fichier
Estimation de la difficulté : médium
Pour ce niveau, nous vous proposons de lire et d'écrire un fichier de ce format.
Aucun affichage du dessin ne vous sera encore demandé.
Il vous faudra cependant coder l'équivalent de ces fonctions :
Fonction loadZBmp(path) retourne Image
{Charge une image à partir de son fichier}
Paramètres
(I) path : Chaine {Chemin du fichier}
Fonction loadZBmpOn(img, path) retourne Image
{Charge une image dans img à partir de son fichier}
Paramètres
(O) img : Image {Image à charger}
(I) path : Chaine {Chemin du fichier}
Fonction getPixel(x, y, img) retourne Pixel
{Récupère un pixel selon sa position}
Paramètres
(I) x, y : entiers {coordonées du pixel}
(I) img : Image {Image en question}
Procédure saveZBmp(img, path)
{Sauvegarde l'image}
Paramètres
(I) img : Image {Image à sauvegarder}
(I) path : Chaine {Chemin du fichier}
Procédure freeZBmp(img)
{Libère l'image}
Paramètres
(I/O) img : Image {Image à libérer}
Étape 2 : Ajout de fonctions graphiques
Estimation de la difficulté : variable.
Ce niveau vous propose de créer des fonctions permettant de tracer des formes simples, comme la ligne, le rectangle, le cercle, l'ellipse, etc..
Le but étant d'obtenir l'algorithme le plus efficace possible.
Vous pourrez aussi créer des fonctions de remplissage.
Étape 3 : Exportation
Estimation de la difficulté : difficile
Ce niveau va vous proposer d’exporter notre petit format zBMP en un vrai de vrai : le format BMP. Pourquoi celui-là ? Parce que c’est l’un des formats réels les plus simples. Cependant, la partie est quand même loin d’être gagnée d’avance, vous allez voir.
Il s’agit ici de coder une fonction ressemblant à celle-ci :
Procédure saveBMP(img, path)
{Sauvegarde l'image au format BMP}
Paramètres
(I) img : Image {Image à sauvegarder}
(I) path : Chaine {Chemin du fichier}
En fait, il existe plusieurs versions du format BMP, pas entièrement compatibles entre elles, ce qui le rend compliqué à gérer. On va donc se fixer sur une de ces versions, la 3ème de Microsoft (coquettement nommée BITMAPINFOHEADER). C’est la version la plus courante, et celle qui correspond le mieux aux fonctionnalités de notre format zBMP.
Voici les champs que vous avez à remplir :
Résumé du format Microsoft BMP V3
Adresse
(hexa)
Taille
(en octets)
Champ
Valeur
Commentaire
Entête du fichier (14 octets)
00
2
Nombre magique
"BM"
"BM" en ASCII vaut 0x424D.
02
4
taille du fichier
(en octets)
-
-
06
4
réservé
0
-
0A
4
adresse de l’image
-
L’adresse dans le fichier où commence le tableau de pixels (le “bitmap”) composant l’image elle-même.
Entête de l’image (40 octets)
0E
4
taille de ce header
(en octets)
40
-
12
4
largeur
-
Contrairement aux autres champs, ceux-ci sont des entiers signés (voir explications en-dessous de ce tableau).
16
4
hauteur
-
1A
2
nombre de plans
1
-
1C
2
« profondeur », c’est-à-dire
nombre de bits par pixel
1, 4, 8, 16, 24 ou 32
Correspond à 4 × notre nombre de semi-octets par pixel.
1E
4
compression
Mettez 0 ou 3.
Mettez 3 si la profondeur vaut 16 ou 32 (indique qu’on utilise un masque binaire, cf ci-dessous), 0 sinon.
22
4
taille de l’image
(en octets)
-
Taille du “bitmap”.
26
4
résolution horizontale
(en px/m)
-
Contrairement aux autres champs, ceux-ci sont des entiers signés.
Si vous ne savez pas quoi mettre, mettez 3780 pour les deux.
2A
4
résolution verticale
(en px-m)
-
2E
4
nombre de couleurs
-
Indique le nombre de couleurs dans la palette. Vous pouvez mettre 0 pour indiquer la valeur maximale dépendant de la profondeur n (2n).
32
4
nombre de couleurs importantes
Mettez 0.
-
Palette
(optionnelle, taille variable)
La palette liste les couleurs utilisées dans l’image. Il s’agit d’un tableau de couleurs.
Chaque couleur est codée sur 4 octets par ses composantes RVBA (rouge, vert, bleu, alpha), chacune sur un octet. Un pixel est codé par l’indice de sa couleur dans la palette.
La palette n’est obligatoire que pour une profondeur inférieure à 16, et ne doit pas être présente sinon (la couleur étant codée directement dans le pixel).
Le nombre de couleurs dans la palette dépend de la profondeur n : sa valeur maximale est de 2n. Ce nombre peut cependant être plus petit, il est réglé par le champ correspondant de l’entête.
Masques binaires
(optionnels, 12 octets)
Cette section remplace la palette dans certains cas, si la compression vaut 3. Elle contient trois données, chacune sur 4 octets : les masques binaires pour chacune des composantes rouge, verte et bleue (dans cet ordre).
Ces masques binaires sont utilisés lorsque la profondeur vaut 16 ou 32. Ils indiquent quels bits du pixel codent chaque composante de couleur.
Si vous ne comprenez pas, contentez-vous de mettre ces valeurs en fonction de la valeur de la profondeur :
Si elle vaut 16, mettez 0xF8000000,0x007C0000,0x0001F000 (dans l’ordre RVB). Chaque composante sera alors codée par 5 bits du pixel, et le 16ème bit sera inutilisé.
Si elle vaut 32, mettez 0xFF000000,0x00FF0000,0x0000FF00. Chaque composante sera alors codée par 8 bits du pixel (soit un octet), et le 4ème octet sera inutilisé.
“Bitmap”
(taille variable)
Il s’agit du contenu de l’image lui-même, pixel par pixel et ligne par ligne.
Chaque pixel utilise le nombre de bits spécifié dans l’entête.
Si ce nombre est inférieur à 16, il s’agit de l’indice permettant de retrouver sa couleur dans la palette.
S’il vaut 16 ou 32, on utilise des masques binaires, comme détaillé ci-dessus.
S’il vaut 24, on stocke directement la valeur de la couleur avec ses composantes RVB.
Les lignes doivent utiliser un nombre d’octets multiple de 4. Si ce n’est pas le cas, on complète avec des octets nuls.
Toutes les données sont des entiers non signés, sauf les dimensions (et la résolution) qui sont signées. De plus, les pixels de l’image se lisent de bas en haut (!) et de gauche à droite. Une valeur négative pour la hauteur indique qu’il faut lire l’image dans le sens contraire (de haut en bas), et idem pour la largeur (de droite à gauche).
Enfin, dernier point important : toutes les données doivent être représentées en little-endian ! Et quand je dis « toutes », c’est toutes. Même le nombre magique doit être écrit « à l’envers » (0x4D42 au lieu de 0x424D, donc le M avant le B). Et aussi les composantes de couleurs : en réalité, si elles sont chacune sur un octet, elles devront être dans cet ordre : bleu, vert, rouge, alpha.
Au fait, vous avez vu que les couleurs peuvent avoir une composante alpha ; elle doit cependant toujours valoir 0 car le BMP ne gère pas la transparence.
Vous voyez, il y a du boulot ! Pour tester votre programme, vous pouvez tenter d’ouvrir l’image BMP que vous aurez générée avec un logiciel gérant les BMP ; si ça marche, vous pouvez être fier.
Étape 4 : Affichage
Estimation de la difficulté : facile
Ce niveau-ci, indépendant du niveau 3, demande à réaliser un lecteur de .zBmp
Vous pourrez, par exemple, utiliser la SDL.
Aller plus loin
On peut travailler sur les images de plusieurs manières que ce soit : Compressions, Traçages/Remplissages, Exports : vous pouvez faire encore pas mal de choses pour vous exercer !
Vous pouvez même compiler ce projet en une bibliothèque dynamique, si vous le souhaitez !
Si vous le souhaitez, vous pouvez aussi recréer un paint !
En plus, vous devriez pouvoir faire un générateur d'ASCII Art
_________________________
Cet exercice à été posté ici car il peut être codé à l'aide du C.
Cependant, il est réalisable par tout langage permettant la manipulation de fichiers et qui dispose d'une bibliothèque graphique.
Si vous souhaitez utiliser le langage algorithmique ou du pseudo code, les fonctions que vous pouvez utiliser sont les suivantes :
Fichiers
Fonction loadFile(path) retourne Fichier
{Charge un fichier en mémoire}
Paramètres
(I) path : Chaine {Chemin du fichier}
Fonction checkFile(file) retourne Booleen
{Indique si le fichier file est bien chargé}
Paramètres
(I) file : Fichier {Le fichier}
Procédure putByte(file, byte)
{Ecrit un octet dans le fichier file}
Paramètres
(I) file : Fichier {Le fichier chargé}
(I) byte : Octet {L'octet à écrire}
Fonction readByte(file) retourne Octet
{Lit un octet dans le fichier file}
Paramètres
(I) file : Fichier {Le fichier chargé}
Procedure rwdFile(file)
{Rembobine le fichier}
Paramètres
(I) file : Fichier {le fichier chargé}
Procedure closeFile(file)
{Ferme le fichier}
Paramètres
(I) file : Fichier {le fichier chargé}
Procedure delFile(path)
{Supprime le fichier}
Paramètres
(I) path : Chaine {le fichier à supprimer}
Procedure creatFile(path)
{Creer un fichier}
Paramètres
(I) path : Chaine {le fichier à creer}
Graphique:
Fonction createWindow(w, h) retourne Surface
{Cree une fenetre}
Paramètres
w : Entier {La largeur}
h : Entier {La hauteur}
Procedure freeWindow(screen)
{Libere une fenetre}
Paramètres
screen : Surface {La fenetre}
Fonction createSurface(w, h) retourne Surface
{Cree une surface}
Paramètres
w : Entier {La largeur}
h : Entier {La hauteur}
Procedure freeSurface(surface)
{Libere une surface}
Paramètres
surface : Surface {La surface}
Fonction checkSurface(surface) retourne Booleen
{Verifie si une surface/fenetre a bien été chargée}
Paramètres
surface : Surface {La surface à vérifier}
Procedure blitSurface(dst, x, y, src, xx, yy, w, h)
{Colle une surface sur une autre}
Paramètres
dst : Surface {La destination}
x, y : Entiers {Endroit où coller}
src : Surface {La surface à coller}
xx, yy, w, h : Entiers {La portion de surface à coller}
Procedure paintSurface(surface, x, y, r, g, b)
{Change la couleur d'un pixel d'une surface}
Paramètres
surface : Surface {La surface}
x, y : Entiers {La position du pixel}
r, g, b : Entiers {La nouvelle couleur}
Procedure display(screen)
{Met à jour la fenetre donnée}
Paramètres
screen : Surface {la fenetre}
Si vous avez besoin de fonctions supplémentaires que vous ne pouvez pas coder, indiquez leurs spécifications avant (par exemple, si vous voulez faire un paint)
Ce qui est bizarre c'est que le format BMP à une composante alpha mais ne gère pas la transparence
Le fait qu'il y ait à la fois une palette et une composante alpha n'est pas si étrange
c'est pas clair les 2 dernières entrées de l'entête (enfin surtout l'avant dernière). En reprenant ton exemple : 00 04 00 08 -- 4 bits/pixel, 8 bits/composante
Pour la composante, comme ça vaut 8, ça veut dire que l'image est en 32 bits (RGBA) ? D'ailleurs, pourquoi 2 octets pour cette valeur, alors que 3 bits suffisent¹ ?
Pour les bits/pixel, ton « 4 » signifie que chaque octet du fichier contient en vérité 2 pixels ?
¹: voire un octet tout simplement, j'ai rarement vu des images avec plus de 8 bits/composante
L'image est effectivement en 32 bits
Je n'ai pas compris la deuxième partie de la question..
En fait c'est 4 bits pour l'indice de la couleur dans la palette.
Effectivement, 3 bits auraient suffit, mais je voulais faire un truc rond (pour l'exemple)
Quelques petites questions, même si je ne pense pas avoir le temps de faire cet exo.
Il est dit :
8 2 Nombre de bits par pixels (indice dans la palette).
10 2 Nombre de bits pour une composante de couleur.
- "indice dans la palette" ? Qu'est ce que ça veut dire ?
- Le nombre de bits pour une composante couleur, je doute fortement que quelqu'un s'amuse à prendre moins de 8 bits, surtout qu'on ne détaille pas comment traiter ce cas.
- Par contre, un palette c'est utile jusqu'à 8BPP, au delà, ça devient inutile/très gros. L'idée serait qu'en 16/24/32 BPP, on stocke directement la couleur. Il faudrait préciser dans l'énoncé comment stocker le 16 bits (RGB en 555, 565...). Et peut-être rajouter un champ pour donner un index ou les composantes de la couleur transparente si nécessaire.
- Pour la palette, il manque une info : le nombre de couleurs présentes. Dans l'exemple, comment savoir qu'il n'y a que 7 couleurs à la lecture ? Ou faut-il stocker le nombre de couleurs max présentes dans la profondeur en cours ? (16c pour 4BPP, 256c pour 8BPP).
- Dans la palette, il y a une composante "opacité", gérer une opacité couleur par couleur me semble délicat (surtout pour faire l'édition de la palette qui va avec). J'en reviens à mon idée première de rajouter un champ pour la couleur de transparence dans le header, si c'est bien le but recherché.
- Dans l'exemple présenté, si je ne m'abuse, le format des datas du mario correspondrait à du 8BPP. En 4BPP, ce serait plutôt : 0x00 0x00 0x01 0x11 0x11 0x00 0x44 0x40, etc... Non ?
(- Faut-il gérer du padding en fin de ligne ? Càd, si dans la ligne décrite ci-dessus, la largeur du dessin était de 17 pixels, faudrait-il commencer la ligne suivant sur un octet ? Et en fin de graph ? Compléter avec des 0 si nécessaire ?).
Voilà, je pense que ça pourrait aider les participants de préciser ces points.
@ minirop :
Oui c'est ça.
le 1111, 2222, 3333, 4444 seraient les positions de la couleur des pixels dans la palette.
@ joe :
Par indice je voulais dire position.
Typiquement, 2 serait la troisième couleur située dans la palette.
Si on met 4 dans le header, on aura jusqu'à 16 couleurs différentes
J'ai effectivement oublié le nombre de couleurs Oo, désolé, je vais rajouter ça (sur 4 octets ?).
Pour le mario, chaque chiffre correspond à 4 bits, donc, oui, si on regroupe les octets, il faut regrouper les chiffres deux par deux.
Pour le padding.
Etant donné que l'on a spécifié les dimensions de l'image, je ne vois pas trop pourquoi remplir le reste de 0..
c'est pas clair les 2 dernières entrées de l'entête (enfin surtout l'avant dernière). En reprenant ton exemple : 00 04 00 08 -- 4 bits/pixel, 8 bits/composante
Pour la composante, comme ça vaut 8, ça veut dire que l'image est en 32 bits (RGBA) ?
Pour les bits/pixel, ton « 4 » signifie que chaque octet du fichier contient en vérité 2 pixels ?
C'est vrai que c'est pas clair du tout. Ces deux entrées font redondance, j'ai l'impression. On a toujours une palette ou pas ? Ces champs de
Citation : minirop
Pour la composante, comme ça vaut 8, ça veut dire que l'image est en 32 bits (RGBA) ? D'ailleurs, pourquoi 2 octets pour cette valeur, alors que 3 bits suffisent¹ ?
¹: voire un octet tout simplement, j'ai rarement vu des images avec plus de 8 bits/composante
Effectivement, mais c'est plus simple et plus pratique d'avoir 2 octets (comme pour le vrai BMP).
Citation : SylafrsOne
Pour le padding.
Etant donné que l'on a spécifié les dimensions de l'image, je ne vois pas trop pourquoi remplir le reste de 0..
C'est ce qu'on fait dans le vrai format BMP. Au cas où, ça ne coûte pas énormément et ça peut se révéler pratique après.
1/ Savoir utiliser les fichiers en mode binaire.
2/ Savoir utiliser les tableaux, et au mieux, les structures.
3/ Savoir utiliser les opérateurs bits à bits est aussi conseillé
Pour les projets, github est assez sympas, il permet de faire la colo syntaxique en ligne, et utilise git, un très bon gestionnaire de version (mateo en a fait un tuto)
Comme ça, on peut passer les archives simplement.
Ou alors on utilise pastebin
Je n'ai pas le temps de lire le code en ce moment, je vais le lire plus tard
Je pense qu'il y a peu de contributeurs car le sujet est.. assez "complexe".
Je veux dire par là qu'on ne sait pas trop comment s'y prendre et qu'on sait pas comme un fichier .bmp est structuré (ou du moins on a que des bases mais qui nous servent pas trop pour commencer).
Ce qu'il faudrait, je pense, c'est prendre une image relativement petite (pourquoi pas un seul pixel ou deux ?), la décomposer en .bmp (ou la structurer), puis de faire notre image avec le format .zBmp
Cela nous donnerait une petite base pour savoir comment écrire une image, en .bmp par exemple.
Si cela est impossible et je le comprendrai (c'est chronophage d'écrire un mini-tutoriel), un petit lien vers un tuto montrant tout ça serait parfait
Je viens de me lancer même si je ne suis pas exactement le sujet : j'ai tout d'abord créé une sorte de mini-librairie (pas encore compilée en tant que telle pour l'instant) dont voici l'architecture :
Comme vous pouvez le voire, j'ai construit le premier module d'export (io) pour la console histoire de vérifier si tout fonctionnait bien. Ça faisait extrêmement longtemps que j'avais fait du C et j'ai néanmoins réussi à me débrouiller avec les malloc et autres après une vite relecture du tutoriel.
Tout est sous la forme de structures relativement simples mais structurées (j'avais pas prévu ce jeu de mot à la base). Cependant je rencontre un problème qui m'a l'air tout simple mais agaçant car je n'arrive pas à le résoudre après relecture du code.
Lorsque je tente de modifier un pixel de mon image et que j'affiche celle-ci dans la console, j'obtient ceci :
Comme vous pouvez le constater, la modification que j'ai effectuée s'est appliquée à toute l'image au lieu d'un seul pixel ; ou peut-être que je me suis trompé dans la boucle d'affichage qui sait.
D'ailleurs, le voici le code de la boucle d'affichage :
#include "console.h"
#include <stdio.h>
void io_console_dump(image_t* image)
{
int x, y;
for (y = 0; y < image->height; y++) {
for (x = 0; x < image->width; x++) {
color_t* pixel = image_pixel(image, x, y);
printf("(%d,%d,%d,%d)", pixel->r, pixel->g, pixel->b, pixel->a);
}
printf("\n");
}
}
Apparemment l'incrémentation fonctionne bien après un test de debug au printf abusif.
Je planche peut-être sur une mauvaise implémentation de ma fonction image_pixel qui permet de récupérer un pointeur sur un pixel ; voici son implémentation :
color_t* image_pixel(image_t* image, unsigned int x, unsigned int y)
{
return &image->palette->colors[image->pixels[x + image->width * y]];
}
Evidemment, afin de mieux cerner mon problème, je vous joins la représentation des deux structures image_t et palette_t :
typedef struct {
color_t* colors;
unsigned int size;
} palette_t;
La structure color_t se résumant à une simple structure avec les 4 attributs RGBA.
Merci d'avance pour votre aide,
MicroJoe.
(En fait je ne vous l'ai pas dit, mais le prétexte de ma participation est de tester mon environnement de travail avant de me lancer dans un projet de domination du monde, muhaha ).
AMHA ça reste faisable par des débutants, c'est juste qu'il faudrait rendre le sujet plus clair comme précisé précédemment. Par exemple, je n'ai toujours pas compris pourquoi tu nous demandais de préciser le nombre d'octets par pixels dans l'entête du format zBMP alors que tu nous imposes ensuite d'utiliser un format RGBA.
Bon après on va pas se mentir non-plus, ça a plutôt l'air d'être un exercice pour des débutants « avancés ».
Je me suis mal expliqué, mais c'est la taille maximale des indices dans la carte
perror(const char * str); will save your life !
[Défis] #12 : zDessin
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Developer / MA Computer Science 👨💻 👨🔬 — bitbucket.org/OPiMedia/workspace/repositories — www.opimedia.be
Developer / MA Computer Science 👨💻 👨🔬 — bitbucket.org/OPiMedia/workspace/repositories — www.opimedia.be