草庐IT

java - 平滑锯齿状路径

coder 2023-08-30 原文

我参与了话题 Image/Graphic into a Shape前几天,他尝试通过向 Area 迭代添加 Rectangle 来获取图像的轮廓。那很慢。

此示例改为构建 GeneralPath 并从 GP 创建 Area。快得多。

左上角的图像是“源图像”。右边的两个是处理轮廓的各个阶段。它们都围绕圆圈和沿着三角形的斜边都有锯齿状的边缘。

我想要获得一种去除或减少锯齿状的形状。

在 ASCII 艺术中。

案例一:

  1234
1 **
2 **
3 ***
4 ***
5 ****
6 ****

角落在:

  • (2,3) 内角
  • (3,3)
  • (3,5) 内角
  • (4,5)

案例二:

  1234
1 ****
2 ****
3 **
4 **
5 ****
6 ****

角落在:

  • (4,2)
  • (2,2) 内角
  • (2,5) 内角
  • (4,5)

假设我们的路径具有显示的形状和列出的点,我想删除第一组的“内角”点,同时保留“一对”内角(咬出图像) 第二个。


  • 有人可以建议一些巧妙的内置方法来完成这项繁重的工作吗?
  • 否则,识别内角位置和性质(成对/单个)的好方法是什么? (我猜我可以得到一个 PathIterator 并构建一个新的 GeneralPath 删除奇异的内角 - 如果我能弄清楚如何识别它们!)。

下面是要玩的代码:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;

/* Gain the outline of an image for further processing. */
class ImageOutline {

    private BufferedImage image;

    private TwoToneImageFilter twoToneFilter;
    private BufferedImage imageTwoTone;
    private JLabel labelTwoTone;

    private BufferedImage imageOutline;
    private Area areaOutline = null;
    private JLabel labelOutline;

    private JLabel targetColor;
    private JSlider tolerance;

    private JProgressBar progress;
    private SwingWorker sw;

    public ImageOutline(BufferedImage image) {
        this.image = image;
        imageTwoTone = new BufferedImage(
            image.getWidth(),
            image.getHeight(),
            BufferedImage.TYPE_INT_RGB);
    }

    public void drawOutline() {
        if (areaOutline!=null) {
            Graphics2D g = imageOutline.createGraphics();
            g.setColor(Color.WHITE);
            g.fillRect(0,0,imageOutline.getWidth(),imageOutline.getHeight());

            g.setColor(Color.RED);
            g.setClip(areaOutline);
            g.fillRect(0,0,imageOutline.getWidth(),imageOutline.getHeight());
            g.setColor(Color.BLACK);
            g.setClip(null);
            g.draw(areaOutline);

            g.dispose();
        }
    }

    public Area getOutline(Color target, BufferedImage bi) {
        // construct the GeneralPath
        GeneralPath gp = new GeneralPath();

        boolean cont = false;
        int targetRGB = target.getRGB();
        for (int xx=0; xx<bi.getWidth(); xx++) {
            for (int yy=0; yy<bi.getHeight(); yy++) {
                if (bi.getRGB(xx,yy)==targetRGB) {
                    if (cont) {
                        gp.lineTo(xx,yy);
                        gp.lineTo(xx,yy+1);
                        gp.lineTo(xx+1,yy+1);
                        gp.lineTo(xx+1,yy);
                        gp.lineTo(xx,yy);
                    } else {
                        gp.moveTo(xx,yy);
                    }
                    cont = true;
                } else {
                    cont = false;
                }
            }
            cont = false;
        }
        gp.closePath();

        // construct the Area from the GP & return it
        return new Area(gp);
    }

    public JPanel getGui() {
        JPanel images = new JPanel(new GridLayout(2,2,2,2));
        JPanel  gui = new JPanel(new BorderLayout(3,3));

        JPanel originalImage =  new JPanel(new BorderLayout(2,2));
        final JLabel originalLabel = new JLabel(new ImageIcon(image));
        targetColor = new JLabel("Target Color");
        targetColor.setForeground(Color.RED);
        targetColor.setBackground(Color.WHITE);
        targetColor.setBorder(new LineBorder(Color.BLACK));
        targetColor.setOpaque(true);

        JPanel controls = new JPanel(new BorderLayout());
        controls.add(targetColor, BorderLayout.WEST);
        originalLabel.addMouseListener( new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent me) {
                originalLabel.setCursor(
                    Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
            }

            @Override
            public void mouseExited(MouseEvent me) {
                originalLabel.setCursor(Cursor.getDefaultCursor());
            }

            @Override
            public void mouseClicked(MouseEvent me) {
                int x = me.getX();
                int y = me.getY();

                Color c = new Color( image.getRGB(x,y) );
                targetColor.setBackground( c );

                updateImages();
            }
        });
        originalImage.add(originalLabel);

        tolerance = new JSlider(
            JSlider.HORIZONTAL,
            0,
            255,
            104
            );
        tolerance.addChangeListener( new ChangeListener() {
            public void stateChanged(ChangeEvent ce) {
                updateImages();
            }
        });
        controls.add(tolerance, BorderLayout.CENTER);
        gui.add(controls,BorderLayout.NORTH);

        images.add(originalImage);

        labelTwoTone = new JLabel(new ImageIcon(imageTwoTone));

        images.add(labelTwoTone);

        images.add(new JLabel("Smoothed Outline"));

        imageOutline = new BufferedImage(
            image.getWidth(),
            image.getHeight(),
            BufferedImage.TYPE_INT_RGB
            );

        labelOutline = new JLabel(new ImageIcon(imageOutline));
        images.add(labelOutline);

        updateImages();

        progress = new JProgressBar();

        gui.add(images, BorderLayout.CENTER);
        gui.add(progress, BorderLayout.SOUTH);

        return gui;
    }

    private void updateImages() {
        if (sw!=null) {
            sw.cancel(true);
        }
        sw = new SwingWorker() {
            @Override
            public String doInBackground() {
                progress.setIndeterminate(true);
                adjustTwoToneImage();
                labelTwoTone.repaint();
                areaOutline = getOutline(Color.BLACK, imageTwoTone);

                drawOutline();

                return "";
            }

            @Override
            protected void done() {
                labelOutline.repaint();
                progress.setIndeterminate(false);
            }
        };
        sw.execute();
    }

    public void adjustTwoToneImage() {
        twoToneFilter = new TwoToneImageFilter(
            targetColor.getBackground(),
            tolerance.getValue());

        Graphics2D g = imageTwoTone.createGraphics();
        g.drawImage(image, twoToneFilter, 0, 0);

        g.dispose();
    }

    public static void main(String[] args) throws Exception {
        int size = 150;
        final BufferedImage outline =
            new BufferedImage(size,size,BufferedImage.TYPE_INT_RGB);
        Graphics2D g = outline.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0,0,size,size);
        g.setRenderingHint(
            RenderingHints.KEY_DITHERING,
            RenderingHints.VALUE_DITHER_ENABLE);
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

        Polygon p = new Polygon();
        p.addPoint(size/2, size/10);
        p.addPoint(size-10, size-10);
        p.addPoint(10, size-10);
        Area a = new Area(p);

        Rectangle r = new Rectangle(size/4, 8*size/10, size/2, 2*size/10);
        a.subtract(new Area(r));

        int radius = size/10;
        Ellipse2D.Double c = new Ellipse2D.Double(
            (size/2)-radius,
            (size/2)-radius,
            2*radius,
            2*radius
            );
        a.subtract(new Area(c));

        g.setColor(Color.BLACK);
        g.fill(a);

        ImageOutline io = new ImageOutline(outline);

        JFrame f = new JFrame("Image Outline");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(io.getGui());
        f.pack();
        f.setResizable(false);
        f.setLocationByPlatform(true);
        f.setVisible(true);
    }
}

class TwoToneImageFilter implements BufferedImageOp {

    Color target;
    int tolerance;

    TwoToneImageFilter(Color target, int tolerance) {
        this.target = target;
        this.tolerance = tolerance;
    }

    private boolean isIncluded(Color pixel) {
        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 BufferedImage createCompatibleDestImage(
        BufferedImage src,
        ColorModel destCM) {
        BufferedImage bi = new BufferedImage(
            src.getWidth(),
            src.getHeight(),
            BufferedImage.TYPE_INT_RGB);
        return bi;
    }

    public BufferedImage filter(
        BufferedImage src,
        BufferedImage dest) {

        if (dest==null) {
            dest = createCompatibleDestImage(src, null);
        }

        for (int x=0; x<src.getWidth(); x++) {
            for (int y=0; y<src.getHeight(); y++) {
                Color pixel = new Color(src.getRGB(x,y));
                Color write = Color.BLACK;
                if (isIncluded(pixel)) {
                    write = Color.WHITE;
                }
                dest.setRGB(x,y,write.getRGB());
            }
        }

        return dest;
    }

    public Rectangle2D getBounds2D(BufferedImage src) {
        return new Rectangle2D.Double(0, 0, src.getWidth(), src.getHeight());
    }

    public Point2D getPoint2D(
        Point2D srcPt,
        Point2D dstPt) {
        // no co-ord translation
        return srcPt;
    }

    public RenderingHints getRenderingHints() {
        return null;
    }
}

最佳答案

这是一个很大的课题。你可能会发现 Depixelizing Pixel Art1 by Johannes Kopf & Dani Lischinski有用:它是可读的,最新的,包括以前工作的总结,并详细解释了他们的方法。

另见 slides covering similar backgroundvideo(!) .

  1. 以下是“最近邻”与“他们的技术”文档中的一些屏幕截图。

关于java - 平滑锯齿状路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7218309/

有关java - 平滑锯齿状路径的更多相关文章

  1. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  2. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  3. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  4. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  5. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  6. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  7. ruby-on-rails - Rails - 使用/自定义 URL : '/dashboard' 指定根路径 - 2

    如何使此根路径转到:“/dashboard”而不仅仅是http://example.com?root:to=>'dashboard#index',:constraints=>lambda{|req|!req.session[:user_id].blank?} 最佳答案 您可以通过以下方式实现:root:to=>redirect('/dashboard')match'/dashboard',:to=>"dashboard#index",:constraints=>lambda{|req|!req.session[:user_id].b

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. java - 为什么 ruby​​ modulo 与 java/other lang 不同? - 2

    我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.

  10. java - Ruby 相当于 Java 的 Collections.unmodifiableList 和 Collections.unmodifiableMap - 2

    Java的Collections.unmodifiableList和Collections.unmodifiableMap在Ruby标准API中是否有等价物? 最佳答案 使用freeze应用程序接口(interface):Preventsfurthermodificationstoobj.ARuntimeErrorwillberaisedifmodificationisattempted.Thereisnowaytounfreezeafrozenobject.SeealsoObject#frozen?.Thismethodretur

随机推荐