2017-07-27 3 views
1

Motivation: Mon objectif est de convertir AWT BufferedImage en SWT ImageData de la manière la plus efficace. La réponse typique à cette question est la conversion pixel par pixel de l'image entière, c'est-à-dire la complexité de O (n^2). Beaucoup plus efficace serait de pouvoir échanger toute la matrice de pixels telle qu'elle est. BufferedImage semble être très flexible pour déterminer en détail comment les couleurs et les alpha sont codés. Pour vous fournir un contexte plus large, j'ai écrit une icône SVG à la demande rasterizer, en utilisant Apache Batik, mais c'est pour l'application SWT (Eclipse). Batik rend seulement à java.awt.image.BufferedImage, mais les composants SWT nécessitent org.eclipse.swt.graphics.Image. Leurs objets raster de support: java.awt.image.Raster et org.eclipse.swt.graphics.ImageData représentent exactement la même chose, ils ne font qu'emballer autour d'un tableau 2D de valeurs d'octets représentant des pixels.Comment créer BufferedImage avec un raster alpha séparé

Si je peux faire l'un ou l'autre pour utiliser l'encodage des couleurs, voila, je peux réutiliser le backing array tel qu'il est.

Je suis assez loin, cela fonctionne:

// defined blank "canvas" for Batik Transcoder for SVG to be rasterized there 
public BufferedImage createCanvasForBatik(int w, int h) { 
    new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR); 
} 

// convert AWT's BufferedImage to SWT's ImageData to be made into SWT Image later 
public ImageData convertToSWT(BufferedImage bufferedImage) { 
    DataBuffer db = bufferedImage.getData().getDataBuffer(); 
    byte[] matrix = ((DataBufferByte) db).getData(); 

    PaletteData palette = 
      new PaletteData(0x0000FF, 0x00FF00, 0xFF0000); // BRG model 

    // the last argument contains the byte[] with the image data 
    int w = bufferedImage.getWidth(); 
    int h = bufferedImage.getHeight(); 

    ImageData swtimgdata = new ImageData(w, h, 32, palette); 
    swtimgdata.data = matrix; // ImageData has all field public!! 

    // ImageData swtimgdata = new ImageData(w, h, 32, palette, 4, matrix); ..also works 
    return swtimgdata; 
} 

tout fonctionne sauf la transparence :(

Il ressemble à ImageData nécessite (toujours?) Alpha pour être une trame séparée, voir ImageData.alphaData de la couleur raster, voir ImageData.data;.? les deux sont types byte[]

est-il possible comment faire accepter ImageDataARGB modèle est alpha mélangé avec d'autres couleurs I? doute donc je suis allé dans l'autre sens. Pour faire BufferedImage pour utiliser des tableaux séparés (aka rasters ou "bande") pour les couleurs et alpha. Les ComponentColorModel et BandedRaster semblent destinés exactement à ces choses.

Jusqu'à présent, je suis arrivé ici:

public BufferedImage createCanvasForBatik(int w, int h) { 
    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); 
    int[] nBits = {8, 8, 8, 8}; // ?? 
    ComponentColorModel colorModel = new ComponentColorModel(cs, nBits, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); 
    WritableRaster raster = Raster.createBandedRaster(
     DataBuffer.TYPE_BYTE, w, h, 4, new Point(0,0)); 
    isPremultiplied = false; 
    properties = null; 
    return new BufferedImage(colorModel, raster, isPremultiplied, properties); 
} 

Cela crée une trame séparée (bande) pour alpha, mais aussi pour chaque couleur séparément, donc je finis avec 4 bandes (4 mires) qui est à nouveau inutilisable pour SWT Image. Est-il possible de créer une bande tramée avec 2 bandes: une pour les couleurs en RGB ou BRG, et une pour l'alpha seulement?

+0

Les données sont d'image SWT BGR intercalés? Cela semble être le cas de la première partie de votre question. Si c'est le cas, cela n'a pas beaucoup de sens de créer un raster à bandes. Vous aurez besoin de créer une sorte de 'DataBuffer' et/ou' SampleModel' personnalisé, je pense, avec un stockage BGR entrelacé et un stockage alpha discret. Bien sûr, il serait plus simple si les données de l'image SWT permettaient aussi l'alpha entrelacée ... – haraldK

+0

Merci @haraldK, vous avez bien résumé ce que je demande, peut-être en de meilleurs termes: Comment créer un tel DataBuffer personnalisé et/ou SampleModel ? – Espinosa

Répondre

1

Je ne sais pas SWT en détail, mais d'après ma compréhension de la doc de l'API, le devrait fonctionner ci-dessous:

L'astuce consiste à utiliser une coutume DataBuffer mise en œuvre qui mascarades comme un tampon « bagués » , mais utilise en interne une combinaison de RGB entrelacé et de tableau alpha séparé pour le stockage. Cela fonctionne bien avec la norme BandedSampleModel. Vous perdrez toute chance d'optimisations spéciales (matérielles) qui sont normalement appliquées à BufferedImage en utilisant ce modèle, mais cela ne devrait pas avoir d'importance puisque vous utilisez SWT pour l'affichage quand même. Je suggère que vous créez d'abord votre image SWT, puis "enveloppez" la couleur et les tableaux alpha de l'image SWT dans le tampon de données personnalisé. Si vous le faites de cette façon, Batik devrait rendre directement à votre image SWT, et vous pouvez simplement jeter le BufferedImage par la suite (si ce n'est pas pratique, vous pouvez bien sûr le faire dans l'autre sens, mais vous devrez peut-être exposer les tableaux internes de la classe de tampon de données personnalisée ci-dessous, pour créer l'image SWT).

code

(parties importantes sont la classe SWTDataBuffer et createImage méthode):

public class SplitDataBufferTest { 
    /** Custom DataBuffer implementation using separate arrays for RGB and alpha.*/ 
    public static class SWTDataBuffer extends DataBuffer { 
     private final byte[] rgb; // RGB or BGR interleaved 
     private final byte[] alpha; 

     public SWTDataBuffer(byte[] rgb, byte[] alpha) { 
      super(DataBuffer.TYPE_BYTE, alpha.length, 4); // Masquerade as banded data buffer 
      if (alpha.length * 3 != rgb.length) { 
       throw new IllegalArgumentException("Bad RGB/alpha array lengths"); 
      } 
      this.rgb = rgb; 
      this.alpha = alpha; 
     } 

     @Override 
     public int getElem(int bank, int i) { 
      switch (bank) { 
       case 0: 
       case 1: 
       case 2: 
        return rgb[i * 3 + bank]; 
       case 3: 
        return alpha[i]; 
      } 
      throw new IndexOutOfBoundsException(String.format("bank %d >= number of banks, %d", bank, getNumBanks())); 
     } 

     @Override 
     public void setElem(int bank, int i, int val) { 
      switch (bank) { 
       case 0: 
       case 1: 
       case 2: 
        rgb[i * 3 + bank] = (byte) val; 
        return; 
       case 3: 
        alpha[i] = (byte) val; 
        return; 
      } 

      throw new IndexOutOfBoundsException(String.format("bank %d >= number of banks, %d", bank, getNumBanks())); 
     } 
    } 

    public static void main(String[] args) { 
     // These are given from your SWT image 
     int w = 300; 
     int h = 200; 
     byte[] rgb = new byte[w * h * 3]; 
     byte[] alpha = new byte[w * h]; 

     // Create an empty BufferedImage around the SWT image arrays 
     BufferedImage image = createImage(w, h, rgb, alpha); 

     // Just to demonstrate that it works 
     System.out.println("image: " + image); 
     paintSomething(image); 
     showIt(image); 
    } 

    private static BufferedImage createImage(int w, int h, byte[] rgb, byte[] alpha) { 
     DataBuffer buffer = new SWTDataBuffer(rgb, alpha); 
     // SampleModel sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, w, h, 4); // If SWT data is RGB, you can use simpler constructor 
     SampleModel sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, w, h, w, 
       new int[] {2, 1, 0, 3}, // Band indices for BGRA 
       new int[] {0, 0, 0, 0}); 

     WritableRaster raster = Raster.createWritableRaster(sampleModel, buffer, null); 
     ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); 
     return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null); 
    } 

    private static void showIt(final BufferedImage image) { 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       JFrame frame = new JFrame("Test"); 
       frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 

       JLabel label = new JLabel(new ImageIcon(image)); 
       label.setOpaque(true); 
       label.setBackground(Color.GRAY); 
       frame.add(label); 

       frame.pack(); 
       frame.setLocationRelativeTo(null); 
       frame.setVisible(true); 
      } 
     }); 
    } 

    private static void paintSomething(BufferedImage image) { 
     int w = image.getWidth(); 
     int h = image.getHeight(); 
     int qw = w/4; 
     int qh = h/4; 

     Graphics2D g = image.createGraphics(); 
     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 

     g.setColor(Color.ORANGE); 
     g.fillOval(0, 0, w, h); 

     g.setColor(Color.RED); 
     g.fillRect(5, 5, qw, qh); 
     g.setColor(Color.WHITE); 
     g.drawString("R", 5, 30); 

     g.setColor(Color.GREEN); 
     g.fillRect(5 + 5 + qw, 5, qw, qh); 
     g.setColor(Color.BLACK); 
     g.drawString("G", 5 + 5 + qw, 30); 

     g.setColor(Color.BLUE); 
     g.fillRect(5 + (5 + qw) * 2, 5, qw, qh); 
     g.setColor(Color.WHITE); 
     g.drawString("B", 5 + (5 + qw) * 2, 30); 

     g.dispose(); 
    } 
} 
+0

Woohaa! Ça marche. Je travaille même avec Apache Batik. Je vous remercie! Quelques petits changements, j'ai dû exposer les tableaux 'rgb' et' alpha', Du côté SWT, je dois utiliser 'bufferedImage.getRaster()' et pas 'getData()'; seulement 'getRaster()' renvoie 'SWTDataBuffer' avec' getRgb() 'et' getAlpha() 'renvoyant les tableaux. – Espinosa

+0

Pour tous ceux qui sont intéressés, voici des sources de travail: https://bitbucket.org/espinosa/org.bitbucket.espinosa.svg.icon/src/59f50b81a0ac3521d997e93a752ad351192397a3/src/org/bitbucket/espinosa/svg/icon/swt/BufferedImageToSwtImageDataConverter2. java? à = alternative buffimg2swt et https://bitbucket.org/espinosa/org.bitbucket.espinosa.svg.icon/src/59f50b81a0ac3521d997e93a752ad351192397a3/src/org/bitbucket/espinosa/svg/icon/swt/BufferedImageToSwtImageConverter2. java? at = alternative-buffimg2swt – Espinosa

+0

@Espinosa Oui, 'getData()' crée une * copie * des données dans le raster, tandis que 'getRaster()' renvoie le raster actuel. Donc vous voulez définitivement utiliser 'getRaster()' ici. – haraldK