Immagine auto-visualizzata [chiusa]


11

sfondo

Esistono .ZIPfile autoestraenti . In genere hanno l'estensione .EXE(e eseguendo il file verranno estratti) ma quando li rinominate in .ZIP, è possibile aprire il file con un software di estrazione ZIP.

(Ciò è possibile perché i .EXEfile richiedono una determinata intestazione ma i .ZIPfile richiedono un determinato trailer, quindi è possibile creare un file che abbia sia .EXEun'intestazione che un .ZIPtrailer.)

Il tuo compito:

Crea un programma che crea file di immagine "auto-visualizzati":

  • Il programma deve acquisire alcune immagini 64x64 (devono essere supportati almeno 4 colori) come input e alcuni file "combinati" come output
  • Il file di output del programma deve essere riconosciuto come file di immagine dai comuni visualizzatori di immagini
  • Quando si apre il file di output con il visualizzatore di immagini, deve essere visualizzata l'immagine di input
  • Il file di output deve inoltre essere riconosciuto come file eseguibile per qualsiasi sistema operativo o tipo di computer

    (Se viene prodotto un file per un sistema operativo o computer non comune, sarebbe bello se esistesse un emulatore di PC open source. Tuttavia, ciò non è necessario.)

  • Quando si esegue il file di output, deve essere visualizzata anche l'immagine di input
  • È probabile che sia necessario rinominare il file (ad esempio da .PNGa .COM)
  • Non è necessario che il programma e il suo file di output vengano eseguiti sullo stesso sistema operativo; il programma può ad esempio essere un programma Windows e file di output che possono essere eseguiti su un Commodore C64.

Criterio vincente

  • Vince il programma che produce il file di output più piccolo
  • Se la dimensione del file di output differisce a seconda dell'immagine di input (ad esempio perché il programma comprime l'immagine), il file di output più grande possibile creato dal programma che rappresenta un'immagine 64x64 con un massimo di 4 colori conta

A proposito

Ho avuto l'idea per il seguente puzzle di programmazione durante la lettura di questa domanda su StackOverflow.


Ho aggiunto i tag delle condizioni vincenti (code-challenge in combinazione con metagolf - output più breve). Per quanto riguarda l'immagine di input 64x64, hai alcune immagini di esempio? Inoltre, l'immagine stessa deve essere la stessa quando viene visualizzata? O l'immagine di output e l'immagine di input possono differire? Per essere più concreti: diciamo che aggiungiamo un qualche tipo di codice per la .exeparte della sfida, e quando lo visualizziamo come .pngci sono pixel modificati basati su questo .exe-code. È consentito finché è ancora .pngpossibile visualizzarlo? Anche l'immagine in uscita deve avere almeno 4 colori?
Kevin Cruijssen,

2
Come si definisce "visualizzatore di immagini comuni"? Ad esempio, un browser Internet con "codice" HTML conta?
Jo King,

@KevinCruijssen Se interpretato come file di immagine, il file di output deve rappresentare la stessa immagine del file di input: stessa larghezza e altezza in pixel e ogni pixel deve avere lo stesso colore. Se i formati di file non supportano esattamente la stessa tavolozza dei colori, i colori di ciascun pixel devono essere il più vicini possibile. Lo stesso vale per il file interpretato come file eseguibile. Se il file di output rappresenta un programma "a schermo intero", può visualizzare l'immagine in qualsiasi punto dello schermo (centrato, bordo superiore sinistro, ...) o estenderla a schermo intero.
Martin Rosenau,

1
@JoKing "Riconosciuto dai comuni visualizzatori di immagini" significa che il formato del file può essere letto dalla maggior parte dei computer con software preinstallato (come HTML) o che molti utenti scaricano uno strumento gratuito per visualizzare il file ( come PDF). Direi che HTML + JavaScript può essere visto come codice, tuttavia il "visualizzatore di immagini" non dovrebbe eseguire il codice! Quindi sarebbe permesso dire che un browser web è un "visualizzatore di immagini", ma in questo caso HTML non è "codice". Oppure puoi dire che HTML + JS è "codice", ma in questo caso il browser web non è un "visualizzatore di immagini".
Martin Rosenau,

2
È triste vedere una domanda così interessante chiusa. Per quanto ho capito, eventuali dubbi dovrebbero essere affrontati prima di riaprire una domanda. La cosa principale nei commenti è il termine "visualizzatore di immagini comuni", che è sufficientemente sfumato per essere ambiguo, e l'immagine visualizzata in uno stato (secondo la preoccupazione di @ KevinCruijssen) inalterata dalla presenza del codice eseguibile è degna di chiarimento . Una modifica che affronta tali preoccupazioni sarebbe sufficiente? (Confesso di non capire l'ambiguità "è quattro colori e quattro colori").
Gastropner

Risposte:


5

8086 MS-DOS File .COM / BMP, dimensione del file di output = 2192 byte

Codificatore

Il codificatore è scritto in C. Richiede due argomenti: file di input e file di output. Il file di input è un'immagine RAW RGB 64x64 (che significa semplicemente triplette RGB 4096). Il numero di colori è limitato a 4, quindi la tavolozza può essere il più corta possibile. È molto diretto nelle sue azioni; costruisce semplicemente una tavolozza, racchiude coppie di pixel in byte e la incolla insieme alle intestazioni predefinite e al programma di decodifica.

#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;
}

File di uscita

Il file di output è un file BMP che può essere rinominato .COM ed eseguito in un ambiente DOS. Al momento dell'esecuzione, passerà alla modalità video 13h e visualizzerà l'immagine.

Un file BMP ha una prima intestazione BITMAPFILEHEADER, che contiene tra l'altro il campo ImageOffset, che indica dove iniziano i dati dell'immagine nel file. Dopo questo arriva BITMAPINFOHEADER con varie informazioni di de / codifica, seguite da una tavolozza, se usata. ImageOffset può avere un valore che punta oltre la fine di qualsiasi intestazione, permettendoci di creare uno spazio vuoto per il decodificatore. Circa:

BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA

Un altro problema è inserire il decodificatore. BITMAPFILEHEADER e BITMAPINFOHEADER possono essere armeggiati per assicurarsi che siano codici macchina legali (che non producono uno stato non recuperabile), ma la tavolozza è più complicata. Ovviamente avremmo potuto allungare artificialmente la palette e inserire lì il codice macchina, ma ho optato per utilizzare invece i campi biXPelsPerMeter e biYPelsPerMeter, il primo per allineare correttamente il codice e il secondo per saltare nel decodificatore. Questi campi avranno ovviamente dei rifiuti, ma qualsiasi visualizzatore di immagini con cui ho provato visualizza bene l'immagine. Tuttavia, la stampa potrebbe produrre risultati particolari.

Per quanto ne so, è conforme agli standard.

Si potrebbe creare un file più breve se l' JMPistruzione fosse inserita in uno dei campi riservati in BITMAPFILEHEADER. Ciò ci consentirebbe di memorizzare l'altezza dell'immagine come -64 anziché 64, che nel magico paese delle meraviglie dei file BMP significa che i dati dell'immagine vengono archiviati nel modo giusto, il che a sua volta consentirebbe un decodificatore semplificato.

decoder

Nessun trucco particolare nel decodificatore. La tavolozza è popolata dall'encoder e mostrata qui con valori fittizi. Potrebbe essere leggermente più breve se non tornasse in DOS dopo aver premuto un tasto, ma non è stato divertente testarlo. Se lo ritieni necessario, puoi sostituire le ultime tre istruzioni con jmp $per salvare alcuni byte. (Non dimenticare di aggiornare le intestazioni dei file se lo fai!)

BMP memorizza le palette come BGR ( no terzine RGB), riempite con zero. Ciò rende l'impostazione della palette VGA più fastidiosa del solito. Il fatto che i BMP siano archiviati capovolti non fa che aumentare il sapore (e le dimensioni).

Elencati qui in stile 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:

Bello. Stavo anche pensando alla coppia BMP / MS-DOS COM; Lo avrei implementato se non ci fossero risposte entro una settimana. Tuttavia, avrei avuto bisogno di molto più di 10 KB: poiché non supponevo che i registri fossero inizializzati a zero, avrei inserito un'istruzione di salto nell'offset del file 2. E poiché questo campo è interpretato come "dimensione del file" nei file BMP, Dovrei riempire il file BMP con byte "fittizi" per assicurarmi che il campo "dimensione file" rappresenti la dimensione file corretta.
Martin Rosenau,

@MartinRosenau In realtà non ho dovuto assumere alcuni dei valori di registro che di solito faccio (come da fysnet.net/yourhelp.htm ), dal momento che gli header clobber si registrano, e persino il primo byte della PSP, che richiede int 0x20oltre ret.
Gastropner
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.