Imagem com exibição automática [fechada]


11

fundo

Existem .ZIParquivos de extração automática . Normalmente eles têm a extensão .EXE(e, executando o arquivo, eles serão extraídos), mas ao renomeá-los .ZIP, você pode abrir o arquivo com algum software de extração ZIP.

(Isso é possível porque os .EXEarquivos exigem um determinado cabeçalho, mas os .ZIParquivos exigem um determinado trailer, portanto, é possível criar um arquivo que tenha um .EXEcabeçalho e um .ZIPtrailer.)

Sua tarefa:

Crie um programa que crie arquivos de imagem "auto-exibidos":

  • O programa terá uma imagem de 64x64 (pelo menos 4 cores serão suportadas) como entrada e algum arquivo "combinado" como saída
  • O arquivo de saída do programa deve ser reconhecido como arquivo de imagem pelos visualizadores de imagens comuns
  • Ao abrir o arquivo de saída com o visualizador de imagens, a imagem de entrada será exibida
  • O arquivo de saída também deve ser reconhecido como arquivo executável para qualquer sistema operacional ou tipo de computador

    (Se um arquivo para um sistema operacional ou computador incomum for produzido, seria bom que exista um emulador de PC de código aberto. No entanto, isso não é necessário.)

  • Ao executar o arquivo de saída, a imagem de entrada também deve ser exibida
  • É provável que seja necessário renomear o arquivo (por exemplo, de .PNGpara .COM)
  • Não é necessário que o programa e seu arquivo de saída sejam executados no mesmo sistema operacional; o programa pode, por exemplo, ser um programa do Windows e arquivos de saída que podem ser executados em um Commodore C64.

Critério de vitória

  • O programa que produz o menor arquivo de saída ganha
  • Se o tamanho do arquivo de saída diferir dependendo da imagem de entrada (por exemplo, porque o programa compacta a imagem), o maior arquivo de saída possível criado pelo programa representando uma imagem de 64x64 com até 4 cores contadas

A propósito

Tive a ideia do seguinte quebra-cabeça de programação ao ler esta pergunta no StackOverflow.


Eu adicionei as tags de condição vencedoras (desafio de código em combinação com metagolf - saída mais curta). Quanto à imagem de entrada de 64x64, você tem algumas imagens de exemplo? Além disso, a imagem em si precisa ser a mesma quando visualizada? Ou a imagem de saída e a imagem de entrada podem diferir? Para ser mais concreto: digamos que adicionamos algum tipo de código para a .exeparte do desafio e, ao vê-lo como um, .pngexistem pixels modificados com base nesse .execódigo. Isso é permitido desde que ainda .pngpossamos ver? A imagem de saída também precisa ter pelo menos 4 cores?
Kevin Cruijssen

2
Como você define "visualizador de imagens comum"? Por exemplo, um navegador da Internet com "código" HTML conta?
Jo rei

@KevinCruijssen Quando interpretado como arquivo de imagem, o arquivo de saída deve representar a mesma imagem que o arquivo de entrada: Mesma largura e altura em pixels e cada pixel deve ter a mesma cor. Se os formatos de arquivo não suportarem exatamente a mesma paleta de cores, as cores de cada pixel deverão ser o mais próximas possível. O mesmo vale para o arquivo interpretado como arquivo executável. Se o arquivo de saída representar um programa de "tela cheia", ele poderá exibir a imagem em qualquer lugar da tela (centralizada, borda superior esquerda, ...) ou esticá-la para o tamanho de tela inteira.
Martin Rosenau

11
@JoKing "Reconhecido por visualizadores de imagens comuns" significa que o formato do arquivo pode ser lido pela maioria dos computadores com software pré-instalado (como HTML) ou que muitos usuários baixam uma ferramenta gratuita para visualizar o arquivo ( como PDF). Eu diria que HTML + JavaScript pode ser visto como código, no entanto, o "visualizador de imagens" não deve executar o código! Portanto, seria permitido dizer que um navegador da web é um "visualizador de imagens", mas nesse caso o HTML não é "código". Ou você pode dizer que HTML + JS é "código", mas nesse caso o navegador da web não é um "visualizador de imagens".
Martin Rosenau

2
É triste ver uma pergunta tão interessante fechada. Tanto quanto eu entendo, qualquer preocupação deve ser tratada antes de reabrir uma pergunta. O principal nos comentários é o termo "visualizador de imagem comum", que é suficientemente nebuloso para ser ambíguo, e a imagem sendo exibida em um estado (conforme a preocupação de @ KevinCruijssen) inalterada pela presença do código executável é digna de esclarecimento . Uma edição abordando essas preocupações seria suficiente? (Confesso não entender a "é de quatro cores quatro cores" ambigüidade.)
gastropner

Respostas:


5

8086 arquivo .COM do MS-DOS / BMP, tamanho do arquivo de saída = 2192 bytes

Codificador

O codificador está escrito em C. São necessários dois argumentos: arquivo de entrada e arquivo de saída. O arquivo de entrada é uma imagem RGB RAW de 64x64 (o que significa que são simplesmente 4096 trigêmeos RGB). O número de cores é limitado a 4, para que a paleta possa ser a menor possível. É muito direto em suas ações; apenas constrói uma paleta, empacota pares de pixels em bytes e cola-a junto com cabeçalhos pré-fabricados e o 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;
}

Arquivo de saída

O arquivo de saída é um arquivo BMP que pode ser renomeado como .COM e executado em um ambiente DOS. Após a execução, ele muda para o modo de vídeo 13h e exibe a imagem.

Um arquivo BMP possui um primeiro cabeçalho BITMAPFILEHEADER, que contém, entre outras coisas, o campo ImageOffset, que indica onde o arquivo começa os dados da imagem. Depois disso, vem o BITMAPINFOHEADER com várias informações de decodificação / codificação, seguidas por uma paleta, se houver. O ImageOffset pode ter um valor que aponte além do final de qualquer cabeçalho, permitindo abrir um espaço para o decodificador residir. Aproximadamente:

BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA

Outro problema é inserir o decodificador. O BITMAPFILEHEADER e o BITMAPINFOHEADER podem ser modificados para garantir que sejam códigos de máquina legais (que não produzem um estado não recuperável), mas a paleta é mais complicada. É claro que poderíamos ter aumentado artificialmente a paleta e colocado o código da máquina, mas optei por usar os campos biXPelsPerMeter e biYPelsPerMeter, o primeiro para alinhar o código corretamente e o segundo para pular no decodificador. É claro que esses campos terão lixo, mas qualquer visualizador de imagens que eu testei exibe a imagem com precisão. A impressão pode produzir resultados peculiares, no entanto.

É, até onde eu sei, compatível com os padrões.

Pode-se criar um arquivo mais curto se a JMPinstrução for colocada em um dos campos reservados em BITMAPFILEHEADER. Isso nos permitiria armazenar a altura da imagem como -64 em vez de 64, o que no país das maravilhas mágico dos arquivos BMP significa que os dados da imagem são armazenados da maneira correta, o que, por sua vez, permitiria um decodificador simplificado.

Decodificador

Não há truques específicos no decodificador. A paleta é preenchida pelo codificador e mostrada aqui com valores dummy. Poderia ser um pouco mais curto se não retornasse ao DOS com o pressionamento de uma tecla, mas não foi divertido testar sem isso. Se achar necessário, você pode substituir as três últimas instruções por jmp $para salvar alguns bytes. (Não esqueça de atualizar os cabeçalhos de arquivo, se você o fizer!)

O BMP armazena paletas como trigêmeos BGR ( não RGB), preenchidos com zeros. Isso torna a configuração da paleta VGA mais irritante do que o normal. O fato de as BMPs serem armazenadas de cabeça para baixo apenas aumenta o sabor (e o tamanho).

Listados aqui no 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:

Agradável. Eu também estava pensando sobre o par BMP / MS-DOS COM; Eu o teria implementado se não houvesse respostas dentro de uma semana. No entanto, eu precisaria de muito mais do que 10K: como não supunha que os registros fossem inicializados com zero, eu teria colocado uma instrução de salto no deslocamento de arquivo 2. E como esse campo é interpretado como "tamanho do arquivo" nos arquivos BMP, Eu precisaria preencher o arquivo BMP com bytes "fictícios" para garantir que o campo "tamanho do arquivo" represente o tamanho correto do arquivo.
Martin Rosenau

@MartinRosenau Na verdade, eu tive que não assumir alguns dos valores de registro que eu costumo fazer (como fysnet.net/yourhelp.htm ), já que os cabeçalhos de clobber registram e até o primeiro byte do PSP, necessitando de int 0x20mais ret.
Gastropner 6/06/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.