Image auto-affichée [fermée]

11

Contexte

Il existe des .ZIPfichiers auto-extractibles . En règle générale, ils ont l'extension .EXE(et en exécutant le fichier, ils seront extraits), mais lorsque vous les renommez .ZIP, vous pouvez ouvrir le fichier avec un logiciel d'extraction ZIP.

(Cela est possible car les .EXEfichiers nécessitent un certain en-tête mais les .ZIPfichiers nécessitent une certaine bande-annonce, il est donc possible de créer un fichier qui possède à la fois un en- .EXEtête et une .ZIPbande - annonce.)

Ta tâche:

Créez un programme qui crée des fichiers image "auto-affichés":

  • Le programme doit prendre une image 64x64 (au moins 4 couleurs doivent être prises en charge) en entrée et un fichier "combiné" en sortie
  • Le fichier de sortie du programme doit être reconnu comme fichier image par les visionneuses d'images courantes
  • Lors de l'ouverture du fichier de sortie avec la visionneuse d'images, l'image d'entrée doit être affichée
  • Le fichier de sortie doit également être reconnu comme fichier exécutable pour tout système d'exploitation ou type d'ordinateur

    (Si un fichier pour un système d'exploitation ou un ordinateur inhabituel est produit, ce serait bien si un émulateur de PC open source existe. Cependant, ce n'est pas nécessaire.)

  • Lors de l'exécution du fichier de sortie, l'image d'entrée doit également être affichée
  • Il est probable que renommer le fichier (par exemple de .PNGà .COM) est nécessaire
  • Il n'est pas nécessaire que le programme et son fichier de sortie s'exécutent sur le même système d'exploitation; le programme peut par exemple être un programme Windows et des fichiers de sortie pouvant être exécutés sur un Commodore C64.

Critère gagnant

  • Le programme qui produit le plus petit fichier de sortie gagne
  • Si la taille du fichier de sortie diffère en fonction de l'image d'entrée (par exemple parce que le programme comprime l'image), le plus grand fichier de sortie possible créé par le programme représentant une image 64x64 avec jusqu'à 4 couleurs compte

Au fait

J'ai eu l'idée du puzzle de programmation suivant en lisant cette question sur StackOverflow.

Martin Rosenau
la source
J'ai ajouté les balises de condition gagnantes (défi de code en combinaison avec metagolf - sortie la plus courte). Quant à l'image d'entrée 64x64, avez-vous des exemples d'images? De plus, l'image elle-même doit-elle être la même lorsqu'elle est vue? Ou l'image de sortie et l'image d'entrée peuvent-elles différer? Pour être plus concret: disons que nous ajoutons une sorte de code pour la .exepartie du défi, et lorsque vous le voyez comme .pngil y a des pixels modifiés basés sur ce .execode. Est-ce autorisé tant qu'il est encore .pngpossible de le voir? L'image de sortie doit-elle également avoir au moins 4 couleurs?
Kevin Cruijssen
2
Comment définissez-vous "visionneuse d'images commune"? Par exemple, un navigateur Internet avec un "code" HTML compte-t-il?
Jo King
@KevinCruijssen Lorsqu'il est interprété comme fichier image, le fichier de sortie doit représenter la même image que le fichier d'entrée: même largeur et hauteur en pixels et chaque pixel doit avoir la même couleur. Si les formats de fichier ne prennent pas en charge exactement la même palette de couleurs, les couleurs de chaque pixel doivent être aussi proches que possible. Il en va de même pour le fichier interprété comme fichier exécutable. Si le fichier de sortie représente un programme "plein écran", il peut afficher l'image n'importe où sur l'écran (centré, bord supérieur gauche, ...) ou l'étirer à la taille plein écran.
Martin Rosenau
1
@JoKing "Reconnu par les visionneuses d'images courantes" signifie que le format de fichier peut être lu par la plupart des ordinateurs avec des logiciels préinstallés (tels que HTML) ou que de nombreux utilisateurs téléchargent un outil gratuit pour afficher le fichier ( tels que PDF). Je dirais que HTML + JavaScript peut être considéré comme du code, cependant, le "visualiseur d'images" ne doit pas exécuter le code! Il serait donc permis de dire qu'un navigateur Web est un "visualiseur d'images", mais dans ce cas, HTML n'est pas un "code". Ou vous pouvez dire que HTML + JS est un "code", mais dans ce cas, le navigateur Web n'est pas un "visualiseur d'images".
Martin Rosenau
2
Il est triste de voir une question aussi intéressante close. Autant que je sache, toute préoccupation doit être traitée avant de rouvrir une question. Les principaux éléments dans les commentaires sont le terme "visionneuse d'image commune", qui est suffisamment flou pour être ambigu, et l'image affichée dans un état (selon @ KevinCruijssen) inchangé par la présence du code exécutable mérite d'être clarifiée. . Un montage répondant à ces préoccupations serait-il suffisant? (J'avoue ne pas comprendre l'ambiguïté "est quatre couleurs quatre couleurs".)
gastropner

Réponses:

5

8086 Fichier .COM MS-DOS / BMP, taille du fichier de sortie = 2192 octets

Encodeur

L'encodeur est écrit en C. Il prend deux arguments: fichier d'entrée et fichier de sortie. Le fichier d'entrée est une image RVB 64x64 RAW (ce qui signifie qu'il s'agit simplement de 4096 triplets RVB). Le nombre de couleurs est limité à 4, afin que la palette puisse être aussi courte que possible. Il est très simple dans ses actions; il construit simplement une palette, regroupe des paires de pixels en octets et les colle avec des en-têtes prédéfinis et le programme de décodeur.

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

Fichier de sortie

Le fichier de sortie est un fichier BMP qui peut être renommé .COM et exécuté dans un environnement DOS. Lors de l'exécution, il passera en mode vidéo 13h et affichera l'image.

Un fichier BMP a un premier en-tête BITMAPFILEHEADER, qui contient entre autres le champ ImageOffset, qui indique où dans le fichier les données d'image commencent. Après cela vient BITMAPINFOHEADER avec diverses informations de décodage / encodage, suivies d'une palette, si une est utilisée. ImageOffset peut avoir une valeur qui pointe au-delà de la fin de tous les en-têtes, ce qui nous permet de faire un écart pour que le décodeur réside. En gros:

BITMAPFILEHEADER
BITMAPINFOHEADER
PALETTE
<gap>
IMAGE DATA

Un autre problème est d'entrer dans le décodeur. BITMAPFILEHEADER et BITMAPINFOHEADER peuvent être bricolés pour s'assurer qu'ils sont du code machine légal (qui ne produit pas un état non récupérable), mais la palette est plus délicate. Nous aurions bien sûr pu allonger artificiellement la palette et y mettre le code machine, mais j'ai choisi d'utiliser à la place les champs biXPelsPerMeter et biYPelsPerMeter, le premier pour aligner correctement le code et le second pour sauter dans le décodeur. Ces champs contiendront bien sûr des ordures, mais tout visualiseur d'images que j'ai testé affiche bien l'image. L'imprimer peut cependant produire des résultats particuliers.

Il est, pour autant que je sache, conforme aux normes.

On pourrait créer un fichier plus court si l' JMPinstruction était placée dans l'un des champs réservés dans BITMAPFILEHEADER. Cela nous permettrait de stocker la hauteur de l'image sous -64 au lieu de 64, ce qui dans le monde magique des fichiers BMP signifie que les données d'image sont stockées dans le bon sens, ce qui permettrait à son tour un décodeur simplifié.

Décodeur

Aucune astuce particulière dans le décodeur. La palette est remplie par l'encodeur et affichée ici avec des valeurs fictives. Il pourrait être légèrement plus court s'il ne revenait pas à DOS lors d'une pression sur une touche, mais ce n'était pas amusant de tester sans cela. Si vous le jugez nécessaire, vous pouvez remplacer les trois dernières instructions par jmp $pour économiser quelques octets. (N'oubliez pas de mettre à jour les en-têtes de fichier si vous le faites!)

BMP stocke les palettes sous forme de triplets BGR (et non RVB), remplis de zéros. Cela rend la configuration de la palette VGA plus ennuyeuse que d'habitude. Le fait que les BMP soient stockés à l'envers ne fait qu'ajouter à la saveur (et à la taille).

Listé ici dans le style 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
la source
Agréable. Je pensais également à la paire BMP / MS-DOS COM; Je l'aurais implémenté s'il n'y avait pas de réponses dans la semaine. Cependant, j'aurais eu besoin de beaucoup plus de 10 Ko: comme je ne pensais pas que les registres étaient initialisés à zéro, j'aurais placé une instruction de saut à l'offset de fichier 2. Et parce que ce champ est interprété comme "taille de fichier" dans les fichiers BMP, Je devrais remplir le fichier BMP avec des octets "factices" pour m'assurer que le champ "taille du fichier" représente la taille de fichier correcte.
Martin Rosenau
@MartinRosenau En fait , je devais ne pas assumer certaines des valeurs de registre que je fais habituellement (comme par fysnet.net/yourhelp.htm ), étant donné que les en- têtes CLOBBER registres, et même le premier octet de la PSP, necessating int 0x20plus ret.
gastropner