8086 Fichier .COM MS-DOS / BMP, taille du fichier de sortie = 2192 octets
Encodeur
L'encodeur est écrit en C. Il prend deux arguments: fichier d'entrée et fichier de sortie. Le fichier d'entrée est une image RVB 64x64 RAW (ce qui signifie qu'il s'agit simplement de 4096 triplets RVB). Le nombre de couleurs est limité à 4, afin que la palette puisse être aussi courte que possible. Il est très simple dans ses actions; il construit simplement une palette, regroupe des paires de pixels en octets et les colle avec des en-têtes prédéfinis et le programme de décodeur.
#include <stdio.h>
#include <stdlib.h>
#define MAXPAL 4
#define IMAGESIZE 64 * 64
int main(int argc, char **argv)
{
FILE *fin, *fout;
unsigned char *imgdata = malloc(IMAGESIZE * 3), *outdata = calloc(IMAGESIZE / 2, 1);
unsigned palette[MAXPAL] = {0};
int pal_size = 0;
if (!(fin = fopen(argv[1], "rb")))
{
fprintf(stderr, "Could not open \"%s\".\n", argv[1]);
exit(1);
}
if (!(fout = fopen(argv[2], "wb")))
{
fprintf(stderr, "Could not open \"%s\".\n", argv[2]);
exit(2);
}
fread(imgdata, 1, IMAGESIZE * 3, fin);
for (int i = 0; i < IMAGESIZE; i++)
{
// BMP saves the palette in BGR order
unsigned col = (imgdata[i * 3] << 16) | (imgdata[i * 3 + 1] << 8) | (imgdata[i * 3 + 2]), palindex;
int is_in_pal = 0;
for (int j = 0; j < pal_size; j++)
{
if (palette[j] == col)
{
palindex = j;
is_in_pal = 1;
}
}
if (!is_in_pal)
{
if (pal_size == MAXPAL)
{
fprintf(stderr, "Too many unique colours in input image.\n");
exit(3);
}
palindex = pal_size;
palette[pal_size++] = col;
}
// High nibble is left-most pixel of the pair
outdata[i / 2] |= (palindex << !(i & 1) * 4);
}
char BITMAPFILEHEADER[14] = {
0x42, 0x4D, // "BM" magic marker
0x90, 0x08, 0x00, 0x00, // FileSize
0x00, 0x00, // Reserved1
0x00, 0x00, // Reserved2
0x90, 0x00, 0x00, 0x00 // ImageOffset
};
char BITMAPINFOHEADER[40] = {
0x28, 0x00, 0x00, 0x00, // StructSize
0x40, 0x00, 0x00, 0x00, // ImageWidth
0x40, 0x00, 0x00, 0x00, // ImageHeight
0x01, 0x00, // Planes
0x04, 0x00, // BitsPerPixel
0x00, 0x00, 0x00, 0x00, // CompressionType (0 = none)
0x00, 0x00, 0x00, 0x00, // RawImagDataSize (0 is fine for non-compressed,)
0x00, 0x00, 0x00, 0x90, // HorizontalRes
// db 0, 0, 0
// nop
0xEB, 0x1A, 0x90, 0x90, // VerticalRes
// jmp Decoder
// nop
// nop
0x04, 0x00, 0x00, 0x00, // NumPaletteColours
0x00, 0x00, 0x00, 0x00, // NumImportantColours (0 = all)
};
char DECODER[74] = {
0xB8, 0x13, 0x00, 0xCD, 0x10, 0xBA, 0x00, 0xA0, 0x8E, 0xC2, 0xBA,
0xC8, 0x03, 0x31, 0xC0, 0xEE, 0x42, 0xBE, 0x38, 0x01, 0xB1, 0x04,
0xFD, 0x51, 0xB1, 0x03, 0xAC, 0xD0, 0xE8, 0xD0, 0xE8, 0xEE, 0xE2,
0xF8, 0x83, 0xC6, 0x07, 0x59, 0xE2, 0xEF, 0xFC, 0xB9, 0x00, 0x08,
0xBE, 0x90, 0x01, 0xBF, 0xC0, 0x4E, 0xAC, 0xD4, 0x10, 0x86, 0xC4,
0xAB, 0xF7, 0xC7, 0x3F, 0x00, 0x75, 0x04, 0x81, 0xEF, 0x80, 0x01,
0xE2, 0xEE, 0x31, 0xC0, 0xCD, 0x16, 0xCD, 0x20,
};
fwrite(BITMAPFILEHEADER, 1, 14, fout);
fwrite(BITMAPINFOHEADER, 1, 40, fout);
fwrite(palette, 4, 4, fout);
fwrite(DECODER, 1, 74, fout);
// BMPs are stored upside-down, because why not
for (int i = 64; i--; )
fwrite(outdata + i * 32, 1, 32, fout);
fclose(fin);
fclose(fout);
return 0;
}
Fichier de sortie
Le fichier de sortie est un fichier BMP qui peut être renommé .COM et exécuté dans un environnement DOS. Lors de l'exécution, il passera en mode vidéo 13h et affichera l'image.
Un fichier BMP a un premier en-tête BITMAPFILEHEADER, qui contient entre autres le champ ImageOffset, qui indique où dans le fichier les données d'image commencent. Après cela vient BITMAPINFOHEADER avec diverses informations de décodage / encodage, suivies d'une palette, si une est utilisée. ImageOffset peut avoir une valeur qui pointe au-delà de la fin de tous les en-têtes, ce qui nous permet de faire un écart pour que le décodeur réside. En gros:
BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA
Un autre problème est d'entrer dans le décodeur. BITMAPFILEHEADER et BITMAPINFOHEADER peuvent être bricolés pour s'assurer qu'ils sont du code machine légal (qui ne produit pas un état non récupérable), mais la palette est plus délicate. Nous aurions bien sûr pu allonger artificiellement la palette et y mettre le code machine, mais j'ai choisi d'utiliser à la place les champs biXPelsPerMeter et biYPelsPerMeter, le premier pour aligner correctement le code et le second pour sauter dans le décodeur. Ces champs contiendront bien sûr des ordures, mais tout visualiseur d'images que j'ai testé affiche bien l'image. L'imprimer peut cependant produire des résultats particuliers.
Il est, pour autant que je sache, conforme aux normes.
On pourrait créer un fichier plus court si l' JMP
instruction était placée dans l'un des champs réservés dans BITMAPFILEHEADER. Cela nous permettrait de stocker la hauteur de l'image sous -64 au lieu de 64, ce qui dans le monde magique des fichiers BMP signifie que les données d'image sont stockées dans le bon sens, ce qui permettrait à son tour un décodeur simplifié.
Décodeur
Aucune astuce particulière dans le décodeur. La palette est remplie par l'encodeur et affichée ici avec des valeurs fictives. Il pourrait être légèrement plus court s'il ne revenait pas à DOS lors d'une pression sur une touche, mais ce n'était pas amusant de tester sans cela. Si vous le jugez nécessaire, vous pouvez remplacer les trois dernières instructions par jmp $
pour économiser quelques octets. (N'oubliez pas de mettre à jour les en-têtes de fichier si vous le faites!)
BMP stocke les palettes sous forme de triplets BGR (et non RVB), remplis de zéros. Cela rend la configuration de la palette VGA plus ennuyeuse que d'habitude. Le fait que les BMP soient stockés à l'envers ne fait qu'ajouter à la saveur (et à la taille).
Listé ici dans le style NASM:
Palette:
db 0, 0, 0, 0
db 0, 0, 0, 0
db 0, 0, 0, 0
db 0, 0, 0, 0
Decoder:
; Set screen mode
mov ax, 0x13
int 0x10
mov dx, 0xa000
mov es, dx
; Prepare to set palette
mov dx, 0x3c8
xor ax, ax
out dx, al
inc dx
mov si, Palette + 2
mov cl, 4
std
pal_loop:
push cx
mov cl, 3
pal_inner:
lodsb
shr al, 1
shr al, 1
out dx, al
loop pal_inner
add si, 7
pop cx
loop pal_loop
cld
; Copy image data to video memory
mov cx, 64 * 64 / 2
mov si, ImageData
mov di, 20160
img_loop:
lodsb
aam 16
xchg al, ah
stosw
test di, 63
jnz skip
sub di, 384
skip:
loop img_loop
; Eat a keypress
xor ax, ax
int 0x16
; Return to DOS
int 0x20
ImageData:
.exe
partie du défi, et lorsque vous le voyez comme.png
il y a des pixels modifiés basés sur ce.exe
code. Est-ce autorisé tant qu'il est encore.png
possible de le voir? L'image de sortie doit-elle également avoir au moins 4 couleurs?