8086 MS-DOS .COM plik / BMP, rozmiar pliku wyjściowego = 2192 bajtów
Enkoder
Koder jest napisany w C. Przybiera dwa argumenty: plik wejściowy i plik wyjściowy. Plik wejściowy to obraz RAW RGB w formacie 64x64 (co oznacza, że jest to po prostu trojaczki 4096 RGB). Liczba kolorów jest ograniczona do 4, dzięki czemu paleta może być jak najkrótsza. Jest to bardzo proste w swoich działaniach; po prostu buduje paletę, upakowuje pary pikseli w bajtach i skleja razem z gotowymi nagłówkami i programem dekodującym.
#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;
}
Plik wyjściowy
Plik wyjściowy to plik BMP, którego nazwę można zmienić .COM i można go uruchomić w środowisku DOS. Po wykonaniu przejdzie w tryb wideo 13h i wyświetli obraz.
Plik BMP ma pierwszy nagłówek BITMAPFILEHEADER, który zawiera między innymi pole ImageOffset, które wskazuje, gdzie w pliku zaczynają się dane obrazu. Następnie pojawia się BITMAPINFOHEADER z różnymi informacjami de / / kodowania, a następnie paletą, jeśli jest używana. ImageOffset może mieć wartość, która wskazuje poza koniec jakichkolwiek nagłówków, co pozwala nam zrobić przerwę dla dekodera. Z grubsza:
BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA
Innym problemem jest wejście do dekodera. BITMAPFILEHEADER i BITMAPINFOHEADER można modyfikować, aby upewnić się, że są one legalnym kodem maszynowym (który nie generuje nieodzyskiwalnego stanu), ale paleta jest trudniejsza. Oczywiście moglibyśmy sztucznie wydłużyć paletę i umieścić tam kod maszynowy, ale zdecydowałem się zamiast tego użyć pól biXPelsPerMeter i biYPelsPerMeter, pierwszego do poprawnego wyrównania kodu, a drugiego do przeskoczenia do dekodera. Te pola będą oczywiście zawierać śmieci, ale każda przeglądarka obrazów, którą przetestowałem, wyświetla obraz w porządku. Jednak drukowanie może dawać dziwne wyniki.
O ile mi wiadomo, jest zgodny ze standardami.
Można by zrobić krótszy plik, gdyby JMP
instrukcja została umieszczona w jednym z zarezerwowanych pól w BITMAPFILEHEADER. Pozwoliłoby nam to zapisać wysokość obrazu jako -64 zamiast 64, co w magicznej krainie czarów plików BMP oznacza, że dane obrazu są przechowywane odpowiednio do góry, co z kolei pozwoliłoby na uproszczony dekoder.
Dekoder
Brak szczególnych sztuczek w dekoderze. Paleta jest wypełniana przez koder i pokazana tutaj z wartościami pozorowanymi. Może być nieco krótszy, jeśli nie wróci do DOS po naciśnięciu klawisza, ale bez niego testowanie nie byłoby zabawne. Jeśli uważasz, że musisz, możesz zastąpić ostatnie trzy instrukcje, jmp $
aby zaoszczędzić kilka bajtów. (Jeśli to zrobisz, nie zapomnij zaktualizować nagłówków plików!)
BMP przechowuje palety jako trojaczki BGR ( nie RGB), wypełnione zerami. To sprawia, że konfigurowanie palety VGA jest bardziej denerwujące niż zwykle. Fakt, że BMP są przechowywane do góry nogami, tylko poprawia smak (i rozmiar).
Wymienione tutaj w stylu 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
części wyzwania, a gdy oglądamy go jako.png
zmodyfikowane piksele oparte na tym.exe
kodzie. Czy jest to dozwolone, o ile nadal.png
możemy to zobaczyć? Czy obraz wyjściowy również musi mieć co najmniej 4 kolory?