IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Développement de plugins pour Millie

Date de publication : 4/07/2008 , Date de mise à jour : 6/01/2010


I. Description du système de plugins
I-A. Contenu de l'archive MilliePlugins
I-B. Création d'un premier plugin
II. Différentes manières de développer
II-A. Première manière
II-B. Seconde méthode
III. Fonctionnalités basiques
III-A. Les types de paramètres
III-A-1. ComboBoxParameter
III-A-2. CheckBoxParameter
III-A-3. IntTextFieldParameter
III-A-4. DoubleTextFieldParameter
III-A-5. DoubleSliderParameter
III-A-6. IntSliderParameter
III-A-7. RadioButtonGroupParameter
III-A-8. ImageParameter
III-A-9. ColorParameter
III-A-10. FileParameter
III-B. GenericAppImagePlugin
III-B-1. Quelques exemples
III-B-1-a. Niveau de gris
III-B-1-b. Image HSL
III-B-1-c. Filtre UnNoise
III-C. AreaAppImagePlugin
III-C-1. Exemples
III-C-1-a. Flou local
III-D. RectangularAppImagePlugin
III-D-1. Exemples
III-D-1-a. Rognage
III-E. NoParameterAppImagePlugin
IV. Fonctionnalités avancées
IV-A. AppPlugin
IV-B. AppImagePlugin
IV-C. Automates
IV-D. LifecyclePlugin

Ce tutoriel a pour but de montrer comment développer des plugins pour la bibliothèque et l'IHM Millie. Une première partie détaillera le système de plugin hérité de la version 1.0.0 tandis que la seconde détaillera les nouveaux concepts introduit avec la version 2.0.0.


I. Description du système de plugins

L'application est structurée selon plusieurs archives java JAR :

  • Millie.jar qui correspond à la bibliothèque de traitement d'images
  • MilliePlugins.jar qui est l'unique bibliothèque à devoir utiliser pour ajouter et développer des plugins
  • MillieGUI.jar qui est une IHM devloppée en Swing
  • MillieCommand.jar qui permet d'appeler les filtres et les plugins en ligne de commande
  • MillieCommons.jar qui correspond aux codes partagées entre les différentes IHM
  • plugins/MillieCoreFilter.jar qui est une archive de plugins qui correspond à ce qu'il y a dans le menu Filtre
Un plugin correspond donc à un fichier JAR qui est ajouté dans le dossier plugins. Il suffit alors de relancer l'application ou d'utiliser le menu Plugins/Refresh pour prendre en compte le plugin.


I-A. Contenu de l'archive MilliePlugins

Cette bibliothèque contient un ensemble de classe pour développer les plugins.

Des classes pour définir des types de filtres :

  • AppPlugin qui correspond à un filtre lié à l'application mais pas à une image (typiquement un traitement en masse sur un dossier, un menu de préférences)
  • AppImagePlugin qui correspond à un filtre lié à une image
  • GenericAppImagePlugin, une spécialisation de AppImagePlugin qui correspond à tous les filtres sans interaction de la souris avec l'image (exemple : convolution, flou gaussien)
  • AreaAppImagePlugin qui correspond aux filtres utilisant la souris pour appliquer une opération à un certain point (exemple : flou local)
  • RectangularAppImagePlugin qui correspond aux filtres utilisant un rectangle de sélection pour le calcul (exemple : Rogner)
  • NoParameterAppImagePlugin qui correspond à un filtre sans paramètre (il ne faut que l'image d'entrée) (exemple : Dilation)
Des classes pour définir des filtres pouvant s'intégrer à l'IHM :

  • AppCommand permet de définir une commande à appliquer suite au démarrage du plugin
  • AppImageCommand permet de définir une commande à appliquer suite au démarrage du plugin sur une image
Ces 2 dernières classes doivent respecter certains contrats pour bien s'intégrer dans l'IHM.

Des classes permettant d'interagir avec l'application :

  • AppActions permet d'interagir avec l'application (typiquement, ouvrir une image, créer une image, fermer l'application)
  • AppImageActions permet d'interagir avec une fenêtre d'image (typiquement, mise à jour d'une image, sauvegarde)
  • AppListener permet d'être notifié d'action sur l'application (typiquement l'ouverture ou la fermeture d'une image)
  • AppImageListener permet d'être notifié d'action sur une image (typiquement la mise à jour d'une image, l'exécution d'une commande, une sauvegarde etc.)
  • MessageManager permet de communiquer des messages à l'application (type erreur, warning, info)
Des classes pour définir des types de paramètres :

  • CheckBoxParameter pour les checkbox
  • ComboBoxParameter pour les combobox
  • DoubleSliderParameter pour des sliders de type double
  • DoubleTextFieldParameter
  • ImageParameter pour choisir dans une combobox une autre image déjà ouverte
  • IntSliderParameter
  • IntTextFieldParameter
  • RadioButtonGroupParameter
  • TextFieldParameter
  • FileParameter
Les classes de paramètres et de définition des filtres sont totalement indépendantes de SWING et du rendu (à l'exception des commandes qui permettent de créer des composants SWING). Grosso modo, les filtres ne contiennent que le traitement métier et une table des paramètres.

Dans l'archive MillieGUI.jar, à chaque type de paramètre et à chaque type de filtre est associé un renderer qui sait afficher l'objet.

A noter que l'archive MillieCommons offre des fonctionnalités permettant d'ajouter son propre système de plugins.


I-B. Création d'un premier plugin

Pour montrer comment développer un plugin, je vais donner un premier exemple de code source. Le code métier utilise la bibliothèque Millie.jar, mais ce n'est pas important.

@PluginInfo(name="Flou Gaussien", category="Flou")
public class GaussianBlurPlugin extends GenericAppImagePlugin {

	public GaussianBlurPlugin() {
		setPluginName("Gaussian Blur");
		setLongProcessing(true);
		setRefreshable(true);
		setCacheable(true);
		addParameter(new IntSliderParameter("rayon", "Rayon", 0,10,0));
		addParameter(new DoubleTextFieldParameter("sigma", "Sigma", 1));
		
	}
	@Override
	public BufferedImage filter() throws Exception {
		Kernel k = PredefinedKernel.getGaussianKernel(getIntValue("rayon"), getDoubleValue("sigma"));
		ConvolveOperator op = new ConvolveOperator(k, new BorderExtenderCopy());
		return op.compute(getInputImage());
		
	}
}
On peut constater que l'on définie certains paramètres dans le constructeur (notamment le rayon et le sigma). On utilise également certains setter : setLongProcessing, setRefreshable... dont l'utilité sera expliqué plus tard.

Il est nécessaire d'ajouter une annotation @PluginInfo pour permettre l'intégration du plugin au menu

Pour ajouter ce filtre à l'IHM, il est nécessaire de compiler le projet sous forme d'une archive JAR puis de le copier dans le répertoire /plugins/ de l'application.

Exemple de rendu avec le filtre Flou Gaussien

II. Différentes manières de développer


II-A. Première manière

La première façon de développer un plugin est celle que l'on a vu : Création d'un projet utilisant l'archive MilliePlugins.jar, écriture des filtres, puis exportation de l'archive.

Il est malheureusement nécessaire d'exporter les archives à chaque essai.


II-B. Seconde méthode

La méthode que j'utilise est de développer avec les sources de l'IHM. Il est donc nécessaire de récuperer les 5 projets Millie, MillieGUI, MilliePlugins, MillieCommons, MillieLib et MillieCoreFilter et de les intégrer dans votre EDI.

Ensuite, il faut créer un nouveau projet attaché aux autres projets et créer vos classes de filtres normalement. En revanche, l'ajout du filtre se fera pas dans le fichier plugins.config du projet MillieGUI qui chaque plugin (une ligne par classe). Lors du lancement de l'application, MillieGUI regarde également dans sa racine le fichier plugins.config, il pourra ainsi voir vos entrées et lancer les filtres.

Une fois que le plugin est validé, il faut tout de même exporter l'archive JAR comme précédemment.


III. Fonctionnalités basiques

Cette section décrit le système de plugins hérité des versions 1.x.x.


III-A. Les types de paramètres

Pour les filtres de type GenericAppImagePlugin, AreaAppImagePlugin, RectangularAppImagePlugin, il est possible d'ajouter des paramètres dans une boite de dialogue.


III-A-1. ComboBoxParameter


public MonFiltre() {
		ComboBoxParameter p = new ComboBoxParameter("convolve", "Type de convolution");
		p.addItem("laplace", "Laplacien");
		p.addItem("sobelV", "Sobel Vertical");
		p.addItem("sobelH", "Sobel Horizontal");
		p.addItem("prewittH", "Prewitt Horizontal");
		p.addItem("prewittW", "Prewitt Vertical");
		p.addItem("mdif", "MDIF");
		p.addItem("mediumSmooth", "Medium Smooth");
		
		addParameter(p);
}
La chaîne "convolve" correspond à une clef qui permet de récuperer la valeur du paramètre. La chaîne "Type de convolution" correspond à une description du plugin affichée. La méthode addItem permet d'ajouter des entrées qui sont un couple de clef et de description affichée.

Lors de l'appel du filtre, il est possible de récuperer la valeur en utilisant un code du type :

String convolve = getStringValue("convolve"); //clef convolve
		if(convolve.equals("laplace")) //clef laplace
			//code métier

III-A-2. CheckBoxParameter


public MonFiltre() {
  addParameter(new CheckBoxParameter("border", "Border Extender", false));
}
De la même manière, il y a une clef et une description puis une valeur par défaut.

On récupère les données de la même manière.
					
if(getBooleanValue("border"))				
  //traitement

III-A-3. IntTextFieldParameter

					
addParameter(new IntTextFieldParameter("window", "Half-Window size", 1));
La valeur 1 correspond à la valeur par défaut. Le paramètre dispose également des méthodes : setMinValue et setMaxValue pour indiquer des valeurs limites.
			
//pour récuperer la valeur		
int window = getIntValue("window");

III-A-4. DoubleTextFieldParameter

Ce paramètre s'utilise de la même manière mais avec des double. On récupère la valeur via la méthode : getDoubleValue


III-A-5. DoubleSliderParameter

					
addParameter(new DoubleSliderParameter("sigma", "Sigma", 0.0, 50.0, 0.1, 0.0));
0.0 et 50.0 correspondent aux bornes. 0.1 correspond au pas et 0.0 correspond à la valeur par défaut.
					
double sigma = getDoubleValue("sigma");

III-A-6. IntSliderParameter

					
addParameter(new IntSliderParameter("sigma", "Sigma", 0, 50, 20));
De la même manière, 0-50 correspondent aux bornes et 20 à la valeur par défaut. Si aucune valeur par défaut n'est indiquée, c'est la valeur (max+min)/2 qui sera sélectionnée.


III-A-7. RadioButtonGroupParameter

	
RadioButtonGroupParameter p = new RadioButtonGroupParameter("radio", "Sélection canal à griser");
p.addItem("all", "Aucun");
p.addItem("red", "Rouge");
p.addItem("green", "Vert");
p.addItem("blue", "Bleu");
addParameter(p);				
Ce paramètre fonctionne de la même manière que le combobox.

String color = getStringValue("radio");
if(color.equals("red"))
 //...	

III-A-8. ImageParameter


addParameter(new ImageParameter("image", "Sélectionner une image");	
Un combobox montrera l'ensemble des images actuellement ouverte dans l'application.

BufferedImage imageChoisie = getImageValue("image");

III-A-9. ColorParameter


addParameter(new ColorParameter("color", "Couleur Masque", COLOR.BLACK);	
Permet de sélectionner une couleur en cliquant sur le rectangle coloré.

Color color = getColorValue("color");

III-A-10. FileParameter


addParameter(new FileParameter("fichier", "Nom du fichier", {"jpg", "png"}, Mode.FILES);	
addParameter(new FileParameter("folder", "Nom du dossier",Mode.FOLDERS);	
Permet de sélectionner un fichier ou un dossier. Le Mode peut être FOLDERS (dossier), FILES (fichiers), FILES_AND_FOLDERS (fichiers et dossiers). Pour les fichiers, il est possible de spécifier un format.

File file = getFileValue("fichier");

III-B. GenericAppImagePlugin

Cette classe abstraite permet de définir des plugins généraux et est en général suffisant pour l'ensemble des filtres.

La classe contient une méthode abstraite à redéfinir : public abstract BufferedImage filter() throws Exception;

La méthode doit retourner l'image de sortie filtrée. La méthode ne prend pas en paramètre d'image d'entrée car elle est toujours identique à chaque appel de filter.

La méthode BufferedImage getInputImage() permet de récuperer n'importe quand l'image d'entrée. Il faut savoir que la méthode "filter" peut être appelé plusieurs fois (notamment à chaque modification des entrées) et la méthode getInputImage ne retourne non pas l'image courante mais l'image qu'il y avait au lancement de la boite de dialogue (ce qui est tout à fait normal).

La méthode setPluginName(String s) permet de définir le nom affiché dans la boite de dialogue. Si le nom n'est pas défini, c'est le nom défini dans l'annotation @PluginInfo qui est pris en premier.

La méthode setRefresheable(boolean) permet d'indiquer que la méthode "filter" doit être automatiquement appelée à chaque modification d'une entrée (sans validation par le bouton Appliquer). Si setRefresheable est mis à false, alors un bouton Appliquer apparaît et la méthode filter n'est appelée que s'il y a un clic sur le bouton.

La méthode setLongProcessing permet d'indiquer que le traitement va être long. Cela affiche une petite barre de progression pendant le calcul.

La méthode setCacheable permet de mettre en cache le résultat d'un filtrage pour un ensemble de paramètres données. Cette méthode peut être utilisée si le nombre de paramètres différents que risque de saisir l'utilisateur est faible (par exemple pour le choix entre 5 types de convolutions) et si les calculs sont longs. Si la méthode "filter" est non déterministe (au sens qu'avec les mêmes paramètres en entrées, l'image de sortie ne sera pas forcement identique comme pour le bruit gaussien), utiliser ce setter n'a pas de sens.

La méthode isReinitializable permet d'ajouter un bouton "Réinitialiser" qui réinitialise les paramètres.

La méthode setup est appelé jusqu'à après l'instanciation. Cela permet de créer des caches sur l'image d'entrée (voir exemple HSL). Il est possible de la redéfinir.

Il est possible d'implémentation Previewable pour permettre de voir une image de preview. Il est dans ce cas nécessaire de définir la méthode : previewFilter(scaleFactor)


III-B-1. Quelques exemples


III-B-1-a. Niveau de gris

Pour choisir un niveau de gris. La méthode getLabel permettait uniquement d'internationaliser le texte.
 
@PluginInfo(name="Gray Filter", "Couleurs")
public class GrayFilter extends GenericAppImagePlugin {
	String getLabel(String key) {
		return MillieGUIProperties.getLabel(key);
	}
	
	public GrayFilter() {
		setPluginName(getLabel("gray.title"));
		setRefreshable(true);
		setCacheable(true);
		
		RadioButtonGroupParameter p = new RadioButtonGroupParameter("radio", getLabel("gray.title"));
		p.addItem("all", getLabel("gray.all"));
		p.addItem("red", getLabel("gray.red"));
		p.addItem("green", getLabel("gray.green"));
		p.addItem("blue", getLabel("gray.blue"));
		addParameter(p);
	}

	@Override
	public BufferedImage filter() throws Exception {
		
		BufferedImage output = new BufferedImage(getInputImage().getWidth(), getInputImage().getHeight(), getInputImage().getType());
		
		String color = getStringValue("radio");
		
		for(int y=0; y<output.getHeight(); y++)
			for(int x=0; x<output.getWidth(); x++) {
				int rgb = getInputImage().getRGB(x, y);
				int rgbOut = 0;
				
				if(color.equals("red"))
					rgbOut = (rgb>>16)&0xFF;
				if(color.equals("green"))
					rgbOut = (rgb>>8)&0xFF;
				if(color.equals("blue"))
					rgbOut = (rgb>>0)&0xFF;
				if(color.equals("all"))
					rgbOut = (int)((0.30 * ((rgb >> 16) & 0xff) + 
								0.59 * ((rgb >> 8) & 0xff) + 
								0.11 * (rgb & 0xff)));
				rgbOut =  (rgbOut << 16) + (rgbOut << 8) + (rgbOut << 0);
				
				output.setRGB(x, y, rgbOut);
			}
		
		return output;
	}

}

III-B-1-b. Image HSL

 
@PluginInfo(name="HSL Filter", "Couleurs")
public class HSLFilter extends GenericAppImagePlugin {
	private String getLabel(String key) {
		return MillieGUIProperties.getLabel(key);
	}
	
	BufferedImage output;
	/**
	 * On ajoute des caches qui contient les couleurs HSL de l'image d'entrée pour éviter de recalculer à chaque fois
	 * les couleurs HSL
	 */
	double[][] cacheH;
	double[][] cacheS;
	double[][] cacheL;
	
	
	public HSLFilter() {
		setRefreshable(true);
		setReinitializable(true);
		setPluginName(getLabel("hsl.title"));
		
		ComboBoxParameter choice = new ComboBoxParameter("type", getLabel("hsl.visualisation"));
		choice.addItem("all", getLabel("hsl.all"));
		choice.addItem("l", getLabel("hsl.l"));
		choice.addItem("s", getLabel("hsl.s"));
		choice.addItem("h", getLabel("hsl.h"));
		
		addParameter(choice);
		addParameter(new IntSliderParameter("s", getLabel("hsl.s"), -100,100,0));
		addParameter(new IntSliderParameter("l", getLabel("hsl.l"), -100,100,0));
		addParameter(new IntSliderParameter("h", getLabel("hsl.h"), -180,180,0));
	}
	
	public void setup() {
		output = new BufferedImage(getInputImage().getWidth(),
				 getInputImage().getHeight(),
				 getInputImage().getType());
		cacheH = new double[output.getWidth()][ output.getHeight()];
		cacheS = new double[output.getWidth()][ output.getHeight()];
		cacheL = new double[output.getWidth()][ output.getHeight()];
		
		double[] hsl = new double[3];
		for(int j=0; j<output.getHeight(); j++)
			for(int i=0; i<output.getWidth();i++) {
				int rgb = getInputImage().getRGB(i, j);
				
				ColorUtils.RGBToHSL(rgb, hsl);
				cacheH[i][j] = hsl[0];
				cacheS[i][j] = hsl[1];
				cacheL[i][j] = hsl[2];
			}
	}
	

	
	@Override
	public BufferedImage filter() throws Exception {
		int h = getIntValue("h");
		int s = getIntValue("s")/5; //change un peu les valeurs pour ne pas trop modifier d'un coup l'image.
		int l = getIntValue("l")/2;
		
		String type = getStringValue("type");
		double sMaxFactor = 5;
		double lMaxFactor = 1;
		double[] hsl = new double[3];
		
		for(int j=0; j<output.getHeight(); j++)
			for(int i=0; i<output.getWidth();i++) {
				
				hsl[0] = cacheH[i][j]; //on récupère du cache les valeurs HSL
				hsl[1] = cacheS[i][j];
				hsl[2] = cacheL[i][j];
				
				hsl[0] = ((hsl[0] + h)%360);
				
				//on recalcule les valeurs HSL
				if(s>=0)
					hsl[1]*= (sMaxFactor*(s/100.0) +1); 
				else
					hsl[1]/= (sMaxFactor*(-s/100.0)+1);
				
				if(l>=0)
					hsl[2] *= lMaxFactor*(l/100.0) +1;
				else
					hsl[2] /= (lMaxFactor*(-l/100.0) +1);
				
				
				hsl[1]  = between01(hsl[1] );
				hsl[2]  = between01(hsl[2] );
				
				int hslColor = 0;
				
				//suivant le type de visualisation, on écrit la couleur que l'on souhaite
				if(type.equals("all"))
					hslColor = ColorUtils.HSLToRGB(hsl);
				if(type.equals("l"))
					hslColor = ColorUtils.getGrayColor((int) (hsl[2]*255.0)); 
				if(type.equals("s"))
					hslColor = ColorUtils.getGrayColor((int) (hsl[1]*255.0)); 
				if(type.equals("h"))
					hslColor = ColorUtils.HSLToRGB(new double[]{hsl[0], 0.5, 0.5}); //ColorUtils.getGrayColor((int) (hsl[0]*255.0/360.0)); 
				
				
				output.setRGB(i, j, hslColor);
			}
		
		return output;
	}
	
	double between01(double t) {
		if(t<0) return 0;
		if(t>1) return 1;
		return t;
	}

}

III-B-1-c. Filtre UnNoise

Exemple pour le plugin utiliser la bibliothèque Millie.
 
@PluginInfo(name="UnNoise", "Restauration")
public class UnNoisePlugin extends GenericAppPluginImage {
	
	public UnNoisePlugin() {
		setPluginName("UnNoise Filter");
		setLongProcessing(true);
		
		ComboBoxParameter p = new ComboBoxParameter("estimator", "Type d'estimation");
		p.addItem("road", "Road");
		p.addItem("variance", "Variance");
		
		addParameter(p);
		addParameter(new IntSliderParameter("aperture", "Ouverture fenêtre", 1, 20, 2));
		addParameter(new DoubleSliderParameter("variance", "Facteur variance", 0.0, 10.0, 0.1, 1.0));
		addParameter(new IntSliderParameter("it", "Itérations", 1,30, 5));
		
	}
	
	@Override
	public BufferedImage filter() throws Exception {
		String estimator = getStringValue("estimator");
		int estimatorI;
		if(estimator.equals("road"))
			estimatorI = UnNoiseOperator.ROAD;
		else 
			estimatorI = UnNoiseOperator.VARIANCE;
		
		int aperture = getIntValue("aperture");
		double variance = getDoubleValue("variance");
		int it = getIntValue("it");
		
		UnNoiseOperator op = new UnNoiseOperator(estimatorI, aperture, variance, it);
			
		Image input = ImageConverter.convert(getInputImage());
		Image out = new Image(input.getNumComponents());
		op.compute(out, input);
		
		return ImageConverter.convert(out);
	}

}
	

III-C. AreaAppImagePlugin

Ce type de filtre permet d'appliquer une opération sur l'image lorsque la souris effectue un clic ou un drag sur l'image (exemple : Flou local).

Ce filtre dispose de la méthode addParameter qui permet d'ajouter des paramètres comme précédemment.

La méthode public void filter(BufferedImage input) throws Exception permet de filtrer l'image d'entrée. Il est nécessaire d'effectuer directement les calculs sur l'image d'entrée (en effet, cela permet d'éviter de copier l'image à chaque clic ou drag).

Les méthodes getX et getY permettent de savoir où a eu lieu le clic (ou le drag).

La méthode addRayonParameter(String description, int min, int max); permet d'indiquer dans le constructeur que l'opérateur utilisera les pixels autour des points getX et getY d'un rayon choisi par l'utilisateur entre min et max. Cela aura également pour conséquence d'afficher un cercle rouge.

La méthode getRayonParameter(); permet de récuperer la valeur du rayon.


III-C-1. Exemples


III-C-1-a. Flou local

 
	@PluginInfo(name="Flou Local", category="Flou")
public class LocalBlurPlugin extends AreaAppImagePlugin {
    Kernel kernel;
    
	public LocalConvolvePlugin() {
		kernel = PredefinedKernel.WEAK_BLUR;
		addRayonParameter("Rayon", 1, 50);
	}
	
	double square(double a) {
		return a*a;
	}
	

	@Override
	public void filter(BufferedImage input) throws Exception {
		int x = getX();
		int y = getY();
		int rayon = getRayonParameter();
		
		int leftpadding = kernel.getLeftPadding();
		int rightpadding = kernel.getRightPadding();
		int toppadding = kernel.getTopPadding();
		int bottompadding = kernel.getBottomPadding();
		
		int[][] colors = new int[2*rayon+1][2*rayon+1];
		
		for(int dx=x-rayon; dx<x+rayon; dx++)
			for(int dy=y-rayon; dy<y+rayon; dy++) {
				//hors du champ
				if(dx<leftpadding || dx>= input.getWidth() - rightpadding|| dy<toppadding || dy>=input.getHeight()-bottompadding) //si hors des bords, on continue
					continue;
				if(square(dx-x)+square(dy-y)>square(rayon)) //hors du cercle
					continue;
				double r = 0;
				double g = 0;
				double b = 0;
				
				//calcul de la convolution dans le point
				for (int kj = 0; kj < kernel.getHeight(); kj++)
					for (int ki = 0; ki < kernel.getWidth(); ki++) {
						int currentX = dx + ki - kernel.getXOrigin();
						int currentY = dy + kj	- kernel.getYOrigin();
						int rgb = input.getRGB(currentX, currentY);
						
						r += kernel.getElement(ki, kj) * ((rgb>>16)&0xFF);
						g += kernel.getElement(ki, kj) * ((rgb>>8)&0xFF);
						b += kernel.getElement(ki, kj) * ((rgb>>0)&0xFF);
					}
				int dR = (int) r;
				int dG = (int) g;
				int dB = (int) b;
				if(dR>255) dR=255; if(dR<0) dR=0;
				if(dG>255) dG=255; if(dG<0) dG=0;
				if(dB>255) dB=255; if(dB<0) dB=0;
				
				int out = (dR<<16)+(dG<<8)+dB;
				colors[dx-x+rayon][dy-y+rayon] = out;
				
			}
		//on remet les couleurs du buffer dans l'image
		for(int dx=x-rayon; dx<x+rayon; dx++)
			for(int dy=y-rayon; dy<y+rayon; dy++) {
				if(dx<leftpadding || dx>= input.getWidth() - rightpadding|| dy<toppadding || dy>=input.getHeight()-rightpadding) //si hors des bords, on continue
					continue;
				if(square(dx-x)+square(dy-y)>square(rayon)) //hors du cercle
					continue;
				input.setRGB(dx, dy, colors[dx-x+rayon][dy-y+rayon]);
			}
	}

}

	

III-D. RectangularAppImagePlugin

Cet opérateur permet d'effectuer un filtrage dans un rectangle choisi par l'utilisateur.

La méthode abstraite à redéfinir : public BufferedImage filter() throws Exception fonctionne de la même façon que pour le GenericPluginFilter.

Les méthodes getStartX(), getStartY(), getEndX() et getEndY() permettent de savoir les quatres coins du rectangle sélectionnés.

Il est également possible d'y ajouter des paramètres.


III-D-1. Exemples


III-D-1-a. Rognage

 					
@PluginInfo(name="Crop", category="crop")
public class CropFilter extends RectangularAppImagePlugin {
	public CropFilter() {
		setPluginName(MillieGUIProperties.getLabel("crop.title"));
	}

	@Override
	public BufferedImage filter() throws Exception {
		BufferedImage input = getInputImage();
		int newWidth = getEndX()-getStartX();
		int newHeight = getEndY()-getStartY();
		
		BufferedImage output = new BufferedImage(newWidth, newHeight, input.getType());
		Graphics2D g = output.createGraphics();
		g.drawImage(input, 0, 0, newWidth, newHeight, getStartX(), getStartY(), getEndX(), getEndY(), null);		
		g.dispose();
		
		return output;
		
	}

III-E. NoParameterAppImagePlugin

Cette classe correspond aux filtres qui n'ont besoin que de l'image pour calculer en une seule fois l'image de sortie (exemple : Dilatation).

Lorsque l'on clique sur le filtre, aucune boite de dialogue n'apparait et le filtre est directement appelé.
 			
    @PluginInfo(name="Dilatation", cateogyr="Morphologique")		
	public class DilatePlugin extends NoParameterAppImagePlugin {

	@Override
	public BufferedImage filter(BufferedImage input) throws Exception {
		Image in = ImageConverter.convert(input);
		Image out = ImageConverter.convert(getInputImage());
		DilateOperator op = new DilateOperator(new BorderExtenderCopy());
	
		op.compute(out, in);
		
		return ImageConverter.convert(out);
		
	}
}

IV. Fonctionnalités avancées

Cette section décrit le système apparu à partir de la version 2.0.0


IV-A. AppPlugin

La classe AppPlugin permet de définir des plugins qui s'intégreront dans la barre de menu de l'application et non de l'image.

Les méthodes à redéfinir sont les suivantes :

  • prepare() : est appelé une seule fois après l'initialisation du plugin par le controleur (notamment en ce qui concerne les classes d'actions)
  • execute() : est appelé éventuellement plusieurs fois (correspond à l'appuie sur le bouton Apply)
  • finish() : est appelé à la fini si tout s'est bien passé
  • cancel() : est appelé s'il y a eu une annulation demandée par l'utilisateur
Il existe les méthodes suivantes :

  • addParameter : Permet d'ajouter des paramètres comme dans les autres systèmes de plugins
  • setWithMultipleExecution : Permet de savoir si plusieurs appels de la méthode execute sont permis, cela correspond à l'ajout du bouton Apply
  • addParameterChangeListener : Permet d'ajouter un listener pour être notifié du changement d'un paramètre
  • notifyProgress : Permet de notifier de l'avancement de l'exécution d'un traitement
  • setLongProcessing : Permet d'indiquer que les traitements seront long
  • setReinitializable : Permet d'indiquer sur les paramètres peuvent être reinitialisé
  • getMessageManager : Permet de récuperer un MessageManager qui permet de communiquer des messages d'info/warning/error avec l'application
  • getAppActions : Permet de récuper la classe d'action
La classe d'action contient les méthodes suivantes :

  • openImage(File file) : Permet d'ouvrir un fichier
  • openImage(ImageBean) : Permet d'ouvrir directement un bean chargé
  • newImage(width, height, background) : Permet de faire une nouvelle image vide
  • importFromToolkit() : Permet d'ouvrir l'image venant du presse papiers
  • close() : Ferme l'application
  • execute(commandClass, parameters) : Permet d'exécuter une AppCommand
Il est nécessaire d'ajouter l'annotation PluginInfo.

Exemple :
 			
@PluginInfo(name="Automate sur dossier", category="Automation")
public class AutomateFolderPlugin extends AppPlugin {
	private Map<String, Automate> caches = new HashMap<String, Automate>();
	static private Logger logger = LoggerManager.getLogger();
	
	public AutomateFolderPlugin() {
		addParameter(new FileParameter("folder", "Dossier", Mode.FOLDERS));
		addParameter(new FileParameter("output", "Dossier de sortie", Mode.FOLDERS));
		
		setLongProcessing(true);
		List<Automate> automates = AutomateManager.getInstance().listAll();
		if(automates.size()==0) {
			throw new IllegalStateException("Aucun automate enregistré");
		}
		
		ComboBoxEntry[] entries = new ComboBoxEntry[automates.size()];
		int pos = 0;
		for(Automate automate : automates) {
			caches.put(automate.getName(), automate);
			ComboBoxEntry entry = new ComboBoxEntry(automate.getName(), automate.getName());
			entries[pos] = entry;
			pos++;
		}
		
		addParameter(new ComboBoxParameter("script", "Choix du script", entries));
		
		setWithMultipleExecution(true);
	}
	
	@Override
	public void cancel() throws Exception {
	}

	@Override
	public void execute() throws Exception {
		File folder = getFileValue("folder");
		File output = getFileValue("output");
		String script = getStringValue("script");
		
		if(folder.isFile()&&!output.isFile() || !folder.isFile() && output.isFile()) {
			throw new IllegalArgumentException("Dossier d'entrée et de sortie pas du même type");
		}
		
		File[] files = null;
		if(folder.isFile()) {
			files = new File[]{folder};
		}
		else {
			files = folder.listFiles();
		}
		int progress = (int) (100.f/files.length);
		notifyProgress(progress);
		
		for(File f : files) {
			logger.info("Treat file " + f);
			BufferedImage input = javax.imageio.ImageIO.read(f);
			ImageBean imageBean = new ImageBean();
			imageBean.setBufferedImage(input);
			imageBean.setFile(f);
			imageBean.setFileName(f.getName());
			
			Automate automate = caches.get(script);
			
			FakeAppImageActions actions = new FakeAppImageActions();
			
			actions.setImageBean(imageBean);
			
			automate.setAppImageActions(actions);
			automate.setMessageManager(getMessageManager());
			automate.setAppImageEventsManager(new FakeAppImageEventsManager());
			
			automate.execute();
			
			BufferedImage imoutput = imageBean.getBufferedImage();
			if(output.isFile()) {
				ImageIO.write(imoutput, MillieUtils.getFormat(output.getName()), output);
			}
			else {
				String[] tokens = f.getName().split("\\.");
				if(tokens.length!=2)
					throw new IllegalArgumentException("Fichier avec un point");
				
				File file = new File(output.getAbsolutePath() + "/" + tokens[0] + "_t."+ tokens[1]);
				ImageIO.write(imoutput, tokens[1], file);
			}
			
			progress+= (int) (100.f/files.length);
			notifyProgress(progress);
		}
	}

	@Override
	public void finish() throws Exception {
	}
}

IV-B. AppImagePlugin

Cette classe est héritée par les 3 types de plugins standards : GenericAppImagePlugin, AreaAppImagePlugin et RectangularAppImagePlugin.

Les méthodes à redéfinir sont les suivantes :

  • prepare() : est appelé une seule fois après l'initialisation du plugin par le controleur (notamment en ce qui concerne les classes d'actions)
  • execute() : est appelé éventuellement plusieurs fois (correspond à l'appuie sur le bouton Apply)
  • finish() : est appelé à la fini si tout s'est bien passé
  • cancel() : est appelé s'il y a eu une annulation demandée par l'utilisateur
Il existe les méthodes suivantes :

  • addParameter : Permet d'ajouter des paramètres comme dans les autres systèmes de plugins
  • setWithMultipleExecution : Permet de savoir si plusieurs appels de la méthode execute sont permis, cela correspond à l'ajout du bouton Apply
  • addParameterChangeListener : Permet d'ajouter un listener pour être notifié du changement d'un paramètre
  • notifyProgress : Permet de notifier de l'avancement de l'exécution d'un traitement
  • setLongProcessing : Permet d'indiquer que les traitements seront long
  • setReinitializable : Permet d'indiquer sur les paramètres peuvent être reinitialisé
  • getMessageManager : Permet de récuperer un MessageManager qui permet de communiquer des messages d'info/warning/error avec l'application
  • getAppImageActions : Permet de récuper la classe d'action
  • getAppImageEventsManager() : Permet de récuperer l'events manager
Concernant les paramètres, il existe les possibilités suivantes : addPreviewWindow pour ajouté une zone pour faire une preview (cela pourrait être pour afficher une LookupTable). Il existe le paramètre AreaParameter qui correspond au clic d'une zone sur une image. Il existe également le paramètre RectangularParameter qui correspond à la sélection d'une zone sur une image.

La classe d'action contient les méthodes suivantes :

  • save() : Force la sauvegarde d'une image
  • close() : Ferme l'image
  • update(image) : Met à jour l'image (l'image est enregistré sur la pile des undo/redo)
  • update(image, save) : Met à jour l'image, save permet de savoir si l'image doit être enregistré sur la pile des undo/redo
  • redo() : Annulation du retour en arrière
  • undo() : Retour en arrière
  • getImageBean() : Récuperation de l'image courante
  • show() : Repositionne l'image au premier plan. N'a de sens qu'avec Swing
  • execute() : Exécution d'une commande
La classe d'events manager contient les méthodes suivantes :

  • notifyImageUpdate : Notification d'une mise à jour d'image
  • notifyImageClose : Notification d'une fermeture d'image
  • notifyImageClosing : Notification d'une demande de fermeture d'image
  • notifyImageSave : Notification d'une sauvegarde d'image
  • notifyImageSaving : Notification d'une demande de sauvegarde d'image
  • addAppImageCommandListener : Ajoute d'un listener qui permet d'être notifié d'actions sur les commandes (création, terminaison)
  • addAppImageListener : Permet d'être notifié des MAJ/update/Save d'une image
  • addUserInteractionListener : Permet d'être notifié d'une intéraction utilisateur sur l'image
Il est nécessaire d'ajouter l'annotation PluginInfo.


IV-C. Automates

Il existe désormais la possibilité d'enregistrer et d'automatiser une séquence de plugins. Pour cela, il est nécessaire dans la classe de plugin d'hériter de l'interface Automatable qui contient une méthode : Automate getAutomate(). Cette méthode est déjà définie dans la classe AppImagePlugin. Il faut faire attention et ne pas implémenter systèmatiquement cette interface, car la réexécution du filtre peut ne pas avoir de sens (typiquement si un paramètre est un fichier, s'il correspond à une URL ou à une image).


IV-D. LifecyclePlugin

Il est possible de définir une classe qui sera exécutée au chargement et au déchargement des plugins. Il est nécessaire d'hériter de l'interface LifecyclePlugin et d'indiquer l'annotation LifecycleInfo.

  • start() : Appelé au premier chargement du plugin
  • stop() : Appelé à l'arrêt complet de l'application
  • restart() : Appelé en cas de rechargement des plugins
Cette classe peut être utilisée pour pluger son propre système de plugins.



Valid XHTML 1.0 TransitionalValid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Florent HUMBERT. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.