8086 MS-DOS .COM файл / BMP, размер выходного файла = 2192 байта
кодировщик
Кодировщик написан на C. Он принимает два аргумента: входной файл и выходной файл. Входной файл представляет собой изображение RAW RGB размером 64x64 (то есть это просто 4096 триплетов RGB). Количество цветов ограничено 4, поэтому палитра может быть как можно короче. Это очень прямолинейно в своих действиях; он просто строит палитру, упаковывает пары пикселей в байты и склеивает ее вместе с заранее созданными заголовками и программой декодера.
#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;
}
Выходной файл
Выходной файл представляет собой файл BMP, который можно переименовать в .COM и запустить в среде DOS. После выполнения он переключится в режим видео 13h и отобразит изображение.
Файл BMP имеет первый заголовок BITMAPFILEHEADER, который содержит среди прочего поле ImageOffset, которое указывает, где в файле начинаются данные изображения. После этого идет BITMAPINFOHEADER с различной информацией о дешифровании / кодировании, за которой следует палитра, если она используется. ImageOffset может иметь значение, которое указывает за пределы конца любых заголовков, что позволяет нам сделать промежуток для декодера, чтобы находиться в нем. Примерно:
BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA
Другая проблема заключается в том, чтобы войти в декодер. С BITMAPFILEHEADER и BITMAPINFOHEADER можно совместить, чтобы убедиться, что они являются допустимым машинным кодом (который не создает невосстановимое состояние), но палитра более хитрая. Конечно, мы могли бы искусственно увеличить палитру и поместить туда машинный код, но вместо этого я решил использовать поля biXPelsPerMeter и biYPelsPerMeter, первые для правильного выравнивания кода, а последние - для перехода в декодер. Эти поля, конечно, будут содержать мусор, но любой просмотрщик изображений, с которым я тестировал, отображает изображение нормально. Однако печать может привести к особым результатам.
Насколько я знаю, он соответствует стандартам.
Можно создать более короткий файл, если JMP
инструкция была помещена в одно из зарезервированных полей в BITMAPFILEHEADER. Это позволило бы нам сохранить высоту изображения как -64 вместо 64, что в волшебной стране чудес BMP-файлов означает, что данные изображения хранятся правильно, что, в свою очередь, позволило бы упростить декодер.
дешифратор
Никаких особых хитростей в декодере. Палитра заполняется кодером и отображается здесь с фиктивными значениями. Он может быть немного короче, если он не вернется в DOS после нажатия клавиши, но без этого тестирование было неинтересным. Если вы чувствуете, что должны, вы можете заменить последние три инструкции, jmp $
чтобы сохранить несколько байтов. (Не забудьте обновить заголовки файлов, если вы это сделаете!)
BMP хранит палитры как триплеты BGR ( не RGB), дополненные нулями. Это делает настройку VGA-палитры более раздражающей, чем обычно. Тот факт, что BMP хранятся в обратном порядке, только добавляет вкуса (и размера).
Перечислено здесь в стиле 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
части задачи, и при просмотре его в виде.png
измененных пикселей на основе этого.exe
кода. Разрешено ли это до тех пор, пока.png
мы можем его просматривать? У выходного изображения также должно быть как минимум 4 цвета?