Code Vonc

ImageVonc

v 1.0

Win

TéléchargerDownload
Faire un donMake a donation
CommentairesComments

Lecteur d'image ergonomique.

Fichier .exe compilé disponible pour Windows.

Interface avec WX, traitement d'image avec PIL.



Fonctionnalités

- Menu avancé : clic droit.
- Supporte les formats : bmp, gif, jpg, png, pcx, pnm, tif, xpm, ico, cur, ani, tga, pix
- Flèches gauche et droite : Image suivante / précédente du dossier.
- Zoom + et -.
- Ajuster la taille : Ajuste la taille des images plus grandes que l'écran.
- Plein écran (bouton du milieu)
- Supprimer : Déplace le fichier dans la corbeille
- Suppression directe : Supprimer le fichier sans préavis.
- Infos : Affiche les informations EXIF du fichier.
- Utiliser EXIF : Si actif, pivote automatiquement les images ayant l'information d'orientation (jpeg).
- Aléatoire : Lit une image aléatoire du dossier (flèches gauche / droite).
- Figer le zoom : Maintient le pourcentage du zoom identique sur toutes les images.
- Exporter : Enregistre l'image en PNG.
- Exporter par lot : Enregistre toutes les images du dossier en PNG.


Codes sourcesSources code

ImageVonc.py

import os
import sys
import wx
import wx.animate
import wx.lib.platebtn as btnplat
import shutil
import random
import PIL
import struct as st
from PIL import Image as PILImage
from PIL import ImageTk
from PIL.ExifTags import TAGS

class DialogueInfos(wx.Dialog) :
	FENETRE = None
	exif = None
	
	def dicoTexte(val) :
		try : return unicode(val)
		except UnicodeDecodeError : print val
		return ""
	
	def dicoCommentaire(val) :
		return str(len(val)) + u" caractères à décoder."
	
	def dicoDate(val) :
		val = val.split(" ")
		date = val[0].split(":")
		heure = val[1].split(":")
		retour = date[2] + "/" + date[1] + "/" + date[0] + " " + heure[0] + ":" + heure[1] + ":"  + heure[2][0] + heure[2][1]
		return retour
	
	def dicoVide(val) :
		return ""
	
	dicoMeta = {
		'36864' : ['ExifVersion', u'Version Exif', dicoTexte],
		'37121' : ['ComponentsConfiguration', u'Composants de configuration', dicoVide],
		'37122' : ['CompressedBitsPerPixel', u'Compression octet par pixel', dicoTexte],
		'36867' : ['DateTimeOriginal', u'Date et heure d\'origine', dicoDate],
		'36868' : ['DateTimeDigitized', u'Date et heure numérisés', dicoDate],
		'37381' : ['MaxApertureValue', u'Ouverture max', dicoTexte],
		'40960' : ['FlashPixVersion', u'Version de FlashPix', dicoTexte],
		'37383' : ['MeteringMode', u'', dicoTexte],
		'37384' : ['LightSource', u'Source de lumière', dicoTexte],
		'37385' : ['Flash', u'Flash', dicoTexte],
		'37386' : ['FocalLength', u'Distance focale', dicoTexte],
		'41996' : ['41996', u'41996', dicoTexte],
		'37378' : ['ApertureValue', u'Ouverture', dicoTexte],
		'41486' : ['FocalPlaneXResolution', u'Hauteur du foyer', dicoTexte],
		'271' : ['Make', u'Marque', dicoTexte],
		'272' : ['Model', u'Modèle', dicoTexte], 
		'274' : ['Orientation', u'Orientation', dicoTexte],
		'531' : ['YCbCrPositioning', u'', dicoTexte],
		'41495' : ['SensingMethod', u'', dicoTexte],
		'33432' : ['Copyright', u'Tous droits réservés', dicoTexte],  
		'282' : ['XResolution', u'Résolution X', dicoTexte],
		'283' : ['YResolution', u'Résolution Y', dicoTexte],
		'33434' : ['ExposureTime', u'Durée d\exposition', dicoTexte],
		'41728' : ['FileSource', u'', dicoVide],
		'40965' : ['ExifInteroperabilityOffset', u'', dicoTexte],
		'34850' : ['ExposureProgram', u'Programme d\'exposition', dicoTexte],
		'40961' : ['ColorSpace', u'Espace colorimétrique', dicoTexte],
		'41990' : ['41990', u'', dicoTexte],
		'34855' : ['ISOSpeedRatings', u'ISO', dicoTexte],
		'296' : ['ResolutionUnit', u'Unité de la résolution', dicoTexte],
		'41987' : ['41987', u'', dicoTexte],
		'33437' : ['FNumber', u'Indice d\'ouverture (F)', dicoTexte],
		'305' : ['Software', u'Logiciel', dicoTexte],
		'306' : ['DateTime', u'Date et heure', dicoDate],
		'41729' : ['SceneType', u'Type de scène', dicoVide],
		'41994' : ['41994', u'', dicoTexte],
		'40962' : ['ExifImageWidth', u'Largeur en pixels', dicoTexte],
		'41985' : ['41985', u'', dicoTexte],
		'41487' : ['FocalPlaneYResolution', u'Largeur du foyer', dicoTexte],
		'40963' : ['ExifImageHeight', u'Hauteur en pixels', dicoTexte],
		'41488' : ['FocalPlaneResolutionUnit', u'Résolution du foyer', dicoTexte],
		'41986' : ['41986', u'', dicoTexte],
		'34665' : ['ExifOffset', u'', dicoTexte],
		'37500' : ['MakerNote', u'', dicoVide],
		'37510' : ['UserComment', u'Commentaire', dicoCommentaire],
		'40092' : ['40092', u'', dicoTexte],
		'40091' : ['40091', u'', dicoTexte],
		'270' : ['ImageDescription', u'Description', dicoTexte],
		'41992' : ['41992', u'', dicoTexte],
		'41988' : ['41988', u'', dicoTexte],
		'41991' : ['41991', u'', dicoTexte],
		'41993' : ['41993', u'', dicoTexte],
		'41989' : ['41989', u'', dicoTexte],
		'256' : ['ImageWidth', u'Largeur', dicoTexte],
		'257' : ['ImageLength', u'Hauteur', dicoTexte],
		'258' : ['BitsPerSample', u'Bits par échantillon', dicoTexte],
		'259' : ['Compression', u'Compression', dicoTexte],
		'37520' : ['SubsecTime', u'Sous-seconde', dicoTexte],
		'37521' : ['SubsecTimeOriginal', u'Sous-seconde d\'origine', dicoTexte],
		'262' : ['PhotometricInterpretation', u'Interprétation photométrique', dicoTexte],
		'277' : ['SamplesPerPixel', u'Échantillons par pixel', dicoTexte],
		'284' : ['PlanarConfiguration', u'Configuration planaire', dicoTexte],
		'37382' : ['SubjectDistance', u'Distance du sujet', dicoTexte],
		'37522' : ['SubsecTimeDigitized', u'Sous-seconde numérisée', dicoTexte],
		'34853' : ['GPSInfo', u'Infos GPS', dicoTexte],
		'315' : ['Artist', u'Artiste', dicoTexte],
		'41730' : ['CFAPattern', u'', dicoTexte],
		'42034' : ['42034', u'', dicoTexte],
		'42033' : ['42033', u'', dicoTexte],
		'50341' : ['50341', u'', dicoTexte],
		'42032' : ['42032', u'', dicoTexte],
		'42036' : ['Lens', u'Objectif', dicoTexte],
		'34866' : ['34866', u'', dicoTexte],
		'34864' : ['34864', u'', dicoTexte],
		'42037' : ['42037', u'', dicoTexte],
		'529' : ['YCbCrCoefficients', u'', dicoTexte],
		'42240' : ['42240', u'', dicoTexte],
		'318' : ['WhitePoint', u'Point blanc', dicoTexte],
		'319' : ['PrimaryChromaticities', u'', dicoTexte],
		'' : ['', u'', dicoTexte]
	}
	
	def __init__(self, parent, pos=(-1,-1)):
		wx.Dialog.__init__(self, parent, pos=pos) 
		self.FENETRE = parent
		self.exif = parent.exif
		self.InitInterface()
		self.SetSize((400, 670))
		self.SetTitle(u"Informations")
		self.style = wx.NO_BORDER
	
	def InitBouton(self, txt="", gros=False) :
		btn = btnplat.PlateButton(self.panneau, wx.ID_ANY, txt, None, style=btnplat.PB_STYLE_SQUARE)
		if gros == 1 : btn.SetFont(wx.Font(15, wx.MODERN, wx.NORMAL, wx.BOLD))
		elif gros == 2 : btn.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.BOLD))
		elif gros == -1 : btn.SetFont(wx.Font(9, wx.MODERN, wx.NORMAL, wx.BOLD))
		else : btn.SetFont(wx.Font(8, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.NORMAL))
		btn.SetLabelColor(wx.Colour(230, 230, 230))
		btn.SetPressColor(wx.Colour(40, 40, 40))
		btn.SetBackgroundColour(wx.Colour(20, 20, 20))
		return btn
	
	def InitInterface(self):
		self.panneau = wx.Panel(self)
		self.boite = wx.GridBagSizer()
		gros = wx.Font(15, wx.MODERN, wx.NORMAL, wx.BOLD)
		
		self.panneau.SetBackgroundColour(wx.Colour(20, 20, 20))
		
		btnGauche = self.InitBouton(u'   ◄   ', 1)
		self.boite.Add(btnGauche, (0,0), (1,1), wx.ALIGN_CENTER)
		btnGauche.Bind(wx.EVT_BUTTON, self.eveBtnGauche)
		
		self.texte = wx.TextCtrl(self.panneau, size=(-1,600), style=wx.NO_BORDER|wx.TE_MULTILINE)
		self.boite.Add(self.texte, (1,0), (1,1), wx.EXPAND|wx.ALL, border=5)
		self.texte.SetBackgroundColour(wx.Colour(40, 40, 40))
		self.texte.SetForegroundColour(wx.Colour(230, 230, 230))
		self.TraitementMeta()
		
		self.boite.AddGrowableCol(0)
		self.boite.AddGrowableCol(1)
		# self.boite.AddGrowableRow(0)
		
		wx.EVT_CLOSE(self, self.eveFerme)
		self.panneau.SetSizer(self.boite)
		self.Layout()
	
	def TraitementMeta(self) :
		exif = self.exif
		if not exif :
			self.texte.Value = 'Aucune information EXIF.'
			return
		liste = []
		self.texte.Value = ""
		i = 1
		for clef, valeur in exif.items():
			if not str(clef) in self.dicoMeta :
				print clef, TAGS.get(clef, clef), valeur
				continue
			motdic = TAGS.get(clef, clef)
			motdic = self.dicoMeta[str(clef)]
			if motdic[1] == '' : mot = "***" + motdic[0]
			else : mot = motdic[1]
			fnc = motdic[2]
			ti = str(i)
			if i < 10 : ti = "0" + str(i)
			entree = ti + ". " + mot + " : " + fnc(valeur)
			liste.append(entree)
			i += 1
		imax = len(liste)
		liste.sort()
		liste = '\n'.join(liste)
		self.texte.Value = liste + "\n" + str(imax)
		# print ''.join(liste)
		# return ''
		
	
	def eveBtnGauche(self, eve) :
		pass

	def eveFerme(self, eve) :
		self.Destroy()


class Dialogue(wx.Dialog) :
	FENETRE = None
	verrsuppr = False
	plus = False
	
	def __init__(self, parent, pos):
		wx.Dialog.__init__(self, parent, pos=pos) 
		self.FENETRE = parent
		self.InitInterface()
		self.SetSize((208, 135))
		self.SetTitle(u"Contrôle")
		self.style = wx.NO_BORDER
	
	def InitBouton(self, txt="", gros=False, coul=[0,0,0]) :
		btn = btnplat.PlateButton(self.panneau, wx.ID_ANY, txt, None, style=btnplat.PB_STYLE_SQUARE)
		if gros == 1 : btn.SetFont(wx.Font(15, wx.MODERN, wx.NORMAL, wx.BOLD))
		elif gros == 2 : btn.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.BOLD))
		elif gros == -1 : btn.SetFont(wx.Font(9, wx.MODERN, wx.NORMAL, wx.BOLD))
		else : btn.SetFont(wx.Font(8, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.NORMAL))
		
		if coul[0] == 0 : btn.SetLabelColor(wx.Colour(230, 230, 230))
		
		if coul[1] == 0 : btn.SetPressColor(wx.Colour(40, 40, 40))
		
		if coul[2] == 0 : btn.SetBackgroundColour(wx.Colour(20, 20, 20))
		elif coul[2] == 1 : btn.SetBackgroundColour(wx.Colour(40, 40, 40))
		elif coul[2] == 2: btn.SetBackgroundColour(wx.Colour(100, 20, 20))
		
		return btn
	
	def InitInterface(self):
		self.panneau = wx.Panel(self)
		self.boite = wx.GridBagSizer()
		gros = wx.Font(15, wx.MODERN, wx.NORMAL, wx.BOLD)
		
		self.panneau.SetBackgroundColour(wx.Colour(20, 20, 20))
		
		btnGauche = self.InitBouton(u'   ◄   ', 1)
		self.boite.Add(btnGauche, (0,0), (1,1), wx.EXPAND)
		btnGauche.Bind(wx.EVT_BUTTON, self.eveBtnGauche)
		
		btnDroite = self.InitBouton(u'   ►   ', 1)
		self.boite.Add(btnDroite, (0,1), (1,1), wx.EXPAND)
		btnDroite.Bind(wx.EVT_BUTTON, self.eveBtnDroite)
		
		btnZoomM = self.InitBouton('     -     ', 2)
		self.boite.Add(btnZoomM, (1,0), (1,1), wx.EXPAND)
		btnZoomM.Bind(wx.EVT_BUTTON, self.eveBtnZoomM)
		
		btnZoomP = self.InitBouton('     +     ', 2)
		self.boite.Add(btnZoomP, (1,1), (1,1), wx.EXPAND)
		btnZoomP.Bind(wx.EVT_BUTTON, self.eveBtnZoomP)
		
		btnTaille = self.InitBouton(u'   Ajuster la taille   ')
		self.boite.Add(btnTaille, (2,0), (1,1), wx.EXPAND)
		btnTaille.Bind(wx.EVT_BUTTON, self.eveBtnTaille)
		
		btnPleinEcran = self.InitBouton(u'     Plein écran     ')
		self.boite.Add(btnPleinEcran, (2,1), (1,1), wx.EXPAND)
		btnPleinEcran.Bind(wx.EVT_BUTTON, self.eveBtnPleinEcran)
		
		self.btnSupprimer = self.InitBouton(u'      Supprimer      ')
		self.boite.Add(self.btnSupprimer, (3,0), (1,1), wx.EXPAND)
		self.btnSupprimer.Bind(wx.EVT_BUTTON, self.eveBtnSuppr)
		if self.verrsuppr : self.btnSupprimer.SetBackgroundColour(wx.Colour(100, 20, 20))
		
		btnFermer = self.InitBouton(u'         Quitter         ')
		self.boite.Add(btnFermer, (3,1), (1,1), wx.EXPAND)
		btnFermer.Bind(wx.EVT_BUTTON, self.eveBtnFermer)
		
		btnPlus = self.InitBouton(u'            ...            ', -1, [0,0,1])
		self.boite.Add(btnPlus, (4,0), (1,2), wx.EXPAND)
		btnPlus.Bind(wx.EVT_BUTTON, self.eveBtnPlus)
		btnPlus.SetLabelColor(wx.Colour(100, 100, 100))
		
		btnVerrSuppr = self.InitBouton(u'   Suppr. directe   ')
		self.boite.Add(btnVerrSuppr, (5,0), (1,1), wx.EXPAND)
		btnVerrSuppr.Bind(wx.EVT_BUTTON, self.eveBtnVerrSuppr)
		
		btnInfos = self.InitBouton(u'          Infos          ')
		self.boite.Add(btnInfos, (5,1), (1,1), wx.EXPAND)
		btnInfos.Bind(wx.EVT_BUTTON, self.eveBtnInfos)
		
		self.btnFond = self.InitBouton(u'          Fond           ')
		self.boite.Add(self.btnFond, (6,0), (1,1), wx.EXPAND)
		self.btnFond.Bind(wx.EVT_BUTTON, self.eveBtnFond)
		self.eveBtnFond(None, False)
		
		self.btnExif = self.InitBouton(u'     Utiliser EXIF     ')
		self.boite.Add(self.btnExif, (6,1), (1,1), wx.EXPAND)
		self.btnExif.Bind(wx.EVT_BUTTON, self.eveBtnExif)
		if self.FENETRE.verrexif : self.btnExif.SetBackgroundColour(wx.Colour(100, 20, 20))
		
		self.btnAleat = self.InitBouton(u'       Aléatoire')
		self.boite.Add(self.btnAleat, (7,0), (1,1), wx.EXPAND)
		self.btnAleat.Bind(wx.EVT_BUTTON, self.eveBtnAleat)
		if self.FENETRE.verraleat : self.btnAleat.SetBackgroundColour(wx.Colour(100, 20, 20))
		
		self.btnFigzo = self.InitBouton(u'    Figer le zoom')
		self.boite.Add(self.btnFigzo, (7,1), (1,1), wx.EXPAND)
		self.btnFigzo.Bind(wx.EVT_BUTTON, self.eveBtnFigzo)
		if self.FENETRE.verrzoom : self.btnFigzo.SetBackgroundColour(wx.Colour(100, 20, 20))
		
		btnEnr = self.InitBouton(u'       Exporter')
		self.boite.Add(btnEnr, (8,0), (1,1), wx.EXPAND)
		btnEnr.Bind(wx.EVT_BUTTON, self.eveBtnEnr)
		
		btnEnrLot = self.InitBouton(u'    Export par lot')
		self.boite.Add(btnEnrLot, (8,1), (1,1), wx.EXPAND)
		btnEnrLot.Bind(wx.EVT_BUTTON, self.eveBtnEnrLot)
		
		
		self.boite.AddGrowableCol(0)
		self.boite.AddGrowableCol(1)
		# self.boite.AddGrowableRow(0)
		
		wx.EVT_CLOSE(self, self.eveFerme)
		
		self.panneau.SetSizer(self.boite)
		self.Layout()
	
	def eveBtnEnrLot(self, eve) :
		self.FENETRE.ExportLot()
	
	def eveBtnEnr(self, eve) :
		self.FENETRE.Exporter()
	
	def eveBtnFigzo(self, eve) :
		self.FENETRE.verrzoom = not self.FENETRE.verrzoom
		if self.FENETRE.verrzoom : self.btnFigzo.SetBackgroundColour(wx.Colour(100, 20, 20))
		else : self.btnFigzo.SetBackgroundColour(wx.Colour(20, 20, 20))
		self.btnFigzo.Refresh()
	
	def eveBtnAleat(self, eve) :
		self.FENETRE.verraleat = not self.FENETRE.verraleat
		if self.FENETRE.verraleat : self.btnAleat.SetBackgroundColour(wx.Colour(100, 20, 20))
		else : self.btnAleat.SetBackgroundColour(wx.Colour(20, 20, 20))
		self.btnAleat.Refresh()
	
	def eveBtnFond(self, eve, affecter=True) :
		coul = self.FENETRE.fond
		if affecter :
			if coul == 0 : coul = 20
			elif coul == 20 : coul = 127
			elif coul == 127 : coul = 255
			elif coul == 255 : coul = 0
			self.FENETRE.ModifFond(coul)
		self.btnFond.SetBackgroundColour(wx.Colour(coul, coul, coul))
		self.btnFond.SetPressColor(wx.Colour(coul, coul, coul))
		
	def eveBtnExif(self, eve, forcer=False) :
		if not forcer : self.FENETRE.verrexif = not self.FENETRE.verrexif
		else : self.FENETRE.verrexif = True
		
		if self.FENETRE.verrexif :
			self.btnExif.SetBackgroundColour(wx.Colour(100, 20, 20))
			if not self.FENETRE.exif : self.FENETRE.MetaDonnees()
		else :
			self.btnExif.SetBackgroundColour(wx.Colour(20, 20, 20))
			self.FENETRE.exif = None
		self.btnExif.Refresh()
	
	def eveBtnInfos(self, eve) :
		if self.FENETRE.DIALINFOS :
			self.FENETRE.DIALINFOS.Destroy()
			return
		self.eveBtnExif(None, True)
		pos = (wx.DisplaySize()[0] - 420, 20)
		self.FENETRE.DIALINFOS = DialogueInfos(self.FENETRE, pos)
		self.FENETRE.DIALINFOS.Show()
	
	def eveBtnVerrSuppr(self, eve) :
		self.verrsuppr = not self.verrsuppr
		if self.verrsuppr : self.btnSupprimer.SetBackgroundColour(wx.Colour(100, 20, 20))
		else : self.btnSupprimer.SetBackgroundColour(wx.Colour(20, 20, 20))
		self.btnSupprimer.Refresh()
	
	def eveBtnPlus(self, eve) :
		self.plus = not self.plus
		if self.plus : self.SetSize((208, 300))
		else : self.SetSize((208, 135))
		self.Layout()
	
	def eveBtnFermer(self, eve) :
		self.Destroy()
		self.FENETRE.Destroy()
	
	def eveBtnSuppr(self, eve) :
		self.FENETRE.Supprimer(self.verrsuppr)
	
	def eveBtnZoomP(self, eve) :
		self.FENETRE.AjusteZoom(100)
	
	def eveBtnZoomM(self, eve) :
		self.FENETRE.AjusteZoom(-100)
	
	def eveBtnPleinEcran(self, eve) :
		self.FENETRE.ModePleinEcran()
	
	def eveBtnTaille(self, eve) :
		self.FENETRE.AjusteTaille()
	
	def eveBtnGauche(self, eve) :
		self.FENETRE.Naviguer(-1)
		
	def eveBtnDroite(self, eve) :
		self.FENETRE.Naviguer(1)
	
	def eveFerme(self, eve) :
		self.Destroy()

class Fenetre(wx.Frame) :
	liste = []
	listeLong = 0
	num = 0
	echelle = 1.0
	echelleOpt = 1.0
	exts = ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'pcx', 'pnm', 'tif', 'xpm', 'ico', 'cur', 'ani', 'tga', 'pix']
	bordures = (0, 0)
	pleinecran = False
	DIALOGUE = None
	DIALINFOS = None
	posImg = (0, 0)
	posImgDer = (0, 0)
	exif = None
	verrexif = False
	verraleat = False
	verrzoom = False
	orientation = 1
	fond = 20
	image = None
	
	def __init__(self, parent, id, title) :
		wx.Frame.__init__(self, parent, id, title, size=[300, 500])
		
		icone = wx.Icon("icone.ico", wx.BITMAP_TYPE_ICO)
		self.SetIcon(icone)
		
		self.SetBackgroundColour(wx.Colour(self.fond, self.fond, self.fond))
		self.panneau = wx.Panel(self, -1, style=0)
		
		self.anim = None
		self.bitmap = wx.EmptyBitmap(1, 1)
		self.staticbitmap = wx.StaticBitmap(self, bitmap = self.bitmap)
		
		self.boite = wx.GridSizer()
		# self.boite = wx.BoxSizer()
		self.boite.Add(self.staticbitmap, 0, wx.ALIGN_CENTER | wx.ALL | wx.ADJUST_MINSIZE, 0)
		self.panneau.Bind(wx.EVT_KEY_DOWN, self.eveClavier)
		self.panneau.Bind(wx.EVT_MOUSEWHEEL, self.eveSourisMolette)
		self.panneau.Bind(wx.EVT_RIGHT_UP, self.eveSourisClicDroit)
		self.panneau.Bind(wx.EVT_MOTION, self.eveSourisBouge)
		self.panneau.Bind(wx.EVT_LEFT_DOWN, self.eveSourisClicGauche)
		self.panneau.Bind(wx.EVT_LEFT_UP, self.eveSourisDeclicGauche)
		self.panneau.Bind(wx.EVT_LEFT_DCLICK, self.eveSourisDoubleClicGauche)
		self.panneau.Bind(wx.EVT_MIDDLE_DOWN, self.eveSourisClicMilieu)
		
		self.SetSizerAndFit(self.boite)
		# self.panneau.SetSizerAndFit(self.boite)
		
		self.Listeur()
		
		wx.EVT_CLOSE(self, self.eveFerme)
		self.Bind(wx.EVT_SIZE, self.eveRecadre)
		
		self.Show(True)
	
	
	def eveSourisClicMilieu(self, eve) :
		self.AjusteTaille()
	
	def eveSourisDoubleClicGauche(self, eve) :
		self.ModePleinEcran()
	
	def eveSourisDeclicGauche(self, eve) :
		self.Update()
	
	def eveSourisClicGauche(self, eve) :
		posImage = self.staticbitmap.GetPosition()
		posSouris = eve.GetPosition()
		self.posImgDer = (posSouris[0] - posImage[0], posSouris[1] - posImage[1])
		
	def eveSourisBouge(self, eve) :
		if eve.LeftIsDown() :
			posAns = self.posImgDer
			posSouris = eve.GetPosition()
			pos = (posSouris[0] - posAns[0], posSouris[1] - posAns[1])
			self.staticbitmap.SetPosition(pos)
			self.Update()
	
	def eveSourisClicDroit(self, eve) :
		if self.DIALOGUE :
			self.DIALOGUE.Destroy()
			return
		posSouris = eve.GetPosition() + self.GetPosition()
		posSouris[0] -= 104
		posSouris[1] += 10
		self.DIALOGUE = Dialogue(self, pos=posSouris)
		self.DIALOGUE.Show()
	
	def eveSourisMolette(self, eve) :
		val = eve.GetWheelRotation()
		self.AjusteZoom(val)
	
	def eveClavier(self, eve) :
		touche = eve.GetKeyCode()
		
		if touche == wx.WXK_LEFT :
			self.Naviguer(-1)
		
		elif touche == wx.WXK_RIGHT :
			self.Naviguer(1)
		
		elif touche == wx.WXK_DELETE :
			self.Supprimer()
	
	def eveRecadre(self, eve) :
		taille = self.GetSize()
		self.panneau.SetSize(taille)
		self.panneau.Layout()
	
	def eveFerme(self, eve) :
		self.Destroy()
	
	def ModifFond(self, coul) :
		self.fond = coul
		self.SetBackgroundColour(wx.Colour(coul, coul, coul))
		self.Refresh()
	
	def MetaDonnees(self, lien=None) :
		if not lien :
			lien = self.liste[self.num]
			if not os.path.exists(lien) : return
		
		image = PILImage.open(lien)
		exifbrut = None
		self.orientation = 1
		
		try : exifbrut = image._getexif()
		except : pass
		
		if exifbrut :
			try : self.orientation = exifbrut[274]
			except KeyError : pass
		
		self.exif = exifbrut
	
	def ExportLot(self) :
		if not self.image : return
		if not self.liste : return
		source = self.liste[self.num]
		if not source : return
		dossier = os.path.dirname(source)
		dossier = os.path.abspath(dossier)
		dest = wx.DirDialog(self, defaultPath = dossier)
		retour = dest.ShowModal()
		if retour == 5101 : return
		chemin = dest.GetPath()
		if not chemin : return
		for i, f in enumerate(self.liste) :
			self.num = i
			self.Charger()
			nom = os.path.basename(f)
			nom = os.path.splitext(nom)[0] + ".png"
			chem = os.path.join(chemin, nom)
			self.image.SaveFile(chem, wx.BITMAP_TYPE_PNG)
	
	def Exporter(self) :
		if not self.image : return
		if not self.liste : return
		source = self.liste[self.num]
		if not source : return
		dossier = os.path.dirname(source)
		nom = os.path.basename(source)
		nom = os.path.splitext(nom)[0]
		ext = "PNG (*.png)|*.png"
		selection = wx.FileDialog(self, 'Exporter',  defaultDir = dossier, defaultFile = nom,  wildcard = ext, style = wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
		retour = selection.ShowModal()
		chemin = selection.GetPath()
		if not chemin : return
		self.image.SaveFile(chemin, wx.BITMAP_TYPE_PNG)
	
	def Supprimer(self, confirme=False) :
		if not confirme :
			if not self.BoiteMessageSuppr() : return
		poub = os.path.normpath(os.environ['SYSTEMDRIVE'] + r"\\$Recycle.Bin\\")
		if not os.path.exists(poub) : return
		corb = os.listdir(poub)
		if not corb : return
		corb = os.path.join(poub, corb[0])
		if not self.liste : return
		source = self.liste[self.num]
		fichier = os.path.basename(source)
		dest = os.path.join(poub, fichier) # corb, fichier
		# print source
		# print corb
		# print dest
		# os.system("del " + source)
		# print os.system("move " + source + " " + corb)
		shutil.move(source, dest)
		self.liste.pop(self.num)
		self.listeLong -= 1
		if not self.listeLong : return
		self.num = self.num % self.listeLong
		self.Charger()
	
	def ModePleinEcran(self) :
		if not self.pleinecran :
			self.ShowFullScreen(True)
			self.pleinecran = True
		
		else :
			self.ShowFullScreen(False)
			self.pleinecran = False
			
		self.Recadre()
	
	def AjusteZoom(self, val) :
		self.echelle += round(val * 0.002 * self.echelle, 3)
		if self.echelle < 0.012 : self.echelle = 0.012
		if self.echelle < 1.1 and self.echelle > 0.9 : self.echelle = 1.0
		if self.echelle < self.echelleOpt + 0.037 and self.echelle > self.echelleOpt - 0.037 : self.echelle = self.echelleOpt
		self.Zoom()
	
	def AjusteTaille(self) :
		if self.echelle == self.echelleOpt : self.echelle = 1.0
		else : self.echelle = self.echelleOpt
		self.Zoom()
	
	def Naviguer(self, n) :
		if not self.listeLong : return
		if self.verraleat : self.num = random.randint(0, self.listeLong-1)
		else : self.num = (self.num + n) % self.listeLong
		self.Charger()
	
	def BoiteMessageSuppr(self):
		retour = wx.MessageBox(u'Voulez-vous supprimer cette image ?', 'Confirmation', wx.YES_NO | wx.ICON_QUESTION)
		if retour == wx.YES : return True
		elif retour == wx.NO : return False
		return False
	
	def Recadre(self) :
		if not self.pleinecran :
			self.Fit()
			self.Center()
		self.Layout()
		self.Refresh()
	
	def Retaille(self) :
		tailleImg = self.staticbitmap.GetSize()
		tailleMax = wx.DisplaySize()
		
		bord = (15, 60)
		if self.pleinecran : bord = (0, 0)
		
		ratio = 1.0
		ratio2 = 1.0
		
		if tailleMax[0] - bord[0] < tailleImg[0] :
			ratio = float(tailleMax[0] - bord[0]) / float(tailleImg[0])
		
		if tailleMax[1] - bord[1] < tailleImg[1] :
			ratio2 = float(tailleMax[1] - bord[1]) / float(tailleImg[1])
		
		if ratio2 < ratio : ratio = ratio2
		if ratio != 1.0 :
			self.echelle = ratio
			self.echelleOpt = ratio
			self.Zoom()
			return
		
		self.echelleOpt = 1.0
		self.Recadre()
	
	def Zoom(self) :
		l = self.image.GetWidth()
		h = self.image.GetHeight()
		e = self.echelle
		
		# image = self.image.Rescale(l * e, h * e, 1)
		if (e > 1) :
			image = self.image.Scale(l * e, h * e, wx.IMAGE_QUALITY_NORMAL)
		else :
			image = self.image.Scale(l * e, h * e, wx.IMAGE_QUALITY_HIGH)
		self.bitmap = wx.BitmapFromImage(image)
		self.staticbitmap.SetBitmap(self.bitmap)
		
		self.Recadre()
		
	def Listeur(self) :
		if len(sys.argv) == 1 : return
		lien = os.path.relpath(sys.argv[1])
		# lien = "Images\\e.jpg"
		
		dossier = os.path.split(lien)
		dossier = dossier[0]
		if dossier == "" :
			dossier = "."
			lien = '.\\' + lien
		contenu = os.listdir(dossier)
		
		liste = []
		exts = self.exts
		for f in contenu :
			if os.path.isdir(f) : continue
			fex = os.path.splitext(f)
			if len(fex) == 1 : continue
			ext = (fex[1][1:]).lower()
			if ext in exts : liste.append(os.path.join(dossier, f))
	

		self.num = liste.index(lien)
		self.liste = liste
		self.listeLong = len(liste)
		
		self.Charger()
	
	def Charger(self) :
		lien = self.liste[self.num]
		if not os.path.exists(lien) : return
		
		if self.verrexif : self.MetaDonnees(lien)
		
		if self.DIALINFOS : 
			self.DIALINFOS.exif = self.exif
			self.DIALINFOS.TraitementMeta()
		
		self.SetTitle(os.path.os.path.basename(lien))
		
		type = os.path.splitext(lien)
		type = (type[1][1:]).lower()
		
		if self.anim : self.anim.Destroy()
		
		if type == 'gif' :
			self.anim = wx.animate.GIFAnimationCtrl(self, wx.ID_ANY, lien, pos=(0, 0))
			self.anim.Play()
			self.image = wx.Image(lien, wx.BITMAP_TYPE_ANY)
			self.bitmap = wx.BitmapFromImage(self.image)
			self.staticbitmap.SetBitmap(self.bitmap)
		
		elif type == 'jpg' or type == 'jpeg' :
			self.image = wx.Image(lien, wx.BITMAP_TYPE_ANY)
			self.image.InitAlpha()
			self.bitmap = wx.BitmapFromImage(self.image)
			self.staticbitmap.SetBitmap(self.bitmap)
		
		elif type == 'tga' :
			self.image = self.LireTGA(lien)
			self.bitmap = wx.BitmapFromImage(self.image)
			self.staticbitmap.SetBitmap(self.bitmap)
		
		elif type == 'pix' :
			self.image = self.LirePIX(lien)
			self.bitmap = wx.BitmapFromImage(self.image)
			self.staticbitmap.SetBitmap(self.bitmap)
		
		else :
			self.image = wx.Image(lien, wx.BITMAP_TYPE_ANY)
			if not self.image.HasAlpha() : self.image.InitAlpha()
			self.bitmap = wx.BitmapFromImage(self.image)
			self.staticbitmap.SetBitmap(self.bitmap)
		
		if self.verrzoom :
			self.Zoom()
		else :
			self.echelle = 1.0
			self.Retaille()
	
	def LirePIX(self, lien) : # Carmageddon 2
		img = wx.EmptyImage()
		f = open(lien, "rb")
		if not f : return img
		# En-tête
		f.read(16)
		f.read(8)
		type = st.unpack(">B", f.read(1))[0] # 3 : 8 bits, 5 : 16 bits, 18 : 16 bits alpha
		f.read(2)
		largeur = st.unpack(">H", f.read(2))[0]
		hauteur = st.unpack(">H", f.read(2))[0]
		f.read(6)
		o = st.unpack(">B", f.read(1))[0]
		while o : o = st.unpack(">B", f.read(1))[0]
		f.read(4)
		taille = st.unpack(">L", f.read(4))[0] # Nombre d'octets des données de pixel
		nbpix = st.unpack(">L", f.read(4))[0] # Nombre de pixels
		format = st.unpack(">L", f.read(4))[0] # Nombre de bits pour un pixel : 1 = 8 bits, 2 = 16 bits
		# Pixels
		pixel = []
		img.Create(largeur, hauteur)
		img.InitAlpha()
		for i in xrange(nbpix) :
			x = i % largeur
			y = i / largeur
			if type == 5 : # 16 bits
				val = st.unpack(">H", f.read(2))[0]
				pixel = []
				pixel.append(((val & 0xF800) >> 11) << 3)
				pixel.append(((val & 0x07E0) >> 5) << 2)
				pixel.append(((val & 0x001F) >> 0) << 3)
				img.SetRGB(x, y, pixel[0], pixel[1], pixel[2])
			
			elif type == 18 : # 16 bits alpha
				val = st.unpack(">H", f.read(2))[0]
				pixel = []
				pixel.append(((val & 0x0F00) >> 8) << 4)
				pixel.append(((val & 0x00F0) >> 4) << 4)
				pixel.append(((val & 0x000F) >> 0) << 4)
				pixel.append(((val ) >> 12) << 4)
				img.SetRGB(x, y, pixel[0], pixel[1], pixel[2])
				img.SetAlpha(x, y, pixel[3])
			
		return img
	
	def LireTGA(self, lien) :
		img = wx.EmptyImage()
		f = open(lien, "rb")
		if not f : return img
		palette = []
		# En-tête
		id_taille = st.unpack("B", f.read(1))[0]
		apalette = st.unpack("B", f.read(1))[0]
		imagetype = st.unpack("B", f.read(1))[0] # ?, 8 bits nc pal, 16 24 32 bits nc BRV, 8 16 bits nc gris, idem 1 compressé, idem 2 c, idem 3 c
		pal_deb = st.unpack("h", f.read(2))[0] # Index de la première couleur dans la palette
		pal_long = st.unpack("h", f.read(2))[0] # Nombre de couleurs de la palette
		pal_tail = st.unpack("B", f.read(1))[0] # Nombre de bits par couleur de la palette. Il peut être de 15, 16, 24 ou 32.
		f.read(4)
		largeur = st.unpack("h", f.read(2))[0]
		hauteur = st.unpack("h", f.read(2))[0]
		format = st.unpack("B", f.read(1))[0] # Nombre de bits utilisés pour stocker un pixel de l'image
		f.read(1)
		f.read(id_taille)
		pixel = []
		#f.seek(id_taille, f.tell())
		# Palette
		if apalette :
			p_tail = pal_tail >> 3
			for p in xrange(pal_long) :
				pixel = st.unpack("BBB", f.read(p_tail))
				# pixel = f.read(p_tail)
				palette.append(pixel)
		# Image
		img.Create(largeur, hauteur)
		img.InitAlpha()
		chn = ""
		chna = ""
		# brut = img.GetData()
		for i in xrange(largeur * hauteur) :
			x = i % largeur
			
			if imagetype == 1 : # 8 bits non compressé avec palette
				index = st.unpack("B", f.read(1))[0]
				pixel = palette[index]
				o = palette[index]
				# if pixel == (242, 242, 242) : chn += '\xf2\xf2\xf2'
				# else : chn += o[2] + o[1] + o[0]
				y = (hauteur-1) - (i/largeur)
				img.SetRGB(x, y, pixel[2], pixel[1], pixel[0])
			
			elif imagetype == 2 : # 16 24 ou 32 bits non compressé BRV
				if format == 16 :
					o = st.unpack("BB", f.read(2))
					# http://tfcduke.developpez.com/tutoriel/format/tga/
					val = o[0] + (o[1] << 8)
					pixel = []
					pixel.append(((val & 0x001F) >> 0) << 3)
					pixel.append(((val & 0x03E0) >> 5) << 3)
					pixel.append(((val & 0x7C00) >> 10) << 3)
					# o = chr(pixel[2]) + chr(pixel[1]) + chr(pixel[0])
					y = (hauteur-1) - (i/largeur)
				if format == 24 :
					# o = f.read(3)
					pixel = st.unpack("BBB", f.read(3))
					y = (hauteur-1) - (i/largeur)
				elif format == 32 :
					# o = f.read(4)
					pixel = st.unpack("BBBB", f.read(4))
					y = i/largeur
					img.SetAlpha(x, y, pixel[3])
					# chna += o[3]
				
				# chn += o[2] + o[1] + o[0]
				# chn.extend([pixel[2], pixel[1], pixel[0]])
				img.SetRGB(x, y, pixel[2], pixel[1], pixel[0])
			
			elif imagetype == 3 : # 8 ou 16 bits non compressé gris
				if format == 8 :
					pixel = st.unpack("B", f.read(1))[0]
				elif format == 16 :
					pixel = st.unpack("h", f.read(2))[0]
				y = (hauteur-1) - (i/largeur)
				img.SetRGB(x, y, pixel, pixel, pixel)
		
		# img.SetData(img.GetData())
		# if chna : img.SetAlphaData(chna)
		f.close()
		return img
	
def main() :
	lec = wx.App() # App() pour console wx, (False) sans
	fenetre = Fenetre(None, -1, "ImageVonc")
	lec.MainLoop()

if __name__ == "__main__" :
	main()