Imagen autoexpuesta [cerrada]

11

Antecedentes

Hay .ZIParchivos autoextraíbles . Por lo general, tienen la extensión .EXE(y al ejecutar el archivo se extraerán), pero al cambiarles el nombre .ZIP, puede abrir el archivo con algún software de extracción ZIP.

(Esto es posible porque los .EXEarchivos requieren un determinado encabezado pero los .ZIParchivos requieren un cierto avance, por lo que es posible crear un archivo que tenga un .EXEencabezado y un .ZIPavance).

Tu tarea:

Cree un programa que cree archivos de imagen "auto-mostrados":

  • El programa tomará alguna imagen de 64x64 (se admitirán al menos 4 colores) como entrada y algún archivo "combinado" como salida
  • El archivo de salida del programa será reconocido como archivo de imagen por los espectadores de imagen comunes
  • Al abrir el archivo de salida con el visor de imágenes, se mostrará la imagen de entrada.
  • El archivo de salida también se reconocerá como archivo ejecutable para cualquier sistema operativo o tipo de computadora

    (Si se produce un archivo para un sistema operativo o computadora poco común, sería bueno que exista un emulador de PC de código abierto. Sin embargo, esto no es necesario).

  • Al ejecutar el archivo de salida, también se mostrará la imagen de entrada.
  • Es probable que sea necesario cambiar el nombre del archivo (por ejemplo, de .PNGa .COM)
  • No es necesario que el programa y su archivo de salida se ejecuten en el mismo sistema operativo; el programa puede ser, por ejemplo, un programa de Windows y archivos de salida que se pueden ejecutar en un Commodore C64.

Criterio ganador

  • El programa que produce el archivo de salida más pequeño gana
  • Si el tamaño del archivo de salida difiere según la imagen de entrada (por ejemplo, porque el programa comprime la imagen), el archivo de salida más grande posible creado por el programa representa una imagen de 64x64 con hasta 4 colores.

Por cierto

Tuve la idea del siguiente rompecabezas de programación al leer esta pregunta en StackOverflow.

Martin Rosenau
fuente
He agregado las etiquetas de condición ganadoras (desafío de código en combinación con metagolf - salida más corta). En cuanto a la imagen de entrada de 64x64, ¿tiene algunas imágenes de ejemplo? Además, ¿la imagen en sí tiene que ser la misma cuando se ve? ¿O pueden diferir la imagen de salida y la imagen de entrada? Para ser más concretos: digamos que agregamos algún tipo de código para la .exeparte del desafío, y cuando lo vemos como un .pnghay píxeles modificados basados ​​en este .execódigo. ¿Está permitido siempre .pngque podamos verlo? ¿La imagen de salida también debe tener al menos 4 colores?
Kevin Cruijssen
2
¿Cómo se define "visor de imágenes común"? Por ejemplo, ¿cuenta un navegador de Internet con "código" HTML?
Jo King
@KevinCruijssen Cuando se interpreta como archivo de imagen, el archivo de salida representará la misma imagen que el archivo de entrada: el mismo ancho y alto en píxeles y cada píxel tendrá el mismo color. Si los formatos de archivo no admiten exactamente la misma paleta de colores, los colores de cada píxel serán lo más cercanos posible. Lo mismo es cierto para el archivo interpretado como archivo ejecutable. Si el archivo de salida representa un programa de "pantalla completa", puede mostrar la imagen en cualquier lugar de la pantalla (centrado, borde superior izquierdo, ...) o estirarlo al tamaño de pantalla completa.
Martin Rosenau
1
@JoKing "Reconocido por los visores de imágenes comunes" significa que la mayoría de las computadoras con software preinstalado (como HTML) pueden leer el formato del archivo o que muchos usuarios descargan una herramienta gratuita para ver el archivo ( como PDF). Yo diría que HTML + JavaScript se puede ver como código, sin embargo, ¡el "visor de imágenes" no debe ejecutar el código! Por lo tanto, se podría decir que un navegador web es un "visor de imágenes", pero en este caso HTML no es "código". O puede decir que HTML + JS es "código", pero en este caso el navegador web no es un "visor de imágenes".
Martin Rosenau
2
Es triste ver una pregunta tan interesante cerrada. Según tengo entendido, cualquier inquietud debe abordarse antes de volver a abrir una pregunta. Lo principal en los comentarios es el término "visor de imágenes comunes", que es lo suficientemente confuso como para ser ambiguo, y la imagen que se muestra en un estado (según la preocupación de @ KevinCruijssen) sin alteraciones por la presencia del código ejecutable es digna de aclaración. . ¿Sería suficiente una edición que aborde esas preocupaciones? (Confieso que no entiendo la ambigüedad de "cuatro colores cuatro colores")
Gastropner

Respuestas:

5

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 JMPinstrucció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:
gastropner
fuente
Agradable. También estaba pensando en el par BMP / MS-DOS COM; Lo hubiera implementado si no hubiera respuestas dentro de una semana. Sin embargo, hubiera necesitado mucho más de 10K: debido a que no asumí que los registros están inicializados en cero, habría colocado una instrucción de salto en el desplazamiento del archivo 2. Y como este campo se interpreta como "tamaño de archivo" en los archivos BMP, Tendría que llenar el archivo BMP con bytes "ficticios" para asegurar que el campo "tamaño de archivo" representa el tamaño de archivo correcto.
Martin Rosenau
En realidad @MartinRosenau que tenía que no asuma algunos de los valores de los registros que normalmente hago (según fysnet.net/yourhelp.htm ), ya que las cabeceras sobreescribirán registros, e incluso el primer byte de la PSP, necessating int 0x20más ret.
Gastropner