图像/图形转换成形状

15

我想知道是否有将图像/图形转换为Shape的方法?例如,我可以将摩托车形状的轮廓转换为Shape,以便在Java中使用它吗?我知道你可以用正方形、圆角、多边形等来做这个,但是有没有一种方法来做一个自定义形状呢?


你能上传一个示例图片并告诉我们在哪里可以看到吗?最好不要太大,字节或像素大小都可以。就像在文本形状中剪切图像中所示的示例一样。 - Andrew Thompson
2
你的意思是将光栅位图转换为矢量图形吗? - Jonah
2
如果你在谷歌上搜索“栅格矢量转换算法”,它会给你一些关于如何做的指示。这并不容易。 - Alex
您可以通过指定几何路径使用 java.awt.geom.Path2D 创建自己的形状,但是您的摩托车可能最终会看起来像一只猫或水壶。 - toto2
实际上,我找到了一个形状编辑器。我认为它相当于我上面描述的内容,但至少你可以看到自己在做什么。你可能还可以通过修改程序,将背景设置为某些图像,然后在其上进行描绘。 - toto2
4个回答

16

摩托车.jpg

原始图片

motorcycle-03.png

处理后的图片

ImageOutline.java

运行此代码需要一些耐心。

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.geom.Area;
import javax.imageio.ImageIO;
import java.io.File;
import java.util.Date;
import javax.swing.*;

/* Motorcycle image courtesy of ShutterStock
http://www.shutterstock.com/pic-13585165/stock-vector-travel-motorcycle-silhouette.html */
class ImageOutline {

    public static Area getOutline(BufferedImage image, Color color, boolean include, int tolerance) {
        Area area = new Area();
        for (int x=0; x<image.getWidth(); x++) {
            for (int y=0; y<image.getHeight(); y++) {
                Color pixel = new Color(image.getRGB(x,y));
                if (include) {
                    if (isIncluded(color, pixel, tolerance)) {
                        Rectangle r = new Rectangle(x,y,1,1);
                        area.add(new Area(r));
                    }
                } else {
                    if (!isIncluded(color, pixel, tolerance)) {
                        Rectangle r = new Rectangle(x,y,1,1);
                        area.add(new Area(r));
                    }
                }
            }
        }
        return area;
    }

    public static boolean isIncluded(Color target, Color pixel, int tolerance) {
        int rT = target.getRed();
        int gT = target.getGreen();
        int bT = target.getBlue();
        int rP = pixel.getRed();
        int gP = pixel.getGreen();
        int bP = pixel.getBlue();
        return(
            (rP-tolerance<=rT) && (rT<=rP+tolerance) &&
            (gP-tolerance<=gT) && (gT<=gP+tolerance) &&
            (bP-tolerance<=bT) && (bT<=bP+tolerance) );
    }

    public static BufferedImage drawOutline(int w, int h, Area area) {
        final BufferedImage result = new BufferedImage(
            w,
            h,
            BufferedImage.TYPE_INT_RGB);
        Graphics2D g = result.createGraphics();

        g.setColor(Color.white);
        g.fillRect(0,0,w,h);

        g.setClip(area);
        g.setColor(Color.red);
        g.fillRect(0,0,w,h);

        g.setClip(null);
        g.setStroke(new BasicStroke(1));
        g.setColor(Color.blue);
        g.draw(area);

        return result;
    }

    public static BufferedImage createAndWrite(
        BufferedImage image,
        Color color,
        boolean include,
        int tolerance,
        String name)
        throws Exception {
        int w = image.getWidth();
        int h = image.getHeight();

        System.out.println("Get Area: " + new Date() + " - " + name);
        Area area = getOutline(image, color, include, tolerance);
        System.out.println("Got Area: " + new Date() + " - " + name);

        final BufferedImage result = drawOutline(w,h,area);
        displayAndWriteImage(result, name);

        return result;
    }

    public static void displayAndWriteImage(BufferedImage image, String fileName) throws Exception {
        ImageIO.write(image, "png", new File(fileName));
        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(image)));
    }

    public static void main(String[] args) throws Exception {
        final BufferedImage outline = ImageIO.read(new File("motorcycle.jpg"));
        BufferedImage crop = outline.getSubimage(17,35,420,270);
        displayAndWriteImage(crop, "motorcycle-01.png");

        BufferedImage crude = createAndWrite(crop, Color.white, false, 60, "motorcycle-02.png");

        BufferedImage combo = createAndWrite(crude, Color.red, true, 0, "motorcycle-03.png");
    }
}

+1:很棒的解决方案。你提到运行需要耐心。你知道缓慢运行的主要原因吗?如果是由于创建的区域数量,那么是否可以通过采用“扫描线”方法(其中每个矩形都是包含像素的水平线)来创建较少的矩形以进行优化? - Adamski
@Adamski,平滑崎岖路径有一个更加改进(更快)的版本可用。试一试,看看效果如何。 - Andrew Thompson

3

函数getArea_FastHack是基于Andrew Thompson的工作构建的,这对我非常有帮助。 我的实现应该更快,但也更加粗糙:

import java.awt.*;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
/**
 * CustomShape
 * based on a Class from Andrew Thompson * 
 * Source: https://dev59.com/Smw05IYBdhLWcg3wwEYS#7059497
 * @author Samuel Schneider, Andrew Thompson
 * 
 *
 */
class CustomShape {

    private BufferedImage image=null;

    /**
     * Creates an Area with PixelPerfect precision
     * @param color The color that is draws the Custom Shape
     * @param tolerance The color tolerance
     * @return Area
     */
    public Area getArea(Color color, int tolerance) {
        if(image==null) return null;
        Area area = new Area();
        for (int x=0; x<image.getWidth(); x++) {
            for (int y=0; y<image.getHeight(); y++) {
                Color pixel = new Color(image.getRGB(x,y));
                if (isIncluded(color, pixel, tolerance)) {
                    Rectangle r = new Rectangle(x,y,1,1);
                    area.add(new Area(r));
                }
            }
        }

        return area;
    }

    public Area getArea_FastHack() {
        //Assumes Black as Shape Color
        if(image==null) return null;

        Area area = new Area();
        Rectangle r;
        int y1,y2;

        for (int x=0; x<image.getWidth(); x++) {
            y1=99;
            y2=-1;
            for (int y=0; y<image.getHeight(); y++) {
                Color pixel = new Color(image.getRGB(x,y));
                //-16777216 entspricht RGB(0,0,0)
                if (pixel.getRGB()==-16777216) {
                    if(y1==99) {y1=y;y2=y;}
                    if(y>(y2+1)) {
                        r = new Rectangle(x,y1,1,y2-y1);
                        area.add(new Area(r)); 
                        y1=y;y2=y;
                    }
                    y2=y;
                }               
            }
            if((y2-y1)>=0) {
                r = new Rectangle(x,y1,1,y2-y1);
                area.add(new Area(r)); 
            }
        }

        return area;
    }

    public static boolean isIncluded(Color target, Color pixel, int tolerance) {
        int rT = target.getRed();
        int gT = target.getGreen();
        int bT = target.getBlue();
        int rP = pixel.getRed();
        int gP = pixel.getGreen();
        int bP = pixel.getBlue();
        return(
            (rP-tolerance<=rT) && (rT<=rP+tolerance) &&
            (gP-tolerance<=gT) && (gT<=gP+tolerance) &&
            (bP-tolerance<=bT) && (bT<=bP+tolerance) );
    }

    public CustomShape(String path) {
        try {
            BufferedImage image = ImageIO.read(new File(path));
            this.image = image;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

-1

这里有一种更快但不太准确的方法,适用于碰撞检测或二维物理。

    Point[] MakePoly(BufferedImage spr,int d,int angle){

//creates an outline of a transparent image, points are stored in an array
//arg0 - BufferedImage source image 
//arg1 - Int detail (lower = better)
//arg2 - Int angle threshold in degrees (will remove points with angle differences below this level; 15 is a good value)
//      making this larger will make the body faster but less accurate;


    int w= spr.getWidth(null);  int h= spr.getHeight(null);

    // increase array size from 255 if needed
    int[] vertex_x=new int[255], vertex_y=new int[255], vertex_k=new int[255]; 

    int numPoints=0, tx=0,ty=0,fy=-1,lx=0,ly=0; vertex_x[0]=0; vertex_y[0]=0; vertex_k[0]=1; 
    for (tx=0;tx<w;tx+=d)  for (ty=0;ty<h;ty+=1)       if((spr.getRGB(tx,ty)>>24) != 0x00 ) 
        {vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints++; if (fy<0) fy=ty; lx=tx; ly=ty; break;  }      
    for (ty=0;ty<h;ty+=d)  for (tx=w-1;tx>=0;tx-=1)    if((spr.getRGB(tx,ty)>>24)  != 0x00 && ty > ly)
        {vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints++; lx=tx; ly=ty; break;  }     
    for (tx=w-1;tx>=0;tx-=d)  for (ty=h-1;ty>=0;ty-=1) if((spr.getRGB(tx,ty)>>24) != 0x00 && tx < lx)
        {vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints ++; lx=tx; ly=ty; break; }     
    for (ty=h-1;ty>=0;ty-=d)  for (tx=0;tx<w;tx+=1)    if((spr.getRGB(tx,ty)>>24) != 0x00 && ty < ly && ty > fy)
        {vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints ++; lx=tx; ly=ty; break; }      
    double ang1,ang2;       for (int i=0;i<numPoints-2;i++) {
        ang1 = PointDirection(vertex_x[i],vertex_y[i], vertex_x[i+1],vertex_y[i+1]);
        ang2 = PointDirection(vertex_x[i+1],vertex_y[i+1], vertex_x[i+2],vertex_y[i+2]);
         if (Math.abs(ang1-ang2) <= angle)   vertex_k[i+1] = 0;         }
    ang1 = PointDirection(vertex_x[numPoints-2],vertex_y[numPoints-2], vertex_x[numPoints-1],vertex_y[numPoints-1]);
    ang2 = PointDirection(vertex_x[numPoints-1],vertex_y[numPoints-1], vertex_x[0],vertex_y[0]);
     if (Math.abs(ang1-ang2) <= angle)      vertex_k[numPoints-1] = 0; 
    ang1 = PointDirection(vertex_x[numPoints-1],vertex_y[numPoints-1], vertex_x[0],vertex_y[0]);
    ang2 = PointDirection(vertex_x[0],vertex_y[0], vertex_x[1],vertex_y[1]);
     if (Math.abs(ang1-ang2) <= angle)      vertex_k[0] = 0;
     int n=0;for (int i=0;i<numPoints;i++)if(vertex_k[i]==1)n++;
    Point[] poly= new Point[n]; n=0; for (int i=0;i<numPoints;i++) if (vertex_k[i]==1)
    { poly[n]=new Point(); poly[n].x=vertex_x[i]; poly[n].y=h-vertex_y[i];n++;} return poly;
}

double PointDirection(double xfrom,double yfrom,double xto,double yto){
    return  Math.atan2(yto-yfrom,xto-xfrom)*180/Math.PI ;
}

-2
使用BufferedImage绘制(在一个矩形区域形状的组合内)以获得相同的效果。

我编辑了这个以便作为一个答案,如果你认为它不是一个正确的答案,请删除它并以评论的形式发表(你有足够的声望来这样做)。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接