Crop image to smallest size by removing transparent pixels in java

后端 未结 6 962
走了就别回头了
走了就别回头了 2021-01-21 15:04

I have a sprite sheet which has each image centered in a 32x32 cell. The actual images are not 32x32, but slightly smaller. What I\'d like to do is take a cell and crop the tr

相关标签:
6条回答
  • 2021-01-21 15:36

    A simple fix for code above. I used the median for RGB and fixed the min() function of x and y:

    private static BufferedImage trim(BufferedImage img) {
        int width = img.getWidth();
        int height = img.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 (isFg(img.getRGB(x, y))){
    
                    top    = Math.min(top, y);
                    bottom = Math.max(bottom, y);
    
                    left   = Math.min(left, x);
                    right  = Math.max(right, x);
    
                }
            }
        }
    
        return img.getSubimage(left, top, right - left, bottom - top);
    }
    
    private static boolean isFg(int v) {
        Color c = new Color(v);
        return(isColor((c.getRed() + c.getGreen() + c.getBlue())/2));
    }
    
    private static boolean isColor(int c) {
        return c > 0 && c < 255;
    }
    
    0 讨论(0)
  • 2021-01-21 15:37

    There's a trivial solution – to scan every pixel. The algorithm bellow has constant performance 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, y);
                    bottom = Math.max(bottom, y);
                    left   = Math.min(left, x);
                    right  = Math.max(right, x);
                }
            }
        }
        return image.getSubimage(left, top, right - left + 1, bottom - top + 1);
    }
    

    But this is much more effective:

    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);
    }
    

    This algorithm follows the idea from pepan's answer (see above) and is 2 to 4 times more effective. The difference is: it never scans any pixel twice and tries to contract search range on each stage.

    Method's performance in worst case is O(w•h–a•b)

    0 讨论(0)
  • 2021-01-21 15:38

    [Hi I tried the following. In the images file idle1.png is the image with a big transparent box while testing.png is the same image with minimum bounding box

    'BufferedImage tempImg = (ImageIO.read(new File(fileNPath)));
                    WritableRaster tempRaster = tempImg.getAlphaRaster();
                    int x1 = getX1(tempRaster);
                    int y1 = getY1(tempRaster);
                    int x2 = getX2(tempRaster);
                    int y2 = getY2(tempRaster);
                    System.out.println("x1:"+x1+" y1:"+y1+" x2:"+x2+" y2:"+y2);
                    BufferedImage temp = tempImg.getSubimage(x1, y1, x2 - x1, y2 - y1);
    
                    //for idle1.png
                    String filePath = fileChooser.getCurrentDirectory() + "\\"+"testing.png";
                    System.out.println("filePath:"+filePath);
                    ImageIO.write(temp,"png",new File(filePath));
    

    where the get functions are

    public int getY1(WritableRaster raster) { //top of character

        for (int y = 0; y < raster.getHeight(); y++) {
            for (int x = 0; x < raster.getWidth(); x++) {
                if (raster.getSample(x, y,0) != 0) {
                    if(y>0) {
                        return y - 1;
                    }else{
                        return y;
                    }
                }
            }
        }
        return 0;
    }
    
    public int getY2(WritableRaster raster) {
        //ground plane of character
    
        for (int y = raster.getHeight()-1; y > 0; y--) {
            for (int x = 0; x < raster.getWidth(); x++) {
                if (raster.getSample(x, y,0) != 0) {
                    return y + 1;
                }
            }
        }
        return 0;
    }
    
    public int getX1(WritableRaster raster) {
        //left side of character
    
        for (int x = 0; x < raster.getWidth(); x++) {
            for (int y = 0; y < raster.getHeight(); y++) {
                if (raster.getSample(x, y,0) != 0) {
                    if(x > 0){
                        return x - 1;
                    }else{
                        return x;
                    }
                }
            }
        }
        return 0;
    }
    
    public int getX2(WritableRaster raster) {
        //right side of character
    
        for (int x = raster.getWidth()-1; x > 0; x--) {
            for (int y = 0; y < raster.getHeight(); y++) {
                if (raster.getSample(x, y,0) != 0) {
                    return x + 1;
                }
            }
        }
        return 0;
    }'[Look at Idle1.png and the minimum bounding box idle = testing.png][1]
    

    Thank you for your help regards Michael.Look at Idle1.png and the minimum bounding box idle = testing.png]images here

    0 讨论(0)
  • 2021-01-21 15:54

    I think this is exactly what you should do, loop through the array of pixels, check for alpha and then discard. Although when you for example would have a star shape it will not resize the image to be smaller be aware of this.

    0 讨论(0)
  • 2021-01-21 15:59

    If your sheet already has transparent pixels, the BufferedImage returned by getSubimage() will, too. The default Graphics2D composite rule is AlphaComposite.SRC_OVER, which should suffice for drawImage().

    If the sub-images have a distinct background color, use a LookupOp with a four-component LookupTable that sets the alpha component to zero for colors that match the background.

    I'd traverse the pixel raster only as a last resort.

    Addendum: Extra transparent pixels may interfere with collision detection, etc. Cropping them will require working with a WritableRaster directly. Rather than working from the center out, I'd start with the borders, using a pair of getPixels()/setPixels() methods that can modify a row or column at a time. If a whole row or column has zero alpha, mark it for elimination when you later get a sub-image.

    0 讨论(0)
  • 2021-01-21 16:00

    This code works for me. The algorithm is simple, it iterates from left/top/right/bottom of the picture and finds the very first pixel in the column/row which is not transparent. It then remembers the new corner of the trimmed picture and finally it returns the sub image of the original image.

    There are things which could be improved.

    1. The algorithm expects, there is the alpha byte in the data. It will fail on an index out of array exception if there is not.

    2. The algorithm expects, there is at least one non-transparent pixel in the picture. It will fail if the picture is completely transparent.

      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);
      

      }

    0 讨论(0)
提交回复
热议问题