2010-07-11 8 views
0

J'ai une feuille de sprite qui a chaque image centrée dans une cellule 32x32. Les images réelles ne sont pas 32x32, mais légèrement plus petites. Ce que je voudrais faire est de prendre une cellule et recadrer les pixels transparents afin que l'image soit aussi petite que possible.Recadrer l'image à la plus petite taille en supprimant les pixels transparents dans Java

Comment ferais-je cela en Java (JDK 6)?

Voici un exemple de la façon dont je casse actuellement la feuille de carreaux dans les cellules:

BufferedImage tilesheet = ImageIO.read(getClass().getResourceAsStream("/sheet.png"); 
for (int i = 0; i < 15; i++) { 
    Image img = tilesheet.getSubimage(i * 32, 0, 32, 32); 
    // crop here.. 
} 

Mon idée actuelle était de tester chaque pixel du centre de travail mon chemin pour voir si elle est transparente , mais je me demandais s'il y aurait une façon plus rapide/plus propre de le faire.

Répondre

2

Je pense que c'est exactement ce que vous devez faire, bouclez la matrice de pixels, vérifiez l'alpha, puis jetez-la. Bien que lorsque vous avez par exemple une forme d'étoile, il ne sera pas redimensionner l'image pour être plus petit soyez conscient de cela.

+0

Oui, je suis au courant de quelque chose comme une étoile, pas beaucoup de redimensionnement. J'espérais qu'il y avait une sorte de filtre intégré. – Ruggs

0

Si votre feuille a déjà des pixels transparents, le BufferedImage retourné par getSubimage() le fera également. La valeur par défaut Graphics2Dcomposite rule est AlphaComposite.SRC_OVER, ce qui devrait suffire pour drawImage().

Si les sous-images ont une couleur d'arrière-plan distincte, utilisez un LookupOp avec un LookupTable à quatre composants qui définit le composant alpha sur zéro pour les couleurs correspondant à l'arrière-plan.

Je traverserais le pixel raster uniquement en dernier recours.

Addendum: Les pixels extra-transparents peuvent interférer avec la détection de collision, etc. Pour les recadrer, il faudra travailler directement avec un WritableRaster. Plutôt que de travailler à partir du centre, je commencerais par les frontières, en utilisant une paire de méthodes qui peuvent modifier une ligne ou une colonne à la fois. getPixels()/setPixels() Si une ligne ou une colonne entière a zéro alpha, marquez-la pour l'éliminer lorsque vous obtenez plus tard une sous-image.

+0

Je n'ai pas de problème pour régler les pixels transparents, comme vous l'avez déjà dit. Ce que je veux faire est de couper/rogner l'image de façon à couper autant de pixels transparents environnants que possible. Mes cellules ont 32x32, mais souvent l'image qui m'intéresse n'est que 14x28. – Ruggs

3

Ce code fonctionne pour moi. L'algorithme est simple, il itère de gauche/haut/droite/bas de l'image et trouve le tout premier pixel de la colonne/ligne qui n'est pas transparent. Il se souvient alors du nouveau coin de l'image recadrée et retourne finalement l'image secondaire de l'image originale.

Il y a des choses qui pourraient être améliorées.

  1. L'algorithme attend, il y a l'octet alpha dans les données. Il échouera sur un index hors de l'exception de tableau s'il n'y a pas.

  2. L'algorithme s'attend à ce qu'il y ait au moins un pixel non transparent dans l'image. Cela échouera si l'image est complètement transparente.

    private static BufferedImage trimImage(BufferedImage img) { 
    final byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer()).getData(); 
    int width = img.getWidth(); 
    int height = img.getHeight(); 
    int x0, y0, x1, y1;      // the new corners of the trimmed image 
    int i, j;        // i - horizontal iterator; j - vertical iterator 
    leftLoop: 
    for (i = 0; i < width; i++) { 
        for (j = 0; j < height; j++) { 
         if (pixels[(j*width+i)*4] != 0) { // alpha is the very first byte and then every fourth one 
          break leftLoop; 
         } 
        } 
    } 
    x0 = i; 
    topLoop: 
    for (j = 0; j < height; j++) { 
        for (i = 0; i < width; i++) { 
         if (pixels[(j*width+i)*4] != 0) { 
          break topLoop; 
         } 
        } 
    } 
    y0 = j; 
    rightLoop: 
    for (i = width-1; i >= 0; i--) { 
        for (j = 0; j < height; j++) { 
         if (pixels[(j*width+i)*4] != 0) { 
          break rightLoop; 
         } 
        } 
    } 
    x1 = i+1; 
    bottomLoop: 
    for (j = height-1; j >= 0; j--) { 
        for (i = 0; i < width; i++) { 
         if (pixels[(j*width+i)*4] != 0) { 
          break bottomLoop; 
         } 
        } 
    } 
    y1 = j+1; 
    return img.getSubimage(x0, y0, x1-x0, y1-y0); 
    

    }

4

image on transparent background

Il y a une solution triviale - pour analyser chaque pixel. L'algorithme ci-dessous a des performances constantes O(w•h).

private static BufferedImage trimImage(BufferedImage image) { 
    int width = image.getWidth(); 
    int height = image.getHeight(); 
    int top = height/2; 
    int bottom = top; 
    int left = width/2 ; 
    int right = left; 
    for (int x = 0; x < width; x++) { 
     for (int y = 0; y < height; y++) { 
      if (image.getRGB(x, y) != 0){ 
       top = Math.min(top, x); 
       bottom = Math.max(bottom, x); 
       left = Math.min(left, x); 
       right = Math.max(right, x); 
      } 
     } 
    } 
    return image.getSubimage(left, top, right - left, bottom - top); 
} 

Mais ce qui est beaucoup plus efficace:

private static BufferedImage trimImage(BufferedImage image) { 
    WritableRaster raster = image.getAlphaRaster(); 
    int width = raster.getWidth(); 
    int height = raster.getHeight(); 
    int left = 0; 
    int top = 0; 
    int right = width - 1; 
    int bottom = height - 1; 
    int minRight = width - 1; 
    int minBottom = height - 1; 

    top: 
    for (;top < bottom; top++){ 
     for (int x = 0; x < width; x++){ 
      if (raster.getSample(x, top, 0) != 0){ 
       minRight = x; 
       minBottom = top; 
       break top; 
      } 
     } 
    } 

    left: 
    for (;left < minRight; left++){ 
     for (int y = height - 1; y > top; y--){ 
      if (raster.getSample(left, y, 0) != 0){ 
       minBottom = y; 
       break left; 
      } 
     } 
    } 

    bottom: 
    for (;bottom > minBottom; bottom--){ 
     for (int x = width - 1; x >= left; x--){ 
      if (raster.getSample(x, bottom, 0) != 0){ 
       minRight = x; 
       break bottom; 
      } 
     } 
    } 

    right: 
    for (;right > minRight; right--){ 
     for (int y = bottom; y >= top; y--){ 
      if (raster.getSample(right, y, 0) != 0){ 
       break right; 
      } 
     } 
    } 

    return image.getSubimage(left, top, right - left + 1, bottom - top + 1); 
} 

Cet algorithme suit la réponse de l'idée de Pepan (voir ci-dessus) et est 2 à 4 fois plus efficace. La différence est la suivante: il ne scanne jamais un pixel deux fois et essaie de contracter la plage de recherche sur chaque étape.

performance de la méthode dans le pire des cas est O(w•h–a•b)

+0

Cela m'a vraiment aidé. Je l'ai porté sur Android pour travailler avec un 'Bitmap' avec un fond blanc. Je posterais ici si quelqu'un est intéressé. – Thomas

+0

@Thomas s'il vous plaît poster votre solution, je voudrais vérifier. Merci – prom85