Dans cet article nous allons voir comment réaliser une comparaison des pixels de deux BufferedImage en multithread pour accélérer la performance.
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.
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;
}
}
}
}
}
}
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.
Configuration des packages et début des développements d’un projet Symfony
Youhouuuuu AXOPEN a obtenu la médaille de bronze Ecovadis ! Au-delà du côté cool d’avoir une médaille, c’est surtout une reconnaissance de notre démarche RSE mise en place il y a quelques années dans l’entreprise, et ça, ça fait plaisir. Convaincus depuis toujours qu’une entreprise n’est pas seulement là pour faire de l’argent, on a a coeur de continuer à s’améliorer sur les aspects sociaux et environnementaux de nos métiers. Aussi, on a voulu passé la notation Ecovadis, l’organisme reconnu mondialement pour l'évaluation de la RSE en entreprise ! Je vous partage ici nos résultats dans le détails, mais aussi, les coulisses de la notation Ecovadis, c’est parti :)
Quand la connectivité et l'interopérabilité des SI sont essentielles, les contrats API sont devenus un outil incontournable pour les entreprises. Ces derniers sont des documents qui définissent l'ensemble des fonctionnalités et des données qu'une API expo