Outils pour utilisateurs

Outils du site


issue87:python

1

We’ve been working on a Cross Stitch pattern generator. Last month we did the UI portion, and now it’s time to do the code that does the most of the work. Next month we will start working on the PDF file output portion. We’ll work on the menu items first. The code is shown below. The global ReadyToProcess variable is used to make sure that if the user presses the Process button, the system doesn’t try to process things without anything to process. We use the tkFileDialog askopenfilename built-in dialog routine to get the filename of the original image. We then get the number of colors in the original image as well as the width and height. We save those values and display them in the GUI. We then open the image and create a thumbnail image to display in the left image in the bottom frame. See the text box to the right.

Depuis quelques mois, nous travaillons sur un générateur de motifs de point de croix. Le mois dernier, nous avons mis en place l'interface utilisateur, maintenant il est temps d'écrire le code qui fait le plus gros du travail. Le mois prochain, nous commencerons à travailler sur la partie qui crée le fichier PDF.

Nous allons travailler d'abord sur les éléments de menu. Le code est ci-dessous.

La variable globale PretPourTraitement est utilisée pour s'assurer que si l'utilisateur appuie sur le bouton de traitement, le système ne va pas chercher à traiter des choses s'il n'y a rien à traiter. Nous utilisons la routine de dialogue askopenfilename intégrée à tkFileDialog pour obtenir le nom du fichier qui contient l'image originale. On obtient alors le nombre de couleurs de l'image originale ainsi que la largeur et la hauteur. Nous sauvons ces valeurs et les affichons dans l'interface graphique. Nous ouvrons ensuite l'image et créons une image miniature à afficher dans la partie gauche du cadre inférieur. La boîte de texte est montrée à droite.

2

Next we do the ShowHideGrid function. This simply exchanges two images in the right image label based on the global variable ShowGrid. If False, we change the text on the show/hide button, then set the ShowGrid variable to true and set the image to the one with the grid. Otherwise we change the text on the show/hide button to “Show Grid”, set the ShowGrid variable to False and put up the ungridded image. Code is on the next page, top left. The StitchSizeSelect function is fired whenever the stitch size combobox is changed. We get the value from the combo box and assign it to a local variable. def StitchSizeSelect(self,p): selection = ComboStitch.get()

Ensuite, nous écrivons la fonction AfficherMasquerGrille. Elle échange tout simplement deux images dans le label image de droite en se basant sur la variable globale AfficherGrille. Si elle vaut False, nous changeons le texte du bouton Afficher/Masquer, puis définissons la variable AfficherGrille à True et définissons l'image à celle qui contient la grille. Sinon, nous changeons le texte sur le bouton Afficher/Masquer en « Afficher grille », définissons la variable AfficherGrille à False et mettons en place l'image sans grille. Le code se trouve sur la page suivante, en haut à gauche.

La fonction ChoixTaillePoints est déclenchée à chaque fois que la liste déroulante de taille du point est modifiée. Nous récupérons la valeur de la liste déroulante et l'affectons à une variable locale.

def ChoixTaillePoints(self,p):

selection = ComboTaillePoints.get()

3

The AidaSizeSelect function (top right) is very similar to the StitchSizeSelect function. We set the FabricWidth and FabricHeight globals based on the selection on the combo box. We also default to 30×30 if they select 30. We have a variable called ReadyToProcess (below) just in case the user tries to run the process function before the image is loaded. We pixelate the original file to a 5×5 pixel matrix This allows us to group that 5×5 matrix to a single color. We then reduce the colors, get the width and height of the processed image and set the size so the user can see how big the resulting image will be.

La fonction ChoixTailleAida (en haut à droite) est très similaire à la fonction ChoixTaillePoints. Nous réglons les variables globales LargeurTissu et HauteurTissu en fonction de la sélection dans la liste déroulante. Nous mettons également à 30×30 par défaut si on choisit 30.

Nous avons une variable appelée PretPourTraitement (ci-dessous) juste au cas où l'utilisateur tente d'exécuter la fonction de traitement avant que l'image ne soit chargée.

Nous pixelisons le fichier original à une matrice de pixels 5×5. Cela nous permet de réduire cette matrice 5×5 à une seule couleur. Nous réduisons ensuite les couleurs, récupérons largeur et hauteur de l'image traitée et réglons la taille pour que l'utilisateur puisse voir quelle sera la taille de l'image résultante.

4

# Place image self.im2=Image.open(Reduced) self.im2.thumbnail((400,400)) self.img3 = ImageTk.PhotoImage(self.im2) self.lblImageR['image'] = self.img3 self.ProcessedImage = 'im1.png'

# Placer image

self.im2=Image.open(Reduite)

self.im2.thumbnail((500,500))

self.img3 = ImageTk.PhotoImage(self.im2)

self.lblImageR['image'] = self.img3

self.ImageTraitee = 'im1.png'

5

The above set of code places the processed image into the image that will hold the processed image. The next set of code will create a grid so that the user will have the grid to do the cross stitching. self.MakeLines(Reduced,5) self.MakeLines2('output.png',50) self.im2 = Image.open('output2.png') self.im2.thumbnail((400,400)) self.img3 = ImageTk.PhotoImage(self.im2) self.lblImageR['image'] = self.img3 self.FillScrolledList('output.png') self.GridImage = 'output2.png'

Le code ci-dessus met l'image traitée dans l'image qui contiendra l'image traitée. La suite du code créera une grille afin que l'utilisateur ait la grille pour faire le point de croix.

self.DessinerLignes(Reduite,5)

self.DessinerLignes2('output.png',50)

self.im2 = Image.open('output2.png')

self.im2.thumbnail((500,500))

self.img3 = ImageTk.PhotoImage(self.im2)

self.lblImageR['image'] = self.img3

self.RemplirListeDeroulante('output.png')

self.GrilleImage = 'output2.png'

6

We stub the CreatePDF function until we finish the PDF function next month. def CreatePDF(self): tkMessageBox.showinfo(title=“Create PDF”,message='Sorry, but the Create PDF function is not yet available.') The OriginalInfo() routine gets and sets variables based on the original image format, size and mode. def OriginalInfo(self,file): im = Image.open(file) imFormat = im.format imSize = im.size imMode = im.mode self.size = imSize self.imformat = imFormat self.immode = imMode

Nous bâclons la fonction CreerPDF et nous la finirons le mois prochain.

    def CreerPDF(self):         tkMessageBox.showinfo(title=“Creer PDF”,message='Desole, la fonction CreerPDF est encore inexistante.')

La routine InfoOriginal() récupère et définit des variables en fonction du format de l'image d'origine, sa taille et son mode.

def InfoOriginal(self,fichier):

im = Image.open(fichier)
imFormat = im.format
imTaille = im.size
imMode = im.mode
self.taille = imTaille
self.imformat = imFormat
self.immode = imMode

7

The GetColorCount function uses the .getcolors method to get the number of colors in the image file. We have to use 1600000 as the maxcolors parameter because if the image contains more than 256 colors (or whatever is in the parameter, the method returns ‘None’. This function is similar to the GetColors function except the GetColors works with an already opened image file. If you use GetColorCount, you have to pass an unopened file. def GetColorCount(self,file): im = Image.open(file) numColors = im.getcolors(1600000) self.colors = len(numColors) return self.colors

La fonction RecupererNbCouleurs utilise la méthode .getcolors pour obtenir le nombre de couleurs dans le fichier image. Nous devons utiliser 1600000 comme paramètre maxcolors parce que, si l'image contient plus de 256 couleurs (ou ce que contient le paramètre), la méthode retourne « None ». Cette fonction est similaire à la fonction RecupererCouleurs sauf que RecupererCouleurs travaille avec une image déjà ouverte. Si vous utilisez RecupererNbCouleurs, vous devez passer un fichier non ouvert.

def RecupererNbCouleurs(self,fichier):

      im = Image.open(fichier)
      nbCouleurs = im.getcolors(1600000)
      self.couleurs = len(nbCouleurs)    
      return self.couleurs

8

The next two functions return the height and width of the image file in pixels. The difference between the two is that GetHW returns a string like 1024×768 and GetHW2 returns two integers. def GetHW(self,file): im = Image.open(file) tmp = “{0}x{1}”.format(im.size[0],im.size[1]) return tmp def GetHW2(self,file): im = Image.open(file) return im.size[0],im.size[1]

Les deux fonctions suivantes renvoient la hauteur et la largeur en pixels du fichier image. La différence entre les deux est que RecupererHauteurLargeur renvoie une chaîne comme 1024×768 et RecupererHauteurLargeur2 renvoie deux nombres entiers.

def RecupererHauteurLargeur(self,fichier):

im = Image.open(fichier)
tmp = "{0}x{1}".format(im.size[0],im.size[1])
return tmp

def RecupererHauteurLargeur2(self,fichier):

im = Image.open(fichier)
return im.size[0],im.size[1]

9

GetColors will get the number of colors in the passed image file. We use 1.6 million colors as the parameter, because the image.getcolors() routine defaults to 0 over color count over 256. def GetColors(self,image): numColors = image.getcolors(1600000) colors = len(numColors) The Pixelate function (above) takes two parameters, image filename (im) and the size of pixels you want. The work is done by the image.resize method. I found this routine on the web in a number of places. In this instance we will be passing a pixel size of 5, which works well for Cross Stitch projects. We also tell the method to take the color of the nearest neighbor. This returns a new image, which we save as a file and return the filename.

RecupererCouleurs cherchera le nombre de couleurs dans l'image passée en paramètre. Nous utilisons 1,6 millions de couleurs comme paramètre, car la routine image.getcolors renvoie 0 (par défaut) s'il y a plus que 256 couleurs.

def RecupererCouleurs(self,image):

nbCouleurs = image.getcolors(1600000)
couleurs = len(nbCouleurs)    

La fonction Pixeliser (ci-dessus) prend deux paramètres, le nom du fichier image (im) et la taille des pixels que vous voulez. Le travail est effectué par la méthode image.resize. J'ai trouvé cette routine sur le Web à pas mal d'endroits. Dans cet exemple, nous allons passer une taille de pixel de 5, qui fonctionne bien pour des projets de point de croix. Nous disons aussi à la méthode de prendre la couleur du plus proche voisin. Cela renvoie une nouvelle image, que nous enregistrons dans un fichier et retournons le nom de ce fichier.

10

The ReduceColors routine (below) basically uses the Image.ADAPTIVE pallet so we can get a much smaller number of colors. There are two MakeLines (top right) routines. They create the grid we spoke of earlier. Rgb2Hex() returns a hex value of the RGB value that is passed in. We will use this to try to compare the colors in the database with the colors in the image. def Rgb2Hex(self,rgb): return '#%02x%02x%02x' % rgb The ScrollList (below) on the right side holds the colors that will be used to get the proper floss colors. We simply create labels to hold the colors (visual) and text.

La routine ReduireCouleurs (ci-dessous) utilise essentiellement la palette Image.ADAPTIVE afin d'obtenir un nombre très restreint de couleurs.

Il y a deux routines DessinerLignes (en haut à droite). Elles créent la grille dont nous avons parlé plus tôt.

Rgb2Hex() retourne une valeur hexadécimale de la valeur RVB qui est passée. Nous allons l'utiliser pour essayer de comparer les couleurs dans la base de données avec les couleurs de l'image.

def Rgb2Hex(self,rgb):

return '#%02x%02x%02x' % rgb

La liste déroulante (ci-dessous) sur le côté droit contient les couleurs qui seront utilisées pour obtenir les couleurs appropriées de fils. Nous créons simplement des « labels » pour contenir les couleurs (visuelles) et le texte.

11

This (next page) is the routine that we use to try to find the closest match between the color in the image and the color in the database. There are many different algorithms on the web that you can look at and try to understand the logic behind it. It gets rather complicated. Ok. That’s all for this month. Next time, we will start creating the PDF output file so the cross stitcher has something to work with. As always, the code is available on PasteBin at http://pastebin.com/DmQ1GeUx. We will continue in the next month or so. I’m facing some surgery soon so I’m not sure how soon I will be able to sit for any long periods of time. Until then, enjoy.

Voici la routine (page suivante) que nous utilisons pour essayer de trouver la meilleure correspondance entre la couleur dans l'image et de la couleur dans la base de données. Il existe de nombreux algorithmes différents sur le Web que vous pouvez regarder pour essayer de comprendre leur logique. Cela peut être assez compliqué.

Bon. C'est tout pour ce mois-ci. La prochaine fois, nous allons commencer à créer le fichier de sortie PDF pour que la brodeuse ait un support avec lequel travailler.

Comme toujours, le code est disponible sur Pastebin : http://pastebin.com/d8JUyeKA (http://pastebin.com/DmQ1GeUx pour la version anglaise). Nous continuerons dans les prochains mois. Je dois bientôt me faire opérer et je ne sais pas à partir de quand je pourrai rester assis longtemps. Jusque-là, amusez-vous bien.

cadres orangés

page 12 à droite

  NomFichierOriginal.set(NomFic)
  NombreCouleursOriginal.set(self.RecupererNbCouleurs(NomFic))
  TailleOriginal.set(self.RecupererHauteurLargeur(NomFic))
  imageMaitresse=Image.open(NomFic)
  imageMaitresse.thumbnail((500,500))
  self.img = ImageTk.PhotoImage(imageMaitresse)
  self.lblImageL['image'] = self.img
  PretPourTraitement = True

L'option de menu SauverFichier appellera simplement la routine CreerPDF routine, quand elle sera finie.

  def SauverFichier(self):
      self.CreerPDF()

Nous allons bâcler les routines AfficherAide et AfficherAPropos avec une boite de dialogue indiquant que ces options ne sont pas encore disponibles.

  def AfficherAide(self):
      tkMessageBox.showinfo(title="Aide",message='Desole,

la fonction aide est encore inexistante.')

  def AfficherApropos(self):
      tkMessageBox.showinfo(title="About",message='Desole,

la fonction a propos est encore inexistante.')

Nous avons déjà écrit la routine OuvrirBase une douzaine de fois. vous devez donc savoir ce qu'elle fait.

  def OuvrirBase(self):
      global connexion
      global curseur
      #---------------------------------        
      connexion = apsw.Connection("floss.db3")
      curseur = connexion.cursor()

page 12 en bas

  def RecupererNomFichier(self):
      global PretPourTraitement
      #---------------------------------        
      NomFic = tkFileDialog.askopenfilename(parent=racine,filetypes=self.formatsImages ,title="Choisir le fichier a ouvrir...")

page 13 haut gauche

  def AfficherMasquerGrille(self):
      global AfficherGrille
      #---------------------------------        
      if AfficherGrille == False:
          self.btnAfficherGrille['text'] = 'Masquer grille'
          AfficherGrille = True
          self.im2=Image.open(self.GrilleImage)
          self.im2.thumbnail((400,400))
          self.img3 = ImageTk.PhotoImage(self.im2)
          self.lblImageR['image'] = self.img3            
      else:
          self.btnAfficherGrille['text'] = 'Afficher grille'
          AfficherGrille = False
          self.im2=Image.open(self.ImageTraitee)
          self.im2.thumbnail((400,400))
          self.img3 = ImageTk.PhotoImage(self.im2)
          self.lblImageR['image'] = self.img3

page 13 haut droite

  def ChoixTailleAida(self,p):
      selection = ComboTaille.get()
      if selection != "30":
          pos = selection.find("x")
          largeur = int(selection[:pos])
          hauteur=int(selection[pos+1:])
      else:
          largeur = 30
          hauteur = 30
      LargeurTissu.set(largeur)
      HauteurTissu.set(hauteur)

page 13 bas

  def Traitement(self):
      global PretPourTraitement
      #---------------------------------        
      if PretPourTraitement == False:
          tkMessageBox.showinfo(title="ERREUR...",message='Vous devez charger une image originale.')
      else:
          nouvelleImage = self.Pixeliser(NomFichierOriginal.get(),5)
          Reduite = self.ReduireCouleurs(nouvelleImage)
          L,H = self.RecupererHauteurLargeur2(Reduite)
          tail = "{0}x{1}".format(L/5,H/5)
          TailleTraitee.set(tail)

page 14 haut

  def Pixeliser(self,im,taillePixel):
      image = Image.open(im)
      self.RecupererCouleurs(image)
      image = image.resize((image.size[0]/taillePixel, image.size[1]/taillePixel),Image.NEAREST)
      image = image.resize((image.size[0]*taillePixel, image.size[1]*taillePixel),Image.NEAREST)
      self.RecupererCouleurs(image)
      #image.show()
      image.save('newimage.png')
      return 'newimage.png'

page 14 bas

  def ReduireCouleurs(self,NomImage):
      #Reduire couleurs
      nbCouleurs=MaxCouleurs.get()
      image = Image.open(NomImage)
      output = image.convert('P', palette=Image.ADAPTIVE, colors=nbCouleurs)
      x = output.convert("RGB")
      self.RecupererCouleurs(x)
      nbCouleurs = x.getcolors()
      CouleursTraitees.set(len(nbCouleurs))     
      x.save('im1.png')
      return 'im1.png'

page 15 haut

  def DessinerLignes(self,im,taillePixel):
      global couleurFond1
      #---------------------------------
      image = Image.open(im)
      pixel = image.load()
      for i in range(0,image.size[0],taillePixel):
          for j in range(0,image.size[1],taillePixel):
              for r in range(taillePixel):
                  pixel[i+r,j] = couleurFond1
                  pixel[i,j+r] = couleurFond1
      image.save('output.png')
          
  def DessinerLignes2(self,im,taillePixel):
      global couleurFond1
      #---------------------------------
      image = Image.open(im)
      pixel = image.load()
      for i in range(0,image.size[0],taillePixel):
          for j in range(0,image.size[1],taillePixel):
              for r in range(taillePixel):
                  try:
                      pixel[i+r,j] = couleurFond1
                      pixel[i,j+r] = couleurFond1
                  except:
                      pass
      image.save('output2.png')

page 15 bas

  def RemplirListeDeroulante(self,nomFic):
      im = Image.open(nomFic)
      nbCouleurs = im.getcolors()
      couleurs = len(nbCouleurs)    
      cntr = 1
      for c in nbCouleurs:
          hexcolor = self.rgb2hex(c[1])
          lblCouleur=Label(self.sfFrame,text="                ",bg=hexcolor,relief=GROOVE)
          lblCouleur.grid(row = cntr, column = 0, sticky = 'nsew',padx=10,pady=5) 
          pkID = self.TrouverMeilleureDistance(c[1][0],c[1][1],c[1][2])
          sql = "SELECT * FROM DMC WHERE pkID = {0}".format(pkID)
          rset = curseur.execute(sql)
          for r in rset:
              hexcolor2 = r[6]
              dmcnum = r[1]
              nomCouleur = r[2]
          lblCouleur2=Label(self.sfFrame,text="                ",bg="#" + hexcolor2,relief=GROOVE)
          lblCouleur2.grid(row = cntr,column = 1,sticky = 'w',padx=5,pady=5)
          lblCouleur3=Label(self.sfFrame,text = str(dmcnum) + "-" + nomCouleur,justify=LEFT)
          CouleurDMC.set(dmcnum)
          lblCouleur3.grid(row = cntr, column = 2,sticky = "w",padx=1,pady=5)
          cntr += 1

page 16 haut

  def TrouverMeilleureDistance(self,r1,g1,b1):
      # dist = math.sqrt(((r1-r2)**2) + ((g1-g2)**2) + ((b1-b2)**2))
      sql = "SELECT * FROM DMC"
      rset = curseur.execute(sql)
      BestDist = 10000.0
      for r in rset:
          pkID = r[0]
          r2 = r[3]
          g2 = r[4]
          b2 = r[5]
          dist = math.sqrt(((r1-r2)**2) + ((g1-g2)**2) + ((b1-b2)**2))
          if dist < BestDist:
              BestDist = dist
              BestpkID = pkID
      return BestpkID
issue87/python.txt · Dernière modification : 2015/01/07 15:46 de auntiee