Selbstanzeigendes Bild [geschlossen]

11

Hintergrund

Es gibt selbstextrahierende .ZIPDateien. Normalerweise haben sie die Erweiterung .EXE(und durch Ausführen der Datei werden sie extrahiert), aber wenn Sie sie umbenennen .ZIP, können Sie die Datei mit einer ZIP-Extraktionssoftware öffnen.

(Dies ist möglich, weil für .EXEDateien ein bestimmter Header .ZIPerforderlich ist, für Dateien jedoch ein bestimmter Trailer erforderlich ist, sodass eine Datei erstellt werden kann, die sowohl einen .EXEHeader als auch einen .ZIPTrailer enthält.)

Deine Aufgabe:

Erstellen Sie ein Programm, das "selbstanzeigende" Bilddateien erstellt:

  • Das Programm muss ein 64x64-Bild (mindestens 4 Farben werden unterstützt) als Eingabe und eine "kombinierte" Datei als Ausgabe verwenden
  • Die Ausgabedatei des Programms wird von herkömmlichen Bildbetrachtern als Bilddatei erkannt
  • Beim Öffnen der Ausgabedatei mit dem Bildbetrachter soll das Eingabebild angezeigt werden
  • Die Ausgabedatei muss auch als ausführbare Datei für jedes Betriebssystem oder jeden Computertyp erkannt werden

    (Wenn eine Datei für ein ungewöhnliches Betriebssystem oder einen ungewöhnlichen Computer erstellt wird, wäre es schön, wenn ein Open-Source-PC-Emulator vorhanden wäre. Dies ist jedoch nicht erforderlich.)

  • Bei der Ausführung der Ausgabedatei soll auch das Eingabebild angezeigt werden
  • Es ist wahrscheinlich, dass die Datei umbenannt werden muss (z. B. von .PNGnach .COM)
  • Es ist nicht erforderlich, dass das Programm und seine Ausgabedatei auf demselben Betriebssystem ausgeführt werden. Das Programm kann beispielsweise ein Windows-Programm und Ausgabedateien sein, die auf einem Commodore C64 ausgeführt werden können.

Gewinnkriterium

  • Das Programm, das die kleinste Ausgabedatei erzeugt, gewinnt
  • Wenn sich die Größe der Ausgabedatei je nach Eingabebild unterscheidet (z. B. weil das Programm das Bild komprimiert), zählt die vom Programm erstellte größtmögliche Ausgabedatei, die ein 64 x 64-Bild mit bis zu 4 Farben darstellt

Apropos

Ich hatte die Idee für das folgende Programmierpuzzle, als ich diese Frage auf StackOverflow las .

Martin Rosenau
quelle
Ich habe die Gewinner-Bedingungs-Tags hinzugefügt (Code-Challenge in Kombination mit Metagolf - kürzeste Ausgabe). Haben Sie für das 64x64-Eingabebild einige Beispielbilder? Muss das Bild selbst beim Betrachten dasselbe sein? Oder können sich das Ausgabebild und das Eingabebild unterscheiden? Um es konkreter zu machen: Nehmen wir an, wir fügen für den .exeTeil der Herausforderung eine Art Code hinzu , und wenn wir ihn als betrachten, .pnggibt es modifizierte Pixel, die auf diesem .exeCode basieren . Ist das erlaubt, solange .pngwir es noch sehen können? Muss das Ausgabebild auch mindestens 4 Farben haben?
Kevin Cruijssen
2
Wie definieren Sie "Common Image Viewer"? Zählt beispielsweise ein Internetbrowser mit HTML-Code?
Jo King
@ KevinCruijssen Bei der Interpretation als Bilddatei muss die Ausgabedatei dasselbe Bild wie die Eingabedatei darstellen: Gleiche Breite und Höhe in Pixel und jedes Pixel muss dieselbe Farbe haben. Wenn die Dateiformate nicht genau dieselbe Farbpalette unterstützen, müssen die Farben jedes Pixels so nah wie möglich sein. Gleiches gilt für die als ausführbare Datei interpretierte Datei. Wenn die Ausgabedatei ein "Vollbild" -Programm darstellt, wird das Bild möglicherweise an einer beliebigen Stelle auf dem Bildschirm angezeigt (zentriert, oberer linker Rand, ...) oder auf Vollbildgröße gestreckt.
Martin Rosenau
1
@JoKing "Von gängigen Bildbetrachtern erkannt" bedeutet, dass das Dateiformat entweder von den meisten Computern mit vorinstallierter Software (z. B. HTML) gelesen werden kann oder dass viele Benutzer ein kostenloses Tool zum Anzeigen der Datei herunterladen ( wie PDF). Ich würde sagen, dass HTML + JavaScript als Code angesehen werden kann, der "Bildbetrachter" sollte den Code jedoch nicht ausführen! Man kann also sagen, dass ein Webbrowser ein "Bildbetrachter" ist, aber in diesem Fall ist HTML kein "Code". Oder Sie können sagen, dass HTML + JS "Code" ist, aber in diesem Fall ist der Webbrowser kein "Bildbetrachter".
Martin Rosenau
2
Es ist traurig, eine so interessante Frage geschlossen zu sehen. Soweit ich weiß, sollten alle Bedenken ausgeräumt werden, bevor eine Frage erneut geöffnet wird. Das Wichtigste in den Kommentaren ist der Begriff "Common Image Viewer", der so trüb ist, dass er nicht eindeutig ist, und das Bild, das in einem Zustand angezeigt wird (gemäß @ KevinCruijssens Anliegen), der durch das Vorhandensein des ausführbaren Codes unverändert bleibt, sollte geklärt werden . Wäre eine Bearbeitung dieser Bedenken ausreichend? (Ich gebe zu, die Mehrdeutigkeit "ist vier Farben vier Farben" nicht zu verstehen.)
Gastropner

Antworten:

5

8086 MS-DOS .COM-Datei / BMP, Ausgabedateigröße = 2192 Byte

Encoder

Der Encoder ist in C geschrieben. Er benötigt zwei Argumente: Eingabedatei und Ausgabedatei. Die Eingabedatei ist ein 64 x 64 RAW-RGB-Bild (dh es handelt sich einfach um 4096 RGB-Triplets). Die Anzahl der Farben ist auf 4 begrenzt, damit die Palette so kurz wie möglich ist. Es ist sehr unkompliziert in seinen Handlungen; Es erstellt lediglich eine Palette, packt Pixelpaare in Bytes und klebt sie mit vorgefertigten Headern und dem Decoderprogramm zusammen.

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

Ausgabedatei

Die Ausgabedatei ist eine BMP-Datei, die in .COM umbenannt und in einer DOS-Umgebung ausgeführt werden kann. Nach der Ausführung wechselt es in den Videomodus 13h und zeigt das Bild an.

Eine BMP-Datei hat einen ersten Header BITMAPFILEHEADER, der unter anderem das Feld ImageOffset enthält, das angibt, wo in der Datei die Bilddaten beginnen. Danach folgt BITMAPINFOHEADER mit verschiedenen Ent- / Kodierungsinformationen, gefolgt von einer Palette, falls eine verwendet wird. ImageOffset kann einen Wert haben, der über das Ende von Headern hinaus zeigt, sodass wir eine Lücke für den Decoder schließen können. Grob:

BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA

Ein weiteres Problem ist die Eingabe des Decoders. BITMAPFILEHEADER und BITMAPINFOHEADER können bearbeitet werden, um sicherzustellen, dass es sich um legalen Maschinencode handelt (der keinen nicht wiederherstellbaren Status erzeugt), aber die Palette ist schwieriger. Wir hätten die Palette natürlich künstlich verlängern und den Maschinencode dort platzieren können, aber ich habe mich dafür entschieden, stattdessen die Felder biXPelsPerMeter und biYPelsPerMeter zu verwenden, wobei erstere den Code richtig ausrichten und letztere in den Decoder springen. Diese Felder enthalten dann natürlich Müll, aber jeder Bildbetrachter, mit dem ich getestet habe, zeigt das Bild einwandfrei an. Das Drucken kann jedoch zu besonderen Ergebnissen führen.

Soweit ich weiß, ist es standardkonform.

Man könnte eine kürzere Datei erstellen, wenn die JMPAnweisung in eines der reservierten Felder in BITMAPFILEHEADER gestellt würde. Dies würde es uns ermöglichen, die Bildhöhe als -64 anstelle von 64 zu speichern, was im magischen Wunderland der BMP-Dateien bedeutet, dass die Bilddaten richtig hoch gespeichert werden, was wiederum einen vereinfachten Decoder ermöglichen würde.

Decoder

Keine besonderen Tricks im Decoder. Die Palette wird vom Encoder ausgefüllt und hier mit Dummy-Werten angezeigt. Es könnte etwas kürzer sein, wenn es bei einem Tastendruck nicht zu DOS zurückkehrt, aber es hat keinen Spaß gemacht, ohne das zu testen. Wenn Sie dies für erforderlich halten, können Sie die letzten drei Anweisungen durch ersetzen jmp $, um einige Bytes zu sparen. (Vergessen Sie nicht, die Datei-Header zu aktualisieren, wenn Sie dies tun!)

BMP speichert Paletten als BGR- Triplets ( nicht RGB-Triplets), die mit Nullen aufgefüllt sind. Dies macht das Einrichten der VGA-Palette ärgerlicher als gewöhnlich. Die Tatsache, dass BMPs verkehrt herum gelagert werden, trägt nur zum Geschmack (und zur Größe) bei.

Hier im NASM-Stil aufgeführt:

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
quelle
Nett. Ich dachte auch an das Paar BMP / MS-DOS COM; Ich hätte es umgesetzt, wenn es innerhalb einer Woche keine Antworten gegeben hätte. Ich hätte jedoch viel mehr als 10 KB benötigt: Da ich nicht davon ausgegangen bin, dass die Register mit Null initialisiert sind, hätte ich eine Sprunganweisung bei Dateiversatz 2 platziert. Und weil dieses Feld in BMP-Dateien als "Dateigröße" interpretiert wird, Ich müsste die BMP-Datei mit "Dummy" -Bytes füllen, um sicherzustellen, dass das Feld "Dateigröße" die richtige Dateigröße darstellt.
Martin Rosenau
@MartinRosenau Ich musste tatsächlich einige der Registerwerte, die ich normalerweise mache (gemäß fysnet.net/yourhelp.htm ), nicht annehmen , da die Header Clobber-Register und sogar das erste Byte der PSP über erforderlich sind . int 0x20ret
Gastropner