Samo-wyświetlający się obraz [zamknięty]

11

tło

Istnieją .ZIPpliki samorozpakowujące się . Zazwyczaj mają rozszerzenie .EXE(i po uruchomieniu pliku zostaną rozpakowane), ale podczas zmiany ich nazwy .ZIPmożna otworzyć plik za pomocą oprogramowania do rozpakowywania ZIP.

(Jest to możliwe, ponieważ .EXEpliki wymagają określonego nagłówka, ale .ZIPpliki wymagają określonego zwiastuna, dlatego możliwe jest zbudowanie pliku zawierającego zarówno .EXEnagłówek, jak i .ZIPzwiastun).

Twoje zadanie:

Utwórz program, który tworzy „samowyświetlające się” pliki graficzne:

  • Program powinien pobrać obraz w formacie 64 x 64 (przynajmniej 4 kolory) jako dane wejściowe, a niektóre pliki „połączone” jako dane wyjściowe
  • Plik wyjściowy programu zostanie rozpoznany przez zwykłe przeglądarki obrazów jako plik obrazu
  • Podczas otwierania pliku wyjściowego za pomocą przeglądarki obrazów należy wyświetlić obraz wejściowy
  • Plik wyjściowy należy również rozpoznać jako plik wykonywalny dla dowolnego systemu operacyjnego lub typu komputera

    (Jeśli tworzony jest plik dla nietypowego systemu operacyjnego lub komputera, dobrze byłoby, gdyby istniał emulator PC typu open source. Jednak nie jest to wymagane).

  • Podczas wykonywania pliku wyjściowego należy również wyświetlić obraz wejściowy
  • Prawdopodobnie konieczna jest zmiana nazwy pliku (na przykład z .PNGna .COM)
  • Nie jest wymagane, aby program i jego plik wyjściowy działały w tym samym systemie operacyjnym; program może na przykład być programem Windows i plikami wyjściowymi, które można wykonać na Commodore C64.

Kryterium wygranej

  • Wygrywa program, który tworzy najmniejszy plik wyjściowy
  • Jeśli rozmiar pliku wyjściowego różni się w zależności od obrazu wejściowego (na przykład dlatego, że program kompresuje obraz), liczy się największy możliwy plik wyjściowy utworzony przez program reprezentujący obraz 64x64 z maksymalnie 4 kolorami

tak poza tym

Podczas czytania tego pytania na StackOverflow wpadłem na pomysł następującej łamigłówki programistycznej .

Martin Rosenau
źródło
Dodałem zwycięskie tagi warunków (wyzwanie kodowe w połączeniu z metagolfem - najkrótszy wynik). Co do wejściowego obrazu 64x64, czy masz jakieś przykładowe obrazy? Czy sam obraz musi być taki sam podczas oglądania? Czy obraz wyjściowy i obraz wejściowy mogą się różnić? Mówiąc ściślej: powiedzmy, że dodajemy jakiś kod dla .execzęści wyzwania, a gdy oglądamy go jako .pngzmodyfikowane piksele oparte na tym .exekodzie. Czy jest to dozwolone, o ile nadal .pngmożemy to zobaczyć? Czy obraz wyjściowy również musi mieć co najmniej 4 kolory?
Kevin Cruijssen
2
Jak zdefiniować „wspólną przeglądarkę obrazów”? Na przykład, czy liczy się przeglądarka internetowa z „kodem” HTML?
Jo King
@KevinCruijssen Po zinterpretowaniu jako plik obrazu, plik wyjściowy powinien reprezentować ten sam obraz co plik wejściowy: ta sama szerokość i wysokość w pikselach, a każdy piksel powinien mieć ten sam kolor. Jeśli formaty plików nie obsługują dokładnie tej samej palety kolorów, kolory każdego piksela powinny być jak najbardziej zbliżone. To samo dotyczy pliku interpretowanego jako plik wykonywalny. Jeśli plik wyjściowy reprezentuje program „pełnoekranowy”, może wyświetlić obraz w dowolnym miejscu na ekranie (wyśrodkowany, lewa górna krawędź, ...) lub rozciągnąć go do pełnego ekranu.
Martin Rosenau,
1
@JoKing „Rozpoznawane przez popularne przeglądarki zdjęć” oznacza, że ​​format pliku może być odczytany przez większość komputerów z fabrycznie zainstalowanym oprogramowaniem (takim jak HTML) lub że wielu użytkowników pobiera bezpłatne narzędzie do przeglądania pliku ( takich jak PDF). Powiedziałbym, że HTML + JavaScript może być postrzegany jako kod, jednak „przeglądarka obrazów” nie powinna wykonywać kodu! Można więc powiedzieć, że przeglądarka internetowa jest „przeglądarką obrazów”, ale w tym przypadku HTML nie jest „kodem”. Lub możesz powiedzieć, że HTML + JS to „kod”, ale w tym przypadku przeglądarka internetowa nie jest „przeglądarką obrazów”.
Martin Rosenau,
2
Smutne jest, że tak interesujące pytanie zostało zamknięte. O ile rozumiem, wszelkie wątpliwości należy rozwiązać przed ponownym otwarciem pytania. Najważniejsze w komentarzach jest termin „wspólna przeglądarka obrazów”, który jest wystarczająco zamglony, aby był niejednoznaczny, a obraz wyświetlany w stanie (zgodnie z obawą @ KevinCruijssen) niezmienionym przez obecność kodu wykonywalnego jest wart wyjaśnienia . Czy edycja odpowiadająca na te obawy byłaby wystarczająca? (Przyznaję, że nie rozumiem dwuznaczności „cztery kolory cztery kolory”).
gastropner

Odpowiedzi:

5

8086 MS-DOS .COM plik / BMP, rozmiar pliku wyjściowego = 2192 bajtów

Enkoder

Koder jest napisany w C. Przybiera dwa argumenty: plik wejściowy i plik wyjściowy. Plik wejściowy to obraz RAW RGB w formacie 64x64 (co oznacza, że ​​jest to po prostu trojaczki 4096 RGB). Liczba kolorów jest ograniczona do 4, dzięki czemu paleta może być jak najkrótsza. Jest to bardzo proste w swoich działaniach; po prostu buduje paletę, upakowuje pary pikseli w bajtach i skleja razem z gotowymi nagłówkami i programem dekodującym.

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

Plik wyjściowy

Plik wyjściowy to plik BMP, którego nazwę można zmienić .COM i można go uruchomić w środowisku DOS. Po wykonaniu przejdzie w tryb wideo 13h i wyświetli obraz.

Plik BMP ma pierwszy nagłówek BITMAPFILEHEADER, który zawiera między innymi pole ImageOffset, które wskazuje, gdzie w pliku zaczynają się dane obrazu. Następnie pojawia się BITMAPINFOHEADER z różnymi informacjami de / / kodowania, a następnie paletą, jeśli jest używana. ImageOffset może mieć wartość, która wskazuje poza koniec jakichkolwiek nagłówków, co pozwala nam zrobić przerwę dla dekodera. Z grubsza:

BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA

Innym problemem jest wejście do dekodera. BITMAPFILEHEADER i BITMAPINFOHEADER można modyfikować, aby upewnić się, że są one legalnym kodem maszynowym (który nie generuje nieodzyskiwalnego stanu), ale paleta jest trudniejsza. Oczywiście moglibyśmy sztucznie wydłużyć paletę i umieścić tam kod maszynowy, ale zdecydowałem się zamiast tego użyć pól biXPelsPerMeter i biYPelsPerMeter, pierwszego do poprawnego wyrównania kodu, a drugiego do przeskoczenia do dekodera. Te pola będą oczywiście zawierać śmieci, ale każda przeglądarka obrazów, którą przetestowałem, wyświetla obraz w porządku. Jednak drukowanie może dawać dziwne wyniki.

O ile mi wiadomo, jest zgodny ze standardami.

Można by zrobić krótszy plik, gdyby JMPinstrukcja została umieszczona w jednym z zarezerwowanych pól w BITMAPFILEHEADER. Pozwoliłoby nam to zapisać wysokość obrazu jako -64 zamiast 64, co w magicznej krainie czarów plików BMP oznacza, że ​​dane obrazu są przechowywane odpowiednio do góry, co z kolei pozwoliłoby na uproszczony dekoder.

Dekoder

Brak szczególnych sztuczek w dekoderze. Paleta jest wypełniana przez koder i pokazana tutaj z wartościami pozorowanymi. Może być nieco krótszy, jeśli nie wróci do DOS po naciśnięciu klawisza, ale bez niego testowanie nie byłoby zabawne. Jeśli uważasz, że musisz, możesz zastąpić ostatnie trzy instrukcje, jmp $aby zaoszczędzić kilka bajtów. (Jeśli to zrobisz, nie zapomnij zaktualizować nagłówków plików!)

BMP przechowuje palety jako trojaczki BGR ( nie RGB), wypełnione zerami. To sprawia, że ​​konfigurowanie palety VGA jest bardziej denerwujące niż zwykle. Fakt, że BMP są przechowywane do góry nogami, tylko poprawia smak (i ​​rozmiar).

Wymienione tutaj w stylu 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
źródło
Miły. Myślałem również o parze BMP / MS-DOS COM; Zrobiłbym to, gdyby w ciągu tygodnia nie było odpowiedzi. Potrzebowałbym jednak znacznie więcej niż 10 KB: Ponieważ nie zakładałem, że rejestry są inicjowane zerem, umieściłbym instrukcję skoku przy przesunięciu pliku 2. A ponieważ to pole jest interpretowane jako „rozmiar pliku” w plikach BMP, Musiałbym wypełnić plik BMP bajtami „obojętnymi”, aby upewnić się, że pole „rozmiar pliku” reprezentuje prawidłowy rozmiar pliku.
Martin Rosenau,
@MartinRosenau I rzeczywiście było nie zakładać niektóre wartości rejestru, które zwykle zrobić (zgodnie fysnet.net/yourhelp.htm ), ponieważ nagłówki sprać rejestrów, a nawet pierwszy bajt PSP, necessating int 0x20powyżej ret.
gastropner