自显示图像[关闭]


11

背景

有自解压.ZIP文件。通常,它们具有扩展名.EXE(并通过执行文件将其提取),但是将其重命名为时.ZIP,您可以使用一些ZIP提取软件来打开文件。

(这是可能的,因为.EXE文件需要特定的标头,而.ZIP文件则需要特定的预告片,因此可以构建同时具有.EXE标头和.ZIP预告片的文件。)

你的任务:

创建一个程序来创建“自显示”图像文件:

  • 程序应以一些64x64图像(至少应支持4种颜色)作为输入,并以一些“组合”文件作为输出
  • 程序的输出文件应由普通图像查看器识别为图像文件
  • 当使用图像查看器打开输出文件时,应显示输入图像
  • 对于任何操作系统或计算机类型,输出文件也应被识别为可执行文件。

    (如果生成了用于不常见的操作系统或计算机的文件,那么如果存在开源PC仿真器,那就很好了。但是,这不是必需的。)

  • 执行输出文件时,还应显示输入图像
  • 很可能需要重命名文件(例如,从.PNG.COM
  • 不需要程序及其输出文件在同一操作系统上运行;例如,该程序可以是Windows程序,并且可以在Commodore C64上执行输出文件。

获奖标准

  • 产生最小输出文件的程序获胜
  • 如果输出文件的大小根据输入图像而有所不同(例如,由于程序压缩了图像),则由程序创建的最大可能的输出文件代表了最多4色的64x64图像计数

顺便说说

在StackOverflow上阅读此问题时,我想到了以下编程难题


我添加了获胜条件标签(将代码挑战与metagolf结合使用-最短输出)。至于输入的64x64图像,您有一些示例图像吗?另外,观看时图像本身是否必须相同?还是输出图像和输入图像可以不同?更具体地说:假设我们为.exe挑战的一部分添加了某种代码,并且当以a的形式查看它时.png,会基于此.exe代码修改像素。只要.png我们仍然可以查看,是否允许?输出图像还必须至少具有4种颜色吗?
凯文·克鲁伊森

2
您如何定义“普通图像查看器”?例如,具有HTML“代码”的Internet浏览器是否计数?
乔金

@KevinCruijssen当解释为图像文件时,输出文件应表示与输入文件相同的图像:以像素为单位的宽度和高度相同,并且每个像素应具有相同的颜色。如果文件格式不支持完全相同的调色板,则每个像素的颜色应尽可能接近。对于解释为可执行文件的文件也是如此。如果输出文件表示“全屏”程序,则它可能会在屏幕上的任何位置(居中,左上角,...)显示图像,或将其拉伸到全屏大小。
Martin Rosenau

1
@JoKing“常见的图像查看器可以识别”表示该文件格式可以由装有预装软件(例如HTML)的大多数计算机读取,也可以由许多用户下载免费的工具来查看文件( (例如PDF)。我要说的是HTML + JavaScript可以被看作是代码,但是,“图像查看器”应该执行的代码!因此可以说网络浏览器是“图像查看器”,但是在这种情况下,HTML不是“代码”。或者您可以说HTML + JS是“代码”,但是在这种情况下,Web浏览器不是“图像查看器”。
Martin Rosenau

2
这是可悲的看到这样一个有趣的问题关闭。据我了解,任何问题都应在重新提出问题之前解决。注释中的主要内容是“通用图像查看器”一词,该词模糊不清,难以理解,并且在不存在可执行代码的情况下,以某种状态(按照@KevinCruijssen的关注)显示图像是值得澄清的。解决这些问题的编辑就足够了吗?(我承认不理解“是四色四色”的歧义。)
gastropner

Answers:


5

8086 MS-DOS .COM文件/ BMP,输出文件大小= 2192字节

编码器

编码器用C编写。它带有两个参数:输入文件和输出文件。输入文件是64x64 RAW RGB图像(意味着它只是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:

真好 我也在考虑BMP / MS-DOS COM对。如果一周之内没有答案,我会实施的。但是,我所需要的远不止10K:因为我不认为寄存器是零初始化的,所以我将在文件偏移量2处放置一条跳转指令。并且由于该字段在BMP文件中被解释为“文件大小”,我必须用“虚拟”字节填充BMP文件,以确保“文件大小”字段代表正确的文件大小。
Martin Rosenau

@MartinRosenau实际上,我不必假定我通常会做的某些寄存器值(根据fysnet.net/yourhelp.htm进行),因为标头会阻塞寄存器,甚至PSP的第一个字节都必须int 0x20结束ret
gastropner
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.