Dans cet article


Offres d'emploi

Tags

Multithreading et BufferedImage – JAVA – Comparaison d’image

Multithread et bufferedImage

Dans cet article nous allons voir comment réaliser une comparaison des pixels de deux BufferedImage en multithread pour accélérer la performance.

L’utilisation du multithreading pour le traitement des images apporte une réelle amélioration des performances.

L’exemple de la comparaison n’est pas l’exemple le plus percutant mais il permet de se rendre compte de l’amélioration subtentiel que peut amener le multithreading mais aussi sa simplicité de mise en oeuvre depuis JAVA 7.

Dans cet exemple, nous allons prendre deux BuffereImage créés à partir d’un PNG (donc avec une valeur de transparence). Nous allons comparé, pixel après pixel les deux images et écrire une nouvelle image avec un couleur rouge pour chaque pixel différent entre les deux images sources.

RecursiveAction

Premièrement il faut créer une classe implémentant java.util.concurrent.RecursiveAction. C’est interface nous oblige à créer une méthode compute qui va réaliser le traitement. Cette méthode compute se comporte de la manière suivante. Si la taille tu traitement est trop gros, elle divise la charge en deux sous tâches qui seront à leur tour mutlithreadé. Si les sous tâches sont encore trop grosses alors elles seront encore divisées en deux sous tâches. (De manière récursive). En gros, on veut traiter sur un thread qu’une petite partie de l’image. Chaque thread sur chaque processeur aura la responsabilité de comparer quelques pixels de l’image.

Donc le premier if de la méthode compute sert à définir si le traitement  est trop gros pour être fait sur cette tâche. Le else du if permet de réaliser simplement une comparaison des pixels entre les deux images. En n’oubliant pas la valeur de transparence.

class CompareMultiThreadAction extends RecursiveAction {

	int mOffsetMin;
	int mOffsetMax;

	private static final long serialVersionUID = 1L;
	int splitSize;

	byte[] pixelSource;
	byte[] pixelOpening;
	int width;
	int height;

	public CompareMultiThreadAction(int pOffsetMin, int pOffsetMax,
			int pCuttingPoint, byte[] pixels, byte[] pixelsModified, int pWidth, int pHeight) {
		// Structuring element dimensions
		splitSize = pCuttingPoint;
		mOffsetMin = pOffsetMin;
		mOffsetMax = pOffsetMax;
		pixelSource = pixels;
		pixelOpening = pixelsModified;
		width = pWidth;
		height = pHeight;

	}

	@Override
	protected void compute() {

		if ((mOffsetMax - mOffsetMin) > splitSize) {
			// task is huge so divide in half

			int mid = (mOffsetMax - mOffsetMin) / 2;
			invokeAll(asList(new CompareMultiThreadAction(mOffsetMin,
					mOffsetMin + mid, splitSize, pixelSource, pixelOpening,
					pixelClosing, width, height), new CompareMultiThreadAction(
					mOffsetMin + mid + 1, mOffsetMax, splitSize, pixelSource,
					pixelOpening, width, height)));
		} else {
			// Image dimensions

			boolean lChangeOpening = false;
			// Si on a trouvé un pixel de finesse
			boolean lChangeClosing = false;
			byte lApha_ref, lblue_ref, lVert_ref, lRouge_ref, lApha_open, lblue_open, tlVert_open, lRouge_open;

			int offset = mOffsetMin;
			int y = (mOffsetMin / width);
			int x = (mOffsetMin % width);
			boolean first = true;


			for (; y <
 height; y++) {

				if (first)
					first = false;
				else
					x = 0;
				for (; x <
 width; x++, offset++) {

					lApha_ref = pixelSource[offset * 4];
					lblue_ref = pixelSource[offset * 4 + 1];
					lVert_ref = pixelSource[offset * 4 + 2];
					lRouge_ref = pixelSource[offset * 4 + 3];

					lApha_open = pixelOpening[offset * 4];
					lblue_open = pixelOpening[offset * 4 + 1];
					tlVert_open = pixelOpening[offset * 4 + 2];
					lRouge_open = pixelOpening[offset * 4 + 3];
					
					lChangeOpening = false;

					// Change in opening pixel
					if (lApha_ref != lApha_open || lblue_ref != lblue_open
							|| lVert_ref != tlVert_open
							|| lRouge_ref != lRouge_open) {
						lChangeOpening = true;
					}

					
					if (lChangeOpening) {

						pixelSource[offset * 4] = -1;
						pixelSource[offset * 4 + 1] = 0;
						pixelSource[offset * 4 + 2] = 0;
						pixelSource[offset * 4 + 3] = -1;
					}

					if (x * y >= mOffsetMax) {
						// System.out.println("process " + (x * y -
						// mOffsetMin));
						return;
					}
				}

			}

		}
	}
}

 

ForkJoinPool

Avec cette classe nous effectuons le travail, maintenant il reste à voir comment appeler la classe depuis votre application. Pour ce faire, nous allons dans un premier temps créer un ForkJoinPool sur lequel nous allons invoker notre task.

Mais d’abord, nous allons définir le cuttingPoint (ou nombre de pixel que nous souhaitons traiter sur chaque processeur). Pour cela, nous allons diviser le nombre de pixel global de l’image par le nombre de processeur disponible par la JVM. Pour trouver le nomrbe de processeurs disponibles, il suffit de faire un Runtime.getRuntime().availableProcessors().

Ainsi le traitement va se diviser jusqu’à obtenir un thread sur chaque processus de la machine qui traiteront chaqu’un le bon nombre  de pixel.

public void compareImageMulti(BufferedImage pImageSource,
			BufferedImage pImageOpening, BufferedImage pImageClosing) {
		ForkJoinPool fjpool = new ForkJoinPool(64);
		final int width = pImageSource.getWidth();
		final int height = pImageSource.getHeight();
		int cuttingPoint = (width * height)
				/ Runtime.getRuntime().availableProcessors();
		byte[] pixels = ((DataBufferByte) pImageSource.getRaster()
				.getDataBuffer()).getData();
		byte[] pixel2s = ((DataBufferByte) pImageOpening.getRaster()
				.getDataBuffer()).getData();
	
		RecursiveAction task = new CompareMultiThreadAction(0, width * height,
				cuttingPoint, pixels, pixel2s, width, height);

		fjpool.invoke(task);

		return ;

	}

 

On voit donc que c’est extrêment simple de faire du multithreading avec des bufferedimages car ceci revient à travailler sur des tableaux de pixels (DataBufferByte) qui s’avère être extrêmement performant à l’usage.

 

 

L'équipe AXOPEN

Voir aussi les articles suivants

Planisware : déterminer la version des différents composants d&rsquo;un Intranet Server
Introduction Afin d’assurer une gestion de configuration efficace d’une application sous Planisware il est nécessaire de pouvoir identifier la version de chacun des composants techniques (version du noyau, version de l’applet, etc…). Cet article ne traite pas de la gestion des objets d’environnement (ou paramétrage) Planisware. Composant Exemple Noyau – version majeure P5 SP3 Noyau – version patch (« officiels »+deltas) Maintenance Pack 5.
Lire l'article

Il n’est pas forcément évident de faire un redirect 404 de manière programmatique en JAVA.  Un des principaux cas d’utilisation est lorsqu’on utilise un url rewritting pour générer des liens vers des pages web. Il peut arriver que la page web n’existe pas ou n’existe plus mais que la redirection (par exemple avec pretty-faces) vous a déjà fait calculer une partie de la page. Il devient dès lors très compliqué d’envoyer proprement une 404 au navigateur et non pas un simple message d’erreur.
Lire l'article

Il peut être très utile d’utiliser un Bean de type Singleton pour réaliser par exemple une tâche Asynchrone d’envoi de mail. Par contre il convient de comprendre un peu comment fonctionne le Singleton pour éviter ce genre d’erreur :  JBAS014134: EJB Invocation failed on component … javax.ejb.ConcurrentAccessTimeoutException: JBAS014373 L’erreur même si elle est très explicite (ConcurrentAccessTimeoutException) se doit être un peu expliquée pour comprendre pourquoi ce problème se produit et comment le résoudre.
Lire l'article