8086 MS-DOS .COM archivo / BMP, tamaño del archivo de salida = 2192 bytes
Codificador
El codificador está escrito en C. Toma dos argumentos: archivo de entrada y archivo de salida. El archivo de entrada es una imagen RAW RGB de 64x64 (lo que significa que es simplemente 4096 tripletes RGB). El número de colores está limitado a 4, por lo que la paleta puede ser lo más corta posible. Es muy sencillo en sus acciones; simplemente construye una paleta, empaqueta pares de píxeles en bytes y los pega junto con encabezados prefabricados y el programa decodificador.
#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;
}
Archivo de salida
El archivo de salida es un archivo BMP que se puede renombrar como .COM y ejecutarse en un entorno DOS. Tras la ejecución, cambiará al modo de video 13h y mostrará la imagen.
Un archivo BMP tiene un primer encabezado BITMAPFILEHEADER, que contiene entre otras cosas el campo ImageOffset, que indica en qué parte del archivo comienzan los datos de la imagen. Después de esto viene BITMAPINFOHEADER con diversa información de descodificación / codificación, seguida de una paleta, si se usa una. ImageOffset puede tener un valor que apunta más allá del final de cualquier encabezado, lo que nos permite hacer un espacio para que el decodificador resida. Aproximadamente:
BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA
Otro problema es ingresar al decodificador. BITMAPFILEHEADER y BITMAPINFOHEADER pueden manipularse para asegurarse de que sean códigos de máquina legales (que no producen un estado no recuperable), pero la paleta es más complicada. Por supuesto, podríamos haber hecho la paleta artificialmente más larga y poner el código de la máquina allí, pero opté por usar los campos biXPelsPerMeter y biYPelsPerMeter, el primero para alinear el código correctamente y el segundo para saltar al decodificador. Por supuesto, estos campos tendrán basura, pero cualquier visor de imágenes que haya probado muestra la imagen bien. Sin embargo, imprimirlo puede producir resultados peculiares.
Es, hasta donde yo sé, cumple con los estándares.
Se podría hacer un archivo más corto si la JMP
instrucción se coloca en uno de los campos reservados en BITMAPFILEHEADER. Esto nos permitiría almacenar la altura de la imagen como -64 en lugar de 64, lo que en el país de las maravillas mágicas de los archivos BMP significa que los datos de la imagen se almacenan correctamente, lo que a su vez permitiría un decodificador simplificado.
Descifrador
No hay trucos particulares en el decodificador. La paleta se completa con el codificador y se muestra aquí con valores ficticios. Podría ser un poco más corto si no volviera a DOS al presionar una tecla, pero no fue divertido probar sin eso. Si cree que debe hacerlo, puede reemplazar las últimas tres instrucciones con jmp $
para guardar algunos bytes. (¡No olvides actualizar los encabezados de los archivos si lo haces!)
BMP almacena paletas como tripletas BGR ( no RGB), rellenadas con ceros. Esto hace que configurar la paleta VGA sea más molesto de lo habitual. El hecho de que las BMP se almacenen al revés solo aumenta el sabor (y el tamaño).
Listado aquí en estilo 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
parte del desafío, y cuando lo vemos como un.png
hay píxeles modificados basados en este.exe
código. ¿Está permitido siempre.png
que podamos verlo? ¿La imagen de salida también debe tener al menos 4 colores?