Самопоказывающееся изображение [закрыто]

11

Фон

Есть самораспаковывающиеся .ZIPфайлы. Обычно они имеют расширение .EXE(и, выполнив файл, они будут извлечены), но при переименовании их .ZIPможно открыть файл с помощью некоторого программного обеспечения для извлечения ZIP.

(Это возможно, потому что для .EXEфайлов требуется определенный заголовок, но для .ZIPфайлов требуется определенный трейлер, поэтому можно создать файл, который имеет как .EXEзаголовок, так и .ZIPтрейлер.)

Твое задание:

Создайте программу, которая создает «самоотображаемые» файлы изображений:

  • Программа должна взять некоторое изображение 64x64 (должно поддерживаться не менее 4 цветов) в качестве входных данных и некоторый «комбинированный» файл в качестве выходных
  • Выходной файл программы должен быть распознан как файл изображения обычными программами просмотра изображений.
  • При открытии выходного файла с помощью средства просмотра изображений, входное изображение должно отображаться
  • Выходной файл также должен распознаваться как исполняемый файл для любой операционной системы или типа компьютера.

    (Если создается файл для необычной операционной системы или компьютера, было бы неплохо, если бы существовал эмулятор ПК с открытым исходным кодом. Однако это не требуется.)

  • При выполнении выходного файла, входное изображение также должно отображаться
  • Вероятно, необходимо переименовать файл (например, из .PNGв .COM)
  • Не обязательно, чтобы программа и ее выходной файл работали в одной и той же ОС; программа может, например, быть программой Windows и выходными файлами, которые могут быть выполнены на Commodore C64.

Критерий победы

  • Программа, которая производит наименьший выходной файл, выигрывает
  • Если размер выходного файла различается в зависимости от входного изображения (например, из-за того, что программа сжимает изображение), создается максимально большой выходной файл, созданный программой, представляющий изображение размером 64x64 с количеством цветов до 4

Кстати

При чтении этого вопроса в StackOverflow у меня возникла идея следующей головоломки программирования .

Мартин Розенау
источник
Я добавил выигрышные теги условий (код-вызов в сочетании с метагольфом - самый короткий вывод). Что касается входного изображения 64x64, у вас есть примеры изображений? Кроме того, само изображение должно быть одинаковым при просмотре? Или выходное изображение и входное изображение могут отличаться? Чтобы быть более конкретным: допустим, мы добавили некоторый код для .exeчасти задачи, и при просмотре его в виде .pngизмененных пикселей на основе этого .exeкода. Разрешено ли это до тех пор, пока .pngмы можем его просматривать? У выходного изображения также должно быть как минимум 4 цвета?
Кевин Круйссен
2
Как вы определяете "общий просмотрщик изображений"? Например, считается ли интернет-браузер с HTML-кодом «кодом»?
Джо Кинг
@KevinCruijssen Когда интерпретируется как файл изображения, выходной файл должен представлять то же изображение, что и входной файл: одинаковая ширина и высота в пикселях, и каждый пиксель должен иметь одинаковый цвет. Если форматы файлов не поддерживают точно одинаковую цветовую палитру, цвета каждого пикселя должны быть как можно ближе. То же самое верно для файла, интерпретируемого как исполняемый файл. Если выходной файл представляет собой «полноэкранную» программу, он может отображать изображение в любом месте экрана (по центру, верхний левый край, ...) или растягивать его до полного размера экрана.
Мартин Розенау
1
@JoKing «Распознается обычными программами просмотра изображений» означает, что формат файла может быть прочитан большинством компьютеров с предустановленным программным обеспечением (например, HTML), или что многие пользователи скачивают бесплатный инструмент для просмотра файла ( такие как PDF). Я бы сказал, что HTML + JavaScript можно рассматривать как код, однако «просмотрщик изображений» не должен выполнять код! Таким образом, можно было бы сказать, что веб-браузер является «средством просмотра изображений», но в этом случае HTML не является «кодом». Или вы можете сказать, что HTML + JS - это «код», но в этом случае веб-браузер не является «средством просмотра изображений».
Мартин Розенау
2
Грустно видеть такой интересный вопрос закрытым. Насколько я понимаю, любые вопросы должны быть рассмотрены до повторного открытия вопроса. Главным в комментариях является термин «обычный просмотрщик изображений», который является достаточно туманным, чтобы быть неоднозначным, и изображение, отображаемое в состоянии (в соответствии с заботой @ KevinCruijssen), неизменным из-за присутствия исполняемого кода, заслуживает пояснения. , Будет ли правка, касающаяся этих проблем, достаточной? (Признаюсь, что я не понял двусмысленности «это четыре цвета, четыре цвета».)
gastropner

Ответы:

5

8086 MS-DOS .COM файл / BMP, размер выходного файла = 2192 байта

кодировщик

Кодировщик написан на C. Он принимает два аргумента: входной файл и выходной файл. Входной файл представляет собой изображение RAW RGB размером 64x64 (то есть это просто 4096 триплетов RGB). Количество цветов ограничено 4, поэтому палитра может быть как можно короче. Это очень прямолинейно в своих действиях; он просто строит палитру, упаковывает пары пикселей в байты и склеивает ее вместе с заранее созданными заголовками и программой декодера.

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

Выходной файл

Выходной файл представляет собой файл BMP, который можно переименовать в .COM и запустить в среде DOS. После выполнения он переключится в режим видео 13h и отобразит изображение.

Файл BMP имеет первый заголовок BITMAPFILEHEADER, который содержит среди прочего поле ImageOffset, которое указывает, где в файле начинаются данные изображения. После этого идет BITMAPINFOHEADER с различной информацией о дешифровании / кодировании, за которой следует палитра, если она используется. ImageOffset может иметь значение, которое указывает за пределы конца любых заголовков, что позволяет нам сделать промежуток для декодера, чтобы находиться в нем. Примерно:

BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA

Другая проблема заключается в том, чтобы войти в декодер. С BITMAPFILEHEADER и BITMAPINFOHEADER можно совместить, чтобы убедиться, что они являются допустимым машинным кодом (который не создает невосстановимое состояние), но палитра более хитрая. Конечно, мы могли бы искусственно увеличить палитру и поместить туда машинный код, но вместо этого я решил использовать поля biXPelsPerMeter и biYPelsPerMeter, первые для правильного выравнивания кода, а последние - для перехода в декодер. Эти поля, конечно, будут содержать мусор, но любой просмотрщик изображений, с которым я тестировал, отображает изображение нормально. Однако печать может привести к особым результатам.

Насколько я знаю, он соответствует стандартам.

Можно создать более короткий файл, если JMPинструкция была помещена в одно из зарезервированных полей в BITMAPFILEHEADER. Это позволило бы нам сохранить высоту изображения как -64 вместо 64, что в волшебной стране чудес BMP-файлов означает, что данные изображения хранятся правильно, что, в свою очередь, позволило бы упростить декодер.

дешифратор

Никаких особых хитростей в декодере. Палитра заполняется кодером и отображается здесь с фиктивными значениями. Он может быть немного короче, если он не вернется в DOS после нажатия клавиши, но без этого тестирование было неинтересным. Если вы чувствуете, что должны, вы можете заменить последние три инструкции, jmp $чтобы сохранить несколько байтов. (Не забудьте обновить заголовки файлов, если вы это сделаете!)

BMP хранит палитры как триплеты BGR ( не RGB), дополненные нулями. Это делает настройку VGA-палитры более раздражающей, чем обычно. Тот факт, что BMP хранятся в обратном порядке, только добавляет вкуса (и размера).

Перечислено здесь в стиле 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
источник
Ницца. Я также думал о паре BMP / MS-DOS COM; Я бы реализовал это, если бы не было ответов в течение одной недели. Однако мне потребовалось бы намного больше 10 КБ: поскольку я не предполагал, что регистры инициализируются нулями, я поместил бы инструкцию перехода со смещением файла 2. И поскольку это поле интерпретируется как «размер файла» в файлах BMP, Мне бы пришлось заполнить BMP-файл байтами «фиктивного», чтобы в поле «Размер файла» был указан правильный размер файла.
Мартин Розенау
@MartinRosenau я на самом деле был не предположить , некоторые из значений регистров , которые я обычно делаю (по fysnet.net/yourhelp.htm ), так как заголовки затирать регистры, и даже первый байт PSP, necessating int 0x20более ret.
гастропнер