Compression artistique ASCII avec perte

21

Contexte

PICASCII est un outil soigné qui convertit les images en art ASCII.

Il atteint différents degrés de luminosité en utilisant les dix caractères ASCII suivants:

@#+';:,.` 

Nous dirons que ces charxels (éléments de caractère) ont des luminosités de 1 (au signe) à 10 (espace).

Ci-dessous, vous pouvez voir les résultats de la conversion d'un petit code, le drapeau gallois, une fractale en surplomb, une grosse truite et un petit golf, affichés avec la police correcte:

Art ASCII

Vous pouvez voir les images dans ce violon et les télécharger depuis Google Drive .

Tâche

Bien que les résultats finaux de PICASCII soient visuellement agréables, les cinq images combinées pèsent 153 559 octets. Combien ces images pourraient-elles être compressées si nous sommes prêts à sacrifier une partie de leur qualité?

Votre tâche consiste à écrire un programme qui accepte une image d'art ASCII telle que celles ci-dessus et une qualité minimale en entrée et imprime une compression avec perte de l'image - sous la forme d'un programme complet ou d'une fonction renvoyant une seule chaîne - qui satisfait la exigence de qualité.

Cela signifie que vous ne pouvez pas écrire un décompresseur séparé; il doit être intégré à chacune des images compressées.

L'image originale sera constituée de charxels avec des luminosités comprises entre 1 et 10, séparés par des sauts de ligne en lignes de même longueur. L'image compressée doit avoir les mêmes dimensions et utiliser le même jeu de caractères.

Pour une image non compressée composée de n charxels, la qualité d'une version compressée de l'image est définie comme

formule de qualité

c i est la luminosité du i ème charxel de la sortie de l'image compressée et u i la luminosité du i ème charxel de l'image non compressée.

Notation

Votre code sera exécuté avec les cinq images ci-dessus comme paramètres d'entrée et de qualité minimale de 0,50, 0,60, 0,70, 0,80 et 0,90 pour chacune des images.

Votre score est la moyenne géométrique des tailles de toutes les images compressées, c'est-à-dire la vingt-cinquième racine du produit des longueurs des vingt-cinq images compressées.

Le score le plus bas gagne!

Règles supplémentaires

  • Votre code doit fonctionner pour les images arbitraires, pas seulement celles utilisées pour la notation.

    Il est prévu que vous optimisez votre code vers les cas de test, mais un programme qui ne même pas essayer de compresser les images arbitraires n'obtenir un upvote de moi.

  • Votre compresseur peut utiliser des compresseurs de flux d'octets intégrés (par exemple, gzip), mais vous devez les implémenter vous-même pour les images compressées.

    Les Bulit-ins normalement utilisés dans les décompresseurs de flux d'octets (par exemple, la conversion de base, le décodage de longueur) sont autorisés.

  • Le compresseur et les images compressées ne doivent pas nécessairement être dans la même langue.

    Cependant, vous devez choisir une seule langue pour toutes les images compressées.

  • Pour chaque image compressée, les règles de golf de code standard s'appliquent.

Vérification

J'ai créé un script CJam pour vérifier facilement toutes les exigences de qualité et calculer le score d'une soumission.

Vous pouvez télécharger l'interpréteur Java ici ou ici .

e# URLs of the uncompressed images.
e# "%s" will get replaced by 1, 2, 3, 4, 5.

"file:///home/dennis/codegolf/53199/original/image%s.txt"

e# URLs of the compressed images (source code).
e# "%s-%s" will get replaced by "1-50", "1-60", ... "5-90".

"file:///home/dennis/codegolf/53199/code/image%s-%s.php"

e# URLs of the compressed images (output).

"file:///home/dennis/codegolf/53199/output/image%s-%s.txt"

e# Code

:O;:C;:U;5,:)
{
    5,5f+Af*
    {
        C[IQ]e%g,X*:X;
        ISQS
        [U[I]e%O[IQ]e%]
        {g_W=N&{W<}&}%
        _Nf/::,:=
        {
            {N-"@#+';:,.` "f#}%z
            _::m2f#:+\,81d*/mq1m8#
            _"%04.4f"e%S
            @100*iQ<"(too low)"*
        }{
            ;"Dimension mismatch."
        }?
        N]o
    }fQ
}fI
N"SCORE: %04.4f"X1d25/#e%N

Exemple

Bash → PHP, score 30344.0474

cat

Atteint une qualité de 100% pour toutes les entrées.

$ java -jar cjam-0.6.5.jar vrfy.cjam
1 50 1.0000 
1 60 1.0000 
1 70 1.0000 
1 80 1.0000 
1 90 1.0000 
2 50 1.0000 
2 60 1.0000 
2 70 1.0000 
2 80 1.0000 
2 90 1.0000 
3 50 1.0000 
3 60 1.0000 
3 70 1.0000 
3 80 1.0000 
3 90 1.0000 
4 50 1.0000 
4 60 1.0000 
4 70 1.0000 
4 80 1.0000 
4 90 1.0000 
5 50 1.0000 
5 60 1.0000 
5 70 1.0000 
5 80 1.0000 
5 90 1.0000 

SCORE: 30344.0474
Dennis
la source
J'ai du mal à comprendre cette partie: si quelqu'un choisit q = 0,5, chaque caractère du fichier d'entrée doit être remplacé par le caractère avec la moitié de la luminosité en sortie, non? Évidemment, exclure l'espace blanc car cela gâcherait l'image entière.
Nicolás Siplis
1
C'est trop déroutant et échappatoire. Comment arrêtez-vous un entrée mattmahoney.net/dc/barf.html ? Le décompresseur peut-il également lire un fichier autre que l'image compressée? Pouvez-vous fournir un script python ou quelque chose qui vérifie réellement la qualité d'une image et calcule un score afin qu'il ne puisse y avoir aucun problème sur ce front aussi? Etc.
Will
1
@C'est déroutant? Peut être. Mais je ne pense pas que ce soit une échappatoire. Chaque image compressée doit être un programme ou une fonction, donc les blagues amusantes comme BARF sont automatiquement exclues. Je ne connais pas Python, mais je penserai à quelque chose de simple à vérifier.
Dennis
8
"J'ai créé un script CJam pour vérifier facilement toutes les exigences de qualité et calculer le score d'une soumission." Les gens utilisent-ils vraiment cette chose pour faire des scripts normaux? Cher Seigneur...
Fatalize

Réponses:

4

Java → CJam, score ≈4417,89

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.aditsu.cjam.CJam;

public class Compress {
    protected static final char[] DIGITS = "0123456789ABCDEFGHIJK".toCharArray();
    protected static final String CHARS = "@#+';:,.` ";
    protected static final char[] CHR = CHARS.toCharArray();

    private static class Img {
        public final int rows;
        public final int cols;
        public final int[][] a;

        public Img(final int rows, final int cols) {
            this.rows = rows;
            this.cols = cols;
            a = new int[rows][cols];
        }

        public Img(final List<String> l) {
            rows = l.size();
            cols = l.get(0).length();
            a = new int[rows][cols];
            for (int i = 0; i < rows; ++i) {
                for (int j = 0; j < cols; ++j) {
                    a[i][j] = CHARS.indexOf(l.get(i).charAt(j));
                }
            }
        }

        public static Img read(final Reader r) {
            try {
                final BufferedReader br = new BufferedReader(r);
                final List<String> l = new ArrayList<>();
                while (true) {
                    final String s = br.readLine();
                    if (s == null || s.isEmpty()) {
                        break;
                    }
                    l.add(s);
                }
                br.close();
                return new Img(l);
            }
            catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public static Img read(final File f) {
            try {
                return read(new FileReader(f));
            }
            catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public Img scaleDown(final int fr, final int fc) {
            final int r1 = (rows + fr - 1) / fr;
            final int c1 = (cols + fc - 1) / fc;
            final Img x = new Img(r1, c1);
            final int[][] q = new int[r1][c1];
            for (int i = 0; i < rows; ++i) {
                for (int j = 0; j < cols; ++j) {
                    x.a[i / fr][j / fc] += a[i][j];
                    q[i / fr][j / fc]++;
                }
            }
            for (int i = 0; i < r1; ++i) {
                for (int j = 0; j < c1; ++j) {
                    x.a[i][j] /= q[i][j];
                }
            }
            return x;
        }

        public Img scaleUp(final int fr, final int fc) {
            final int r1 = rows * fr;
            final int c1 = cols * fc;
            final Img x = new Img(r1, c1);
            for (int i = 0; i < r1; ++i) {
                for (int j = 0; j < c1; ++j) {
                    x.a[i][j] = a[i / fr][j / fc];
                }
            }
            return x;
        }

        public Img crop(final int r, final int c) {
            if (r == rows && c == cols) {
                return this;
            }
            final Img x = new Img(r, c);
            for (int i = 0; i < r; ++i) {
                for (int j = 0; j < c; ++j) {
                    x.a[i][j] = a[i][j];
                }
            }
            return x;
        }

        public Img rescale(final int fr, final int fc) {
            return scaleDown(fr, fc).scaleUp(fr, fc).crop(rows, cols);
        }

        public double quality(final Img x) {
            if (x.rows != rows || x.cols != cols) {
                throw new IllegalArgumentException();
            }
            double t = 0;
            for (int i = 0; i < rows; ++i) {
                for (int j = 0; j < cols; ++j) {
                    final int y = a[i][j] - x.a[i][j];
                    t += y * y;
                }
            }
            t /= 81 * rows * cols;
            t = 1 - Math.sqrt(t);
            return Math.pow(t, 8);
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            for (int i = 0; i < rows; ++i) {
                for (int j = 0; j < cols; ++j) {
                    sb.append(CHR[a[i][j]]);
                }
                sb.append('\n');
            }
            return sb.toString();
        }

        public Array toArray() {
            final Array x = new Array(rows * cols);
            int k = 0;
            for (int i = 0; i < rows; ++i) {
                for (int j = 0; j < cols; ++j) {
                    x.a[k++] = a[i][j];
                }
            }
            return x;
        }

        public String compress(final double quality) {
            int bi = 1;
            int bj = 1;
            int bs = rows * cols;
            Img bx = this;

            for (int i = 1; i < 3; ++i) {
                for (int j = 1; j < 3; ++j) {
                    Img x = rescale(i, j);
                    if (quality(x) >= quality) {
                        x = scaleDown(i, j);
                        if (x.rows * x.cols < bs) {
                            bi = i;
                            bj = j;
                            bs = x.rows * x.cols;
                            bx = x;
                        }
                    }
                }
            }

            Array a = bx.toArray();
            int bf = 0;
            for (int i = 1; i <= 20; ++i) {
                final int t = a.rle11(i).n;
                if (t < bs) {
                    bs = t;
                    bf = i;
                }
            }

            int b = 10;
            if (bf > 0) {
                b = 11;
                a = a.rle11(bf);
            }

            String s = null;
            for (int i = 92; i < 97; ++i) {
                for (char c = ' '; c < '$'; ++c) {
                    final String t = a.cjamBase(b, i, c);
                    boolean ok = true;
                    for (int j = 0; j < t.length(); ++j) {
                        if (t.charAt(j) > '~') {
                            ok = false;
                            break;
                        }
                    }
                    if (!ok) {
                        continue;
                    }
                    if (s == null || t.length() < s.length()) {
                        s = t;
                    }
                }
            }

            if (bf > 0) {
                s += "{(_A={;()";
                if (bf > 1) {
                    s += DIGITS[bf] + "*";
                }
                s += "\\(a@*}&\\}h]e_";
            }
            if (bi * bj == 1) {
                return s + '"' + CHARS + "\"f=" + cols + "/N*";
            }
            s += bx.cols + "/";
            if (bi > 1) {
                s += bi + "e*";
                if (rows % 2 == 1) {
                    s += "W<";
                }
            }
            if (bj > 1) {
                s += bj + "fe*";
                if (cols % 2 == 1) {
                    s += "Wf<";
                }
            }
            return s + '"' + CHARS + "\"ff=N*";
        }

        public void verify(final String s, final double quality) {
            final String t = CJam.run(s, "");
            final Img x = read(new StringReader(t));
            final double q = quality(x);
            if (q < quality) {
                throw new RuntimeException(q + " < " + quality);
            }
//          System.out.println(q + " >= " + quality);
        }
    }

    private static class Array {
        public final int[] a;
        public final int n;

        public Array(final int n) {
            this.n = n;
            a = new int[n];
        }

        public Array(final int[] a) {
            this.a = a;
            n = a.length;
        }

        public String join() {
            final StringBuilder sb = new StringBuilder();
            for (int x : a) {
                sb.append(x).append(' ');
            }
            sb.setLength(sb.length() - 1);
            return sb.toString();
        }

//      public String cjamStr() {
//          final StringBuilder sb = new StringBuilder("\"");
//          for (int x : a) {
//              sb.append(DIGITS[x]);
//          }
//          sb.append("\":~");
//          return sb.toString();
//      }

        public String cjamBase(final int m, final int b, final char c) {
            final boolean zero = a[0] == 0;
            String s = join();
            if (zero) {
                s = "1 " + s;
            }
            s = CJam.run("q~]" + m + "b" + b + "b'" + c + "f+`", s);
            s += "'" + c + "fm" + b + "b" + DIGITS[m] + "b";
            if (zero) {
                s += "1>";
            }
            return s;
        }

        public Array rle11(final int f) {
            final int[] b = new int[n];
            int m = 0;
            int x = -1;
            int k = 0;
            for (int i = 0; i <= n; ++i) {
                final int t = i == n ? -2 : a[i];
                if (t == x && m < 11 * f) {
                    m++;
                }
                else {
                    if (m >= f && m > 3) {
                        b[k++] = 10;
                        b[k++] = m / f - 1;
                        b[k++] = x;
                        for (int j = 0; j < m % f; ++j) {
                            b[k++] = x;
                        }
                    }
                    else {
                        for (int j = 0; j < m; ++j) {
                            b[k++] = x;
                        }
                    }
                    m = 1;
                    x = t;
                }
            }
            return new Array(Arrays.copyOf(b, k));
        }
    }

    private static void score() {
        double p = 1;
        for (int i = 1; i < 6; ++i) {
            final File f = new File("image" + i + ".txt");
            final Img img = Img.read(f);
            final int n = (int) f.length();
            for (int j = 5; j < 10; ++j) {
                final double q = j / 10.0;
                final String s = img.compress(q);
                System.out.println(f.getName() + ", " + q + ": " + n + " -> " + s.length());
                img.verify(s, q);
                p *= s.length();
            }
        }
        System.out.println(Math.pow(p, 1 / 25.0));
    }

    public static void main(final String... args) {
        if (args.length != 2) {
            score();
            return;
        }
        final String fname = args[0];
        final double quality = Double.parseDouble(args[1]);
        try {
            final Img img = Img.read(new File(fname));
            final String s = img.compress(quality);
            img.verify(s, quality);
            final FileWriter fw = new FileWriter(fname + ".cjam");
            fw.write(s);
            fw.close();
        }
        catch (IOException e) {
            throw new RuntimeException();
        }
    }
}

Nécessite le pot CJam dans le chemin de classe. Si vous lui donnez 2 arguments de ligne de commande (nom de fichier et qualité), il ajoute ".cjam" au nom de fichier et y écrit l'image compressée. Sinon, il calcule son score sur les 5 images de test, qui sont supposées se trouver dans le répertoire courant. Le programme vérifie également automatiquement chaque image compressée. Vous voudrez peut-être revérifier le calcul du score en cas de divergence.

Les techniques utilisées (jusqu'à présent) sont: la réduction de moitié (horizontalement, verticalement ou les deux) si cela ne réduit pas trop la qualité, un RLE codé sur mesure et une conversion de base pour regrouper plus de données dans chaque caractère tout en restant dans le gamme ASCII imprimable.

aditsu
la source
Pourriez-vous me donner un bref aperçu de la façon de procéder? Je l'ai compilé (avec succès, je pense) avec javac -cp cjam-0.6.5.jar Compress.java, mais java -cp cjam-0.6.5.jar Compressdit Error: Could not find or load main class Compresset java Compressne trouve pas la classe CJam.
Dennis
@Dennis Vous devez ajouter le répertoire contenant Compress.class au chemin de classe (-cp). Si c'est dans le répertoire courant, utilisez -cp .:cjam-0.6.5.jar(dans windoze je pense que vous avez besoin d'un point-virgule au lieu d'un deux-points)
aditsu
Cela a fait l'affaire, merci.
Dennis
2

Python 3.5 (principal et sortie) (actuellement non compétitif)

Joyeux anniversaire, défi! Voici votre cadeau: une réponse!

EDIT: sortie convertie en code python, taux de compression amélioré (légèrement) EDIT2: l'a rendu brut quand sizeest 1. Amélioration du score, mais le score doit être calculé à nouveau. EDIT3: @Dennis a souligné que j'avais encore des bugs à corriger, j'ai donc marqué la réponse comme non compétitive

Code:

import sys
LIST = [' ','`','.',',',':',';',"'",'+','#','@']

def charxel_to_brightness(charxel):
    return LIST.index(charxel)

def brightness_to_charxel(bright):
    return LIST[bright]

def image_to_brightness(imagetext):
    return [list(map(charxel_to_brightness,line)) for line in imagetext.split("\n")]

def brightness_to_image(brightarray):
    return '\n'.join([''.join(map(brightness_to_charxel,line)) for line in brightarray])

def split_into_parts(lst,size):
    return [lst[x:x+size] for x in range(0, len(lst), size)]

def gen_updown(startxel,endxel,size):
    return [[int((size-r)*(endxel-startxel)/size+startxel) for c in range(size)] for r in range(size)]

def gen_leftright(startxel,endxel,size):
    return [[int((size-c)*(endxel-startxel)/size+startxel) for c in range(size)] for r in range(size)]

def gen_tlbr(startxel,endxel,size):
    return [[int((2*size-r-c)/2*(endxel-startxel)/size+startxel) for c in range(size)] for r in range(size)]

def gen_bltr(startxel,endxel,size):
    return [[int((size-r+c)/2*(endxel-startxel)/size+startxel) for c in range(size)] for r in range(size)]

def gen_block(code,startxel,endxel,size):
    if code==0:return gen_updown(startxel,endxel,size)
    if code==1:return gen_leftright(startxel,endxel,size)
    if code==2:return gen_bltr(startxel,endxel,size)
    if code==3:return gen_tlbr(startxel,endxel,size)

def vars_to_data(code,startxel,endxel):
    acc=endxel
    acc+=startxel<<4
    acc+=code<<8
    return acc

def data_to_vars(data):
    code=data>>8
    startxel=(data>>4)&15
    endxel=data&15
    return code,startxel,endxel

def split_into_squares(imgarray,size):
    rows = split_into_parts(imgarray,size)
    allsquares = []
    for rowblock in rows:
        splitrows = []
        for row in rowblock:
            row = split_into_parts(row,size)
            splitrows.append(row)
        rowdict = []
        for row in splitrows:
            for x in range(len(row)):
                if len(rowdict)<=x:
                    rowdict.append([])
                rowdict[x].append(row[x])
        allsquares.append(rowdict)
    return allsquares

def calc_quality(imgarray,comparray):
    acc=0
    for row in range(len(imgarray)):
        for col in range(len(imgarray[row])):
            acc+=pow(imgarray[row][col]-comparray[row][col],2)
    return (1-(acc/81.0/sum([len(row) for row in imgarray]))**.5)**8

def fuse_squares(squarray):
    output=[]
    counter=0
    scounter=0
    sqrow=0
    while sqrow<len(squarray):
        if scounter<len(squarray[sqrow][0]):
            output.append([])
            for square in squarray[sqrow]:
                output[counter].extend(square[scounter])
            scounter+=1
            counter+=1
        else:
            scounter=0
            sqrow+=1
    return output

def main_calc(imgarray,threshold):
    imgarray = image_to_brightness(imgarray)
    size = 9
    quality = 0
    compimg=[]
    datarray=[]
    testdata = [vars_to_data(c,s,e) for c in range(4) for s in range(10) for e in range(10)]
    while quality<threshold:
        squares = split_into_squares(imgarray,size)
        compimg = []
        datarray = []
        testblock = [gen_block(c,s,e,size) for c in range(4) for s in range(10) for e in range(10)]
        for row in squares:
            comprow = []
            datrow=[]
            for square in row:
                quality_values = [calc_quality(square,block) for block in testblock]
                best_quality = quality_values.index(max(quality_values))
                comprow.append(testblock[best_quality])
                datrow.append(testdata[best_quality])
            compimg.append(comprow)
            datarray.append(datrow)
        compimg = fuse_squares(compimg)
        quality = calc_quality(imgarray,compimg)
        print("Size:{} Quality:{}".format(size,quality))
        size-=1
    return brightness_to_image(compimg),datarray,size+1

template = '''def s(d,s,e,z):
 x=range(z)
 return d<1 and[[int((z-r)*(e-s)/z+s)for c in x]for r in x]or d==1 and[[int((z-c)*(e-s)/z+s)for c in x]for r in x]or d==2 and[[int((2*z-r-c)/2*(e-s)/z+s)for c in x]for r in x]or d>2 and[[int((z-r+c)/2*(e-s)/z+s)for c in x] for r in x]
i=lambda a:'\\n'.join([''.join(map(lambda r:" `.,:;'+#@"[r],l))for l in a])
def f(a):
 o=[];c=0;s=0;r=0
 while r<len(a):
  if s<len(a[r][0]):
   o.append([])
   for q in a[r]:
    o[c].extend(q[s])
   s+=1;c+=1
  else:
   s=0;r+=1
 return o
t={};z={}
print(i(f([[s(D>>8,(D>>4)&15,D&15,z)for D in R]for R in t])))'''

template_size_1 = '''print("""{}""")'''   

def main(filename,threshold):
    print(filename+" "+str(threshold))
    file = open(filename,'r')
    compimg,datarray,size = main_calc(file.read(),threshold)
    file.close()
    textoutput = open(filename.split(".")[0]+"-"+str(threshold*100)+".txt",'w')
    textoutput.write(compimg)
    textoutput.close()
    compoutput = open(filename.split(".")[0]+"-"+str(threshold*100)+".py",'w')
    datarray = str(datarray).replace(" ","")
    code = ""
    if size==1:
        code = template_size_1.format(compimg)
    else:
        code= template.format(datarray,str(size))
    compoutput.write(code)
    compoutput.close()
    print("done")

if __name__ == "__main__":
    main(sys.argv[1],float(sys.argv[2]))

Cette réponse pourrait utiliser beaucoup d'améliorations, donc je vais probablement y travailler plus au cours du week-end.

Comment cela fonctionne:

  • Divisez l'image en blocs de taille size.
  • Trouver le meilleur bloc correspondant
    • Les blocs peuvent avoir un gradient maintenant!
  • Calculez la qualité (selon la formule) de l'image entière.
  • Si c'est correct, écrivez l'image zippée dans le fichier.
  • Sinon, décrémentez sizeet réessayez.

Cet algorithme fonctionne bien pour une faible qualité (0,5, 0,6) mais ne fonctionne pas trop bien sur les images de meilleure qualité (gonfle en fait). C'est aussi très lent.

Ici, j'ai tous les fichiers générés, vous n'aurez donc pas à les recréer.

Bleu
la source
Enfin, une réponse! C'est techniquement non compétitif cependant, puisque j'ai créé Bubblegum après avoir posté ce défi ... Je vais exécuter le script de notation plus tard et peut-être le porter dans un langage moins ésotérique.
Dennis
@Dennis Ah, il ne devrait pas être trop difficile de porter la sortie vers un script python. Merci pour le heads-up
Blue
Je viens de relire mon défi (après un an, j'étais un peu flou sur les détails), et il est dit que votre compresseur peut utiliser des compresseurs de flux d'octets intégrés (par exemple, gzip), mais vous devez les implémenter vous-même pour la images compressées. Cela signifie que Bubblegum est de toute façon sorti.
Dennis
Je me suis finalement souvenu que j'avais promis de marquer ceci; Désolé pour le retard. Votre code semble avoir une faute de frappe ( compingdevrait être compimg), que j'ai corrigé pour exécuter le programme. Sauf si j'ai fait une erreur lors de l'exécution de votre code, les dimensions de certaines des images générées sont incorrectes (par exemple, image2.txta 33 164 octets, mais 33 image2-50.0.txt329) et d'autres ne génèrent pas le même fichier lors de l'exécution des programmes générés ( image3-50.0.txta une qualité de 0,5110 , mais l'exécution du programme généré donne une qualité de 0,4508 ).
Dennis
Addendum: j'ai téléchargé à image3-50.0.pypartir de votre Dropbox et il correspond au fichier que j'ai généré.
Dennis