Multithreading et BufferedImage – JAVA – Comparaison d'image

Dans cet article nous allons voir comment réaliser une comparaison des pixels de deux BufferedImage en multithread pour accélérer la performance.
Pierre LISERONMis à jour le 2 Mai 2014

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.

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

Lexemple de la comparaison nest pas lexemple le plus percutant mais il permet de se rendre compte de lamé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 dun 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. Cest 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 quune petite partie de limage. Chaque thread sur chaque processeur aura la responsabilité de comparer quelques pixels de limage.

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 noubliant 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 dabord, 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 limage 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 chaquun 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 cest extrêment simple de faire du multithreading avec des bufferedimages car ceci revient à travailler sur des tableaux de pixels (DataBufferByte) qui savère être extrêmement performant à lusage.