调整大小后的图像质量非常低——Java

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14115950/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 15:07:44  来源:igfitidea点击:

Quality of Image after resize very low -- Java

javaimageimage-resizing

提问by dutchman191

In the script it is going from around the 300x300 mark down to 60x60. Need to improve the overall image quality as it is coming out very poorly at the moment.

在脚本中,它从大约 300x300 标记降低到 60x60。需要提高整体图像质量,因为它目前非常糟糕。

public static Boolean resizeImage(String sourceImg, String destImg, Integer Width, Integer Height, Integer whiteSpaceAmount) 
{
    BufferedImage origImage;

    try 
    {
        origImage = ImageIO.read(new File(sourceImg));
        int type = origImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : origImage.getType();
        int fHeight = Height;
        int fWidth = Width;
        int whiteSpace = Height + whiteSpaceAmount; //Formatting all to squares so don't need two whiteSpace calcs..
        double aspectRatio;

        //Work out the resized dimensions
        if (origImage.getHeight() > origImage.getWidth()) //If the pictures height is greater than the width then scale appropriately.
        {
            fHeight = Height; //Set the height to 60 as it is the biggest side.

            aspectRatio = (double)origImage.getWidth() / (double)origImage.getHeight(); //Get the aspect ratio of the picture.
            fWidth = (int)Math.round(Width * aspectRatio); //Sets the width as created via the aspect ratio.
        }
        else if (origImage.getHeight() < origImage.getWidth()) //If the pictures width is greater than the height scale appropriately.
        {
            fWidth = Width; //Set the height to 60 as it is the biggest side.

            aspectRatio = (double)origImage.getHeight() / (double)origImage.getWidth(); //Get the aspect ratio of the picture.
            fHeight = (int)Math.round(Height * aspectRatio); //Sets the height as created via the aspect ratio.
        }

        int extraHeight = whiteSpace - fHeight;
        int extraWidth = whiteSpace - fWidth;

        BufferedImage resizedImage = new BufferedImage(whiteSpace, whiteSpace, type);
        Graphics2D g = resizedImage.createGraphics();
        g.setColor(Color.white);
        g.fillRect(0, 0, whiteSpace, whiteSpace);

        g.setComposite(AlphaComposite.Src);
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g.drawImage(origImage, extraWidth/2, extraHeight/2, fWidth, fHeight, null);
        g.dispose();

        ImageIO.write(resizedImage, "jpg", new File(destImg));
    } 
    catch (IOException ex) 
    {
        return false;
    }

    return true;
}

Really just need to know if their is something I can plug in that will bump up the quality or if I need to look at something else entirely.

真的只需要知道它们是否是我可以插入的东西来提高质量,或者我是否需要完全看看其他东西。

EDIT: Picture comparison.

编辑:图片比较。

Source, just picked a random washing machine from google. http://www.essexappliances.co.uk/images/categories/washing-machine.jpg

来源,刚刚从谷歌随机挑选了一台洗衣机。 http://www.essexappliances.co.uk/images/categories/washing-machine.jpg

Washing Machine

洗衣机

The same picture converted in Photoshop to what I need it to be. http://imgur.com/78B1p

同样的图片在 Photoshop 中转换成我需要的样子。 http://imgur.com/78B1p

Good resize in Paint shop

在油漆店调整大小

What it looks like being converted like this. http://imgur.com/8WlXD

像这样转换成什么样子。 http://imgur.com/8WlXD

Bad resize

错误调整大小

采纳答案by mmgp

The issue you are seeing is actually related to the resampling filter used for downscaling. Obviously, the one used by your library is a bad one for the situation. Nearest neighbor, bilinear and bicubic are typical bad examples to be used when downscaling. I don't know the exact resampling filter Photoshop uses, but I used 3-lobed lanczos and got the following result:

您看到的问题实际上与用于缩减的重采样过滤器有关。显然,您的库使用的库不适合这种情况。最近邻、双线性和双三次是降尺度时使用的典型坏例子。我不知道 Photoshop 使用的确切重采样过滤器,但我使用了 3-lobed lanczos 并得到了以下结果:

enter image description here

在此处输入图片说明

So, to solve your problem, you need to use a smarter resampling filter.

因此,要解决您的问题,您需要使用更智能的重采样过滤器。

回答by MadProgrammer

Scaling an image down over a large range is inherently dangerous (from the point of view of quality), especially using a single step.

在大范围内缩小图像本质上是危险的(从质量的角度来看),尤其是使用单个步骤。

The recommended method is to use a divide and conquer method. Basically, you scale the image down in steps of 50% until you reach your desired size.

推荐的方法是使用分而治之的方法。基本上,您以 50% 的步长缩小图像,直到达到所需的尺寸。

So, I took the original image of 650x748 and scaled it down to fit within a 60x60 region (52x60)

因此,我拍摄了 650x748 的原始图像并将其缩小以适应 60x60 区域 (52x60)

enter image description here

在此处输入图片说明

Divide and conquer compared to one step...

分而治之,相较于一步……

enter image description hereenter image description here

在此处输入图片说明在此处输入图片说明

public class TestImageResize {

    public static void main(String[] args) {
        new TestImageResize();
    }

    public TestImageResize() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new ScalePane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class ScalePane extends JPanel {

        private BufferedImage original;
        private BufferedImage scaled;

        public ScalePane() {
            try {
                original = ImageIO.read(new File("path/to/master.jpg"));
                scaled = getScaledInstanceToFit(original, new Dimension(60, 60));
                ImageIO.write(scaled, "jpg", new File("scaled.jpg"));

                BufferedImage image = new BufferedImage(52, 60, BufferedImage.TYPE_INT_RGB);
                Graphics2D g2d = image.createGraphics();
                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.drawImage(original, 0, 0, 52, 60, this);
                g2d.dispose();

                ImageIO.write(image, "jpg", new File("test.jpg"));

            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        @Override
        public Dimension getPreferredSize() {

            Dimension size = super.getPreferredSize();
            if (original != null) {
                if (scaled != null) {
                    size.width = original.getWidth() + scaled.getWidth();
                    size.height = original.getHeight();
                } else {
                    size.width = original.getWidth();
                    size.height = original.getHeight();
                }
            }

            return size;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            if (original != null) {
                int x = 0;
                int y = (getHeight() - original.getHeight()) / 2;;
                if (scaled != null) {
                    x = (getWidth() - (original.getWidth() + scaled.getWidth())) / 2;
                } else {
                    x = (getWidth() - original.getWidth()) / 2;
                }
                g2d.drawImage(original, x, y, this);

                if (scaled != null) {
                    x += original.getWidth();
                    y = (getHeight() - scaled.getHeight()) / 2;
                    g2d.drawImage(scaled, x, y, this);
                }
            }
            g2d.dispose();
        }

        public BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {
            float scaleFactor = getScaleFactorToFit(img, size);
            return getScaledInstance(img, scaleFactor);
        }

        public float getScaleFactorToFit(BufferedImage img, Dimension size) {
            float scale = 1f;
            if (img != null) {
                int imageWidth = img.getWidth();
                int imageHeight = img.getHeight();
                scale = getScaleFactorToFit(new Dimension(imageWidth, imageHeight), size);
            }
            return scale;
        }

        public float getScaleFactorToFit(Dimension original, Dimension toFit) {
            float scale = 1f;
            if (original != null && toFit != null) {
                float dScaleWidth = getScaleFactor(original.width, toFit.width);
                float dScaleHeight = getScaleFactor(original.height, toFit.height);
                scale = Math.min(dScaleHeight, dScaleWidth);
            }
            return scale;
        }

        public float getScaleFactor(int iMasterSize, int iTargetSize) {
            float scale = 1;
            if (iMasterSize > iTargetSize) {
                scale = (float) iTargetSize / (float) iMasterSize;
            } else {
                scale = (float) iTargetSize / (float) iMasterSize;
            }
            return scale;
        }

        public BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
            BufferedImage imgBuffer = null;
            imgBuffer = getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
            return imgBuffer;
        }

        protected BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint, boolean higherQuality) {

            int targetWidth = (int) Math.round(img.getWidth() * dScaleFactor);
            int targetHeight = (int) Math.round(img.getHeight() * dScaleFactor);

            int type = (img.getTransparency() == Transparency.OPAQUE)
                            ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;

            BufferedImage ret = (BufferedImage) img;

            if (targetHeight > 0 || targetWidth > 0) {
                int w, h;
                if (higherQuality) {
                    w = img.getWidth();
                    h = img.getHeight();
                } else {
                    w = targetWidth;
                    h = targetHeight;
                }

                do {
                    if (higherQuality && w > targetWidth) {
                        w /= 2;
                        if (w < targetWidth) {
                            w = targetWidth;
                        }
                    }

                    if (higherQuality && h > targetHeight) {
                        h /= 2;
                        if (h < targetHeight) {
                            h = targetHeight;
                        }
                    }

                    BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
                    Graphics2D g2 = tmp.createGraphics();
                    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
                    g2.drawImage(ret, 0, 0, w, h, null);
                    g2.dispose();

                    ret = tmp;
                } while (w != targetWidth || h != targetHeight);
            } else {
                ret = new BufferedImage(1, 1, type);
            }
            return ret;
        }
    }
}

You may, also, find The Perils of Image.getScaledInstance()of interest

您还可以找到感兴趣的 Image.getScaledInstance()危险

回答by Riyad Kalla

dutchman, this is why I maintain the imgscalr library-- to make this kind of stuff painfully easy.

荷兰人,这就是我维护imgscalr 库的原因——让这种事情变得非常容易。

In your example, a single method call would do the trick, right after your first ImageIO.read line:

在您的示例中,在您的第一个 ImageIO.read 行之后,单个方法调用就可以解决问题:

origImage = ImageIO.read(new File(sourceImg));

you can do the following to get what you want (javadoc for this method):

您可以执行以下操作以获得所需的内容(此方法的 javadoc):

origImage = Scalr.resize(origImage, Method.ULTRA_QUALITY, 60);

and if that still looked a little jagged (because you are removing so much information from the image, you can add the following OP to the command to apply a light anti-aliasing filter to the image so it looks smoother):

如果它看起来仍然有点参差不齐(因为您从图像中删除了如此多的信息,您可以将以下 OP 添加到命令中,以对图像应用光抗锯齿过滤器,使其看起来更平滑):

origImage = Scalr.resize(origImage, Method.ULTRA_QUALITY, 60, Scalr.OP_ANTIALIAS);

That will replace all the remainder of the code logic you have. The only other thing I would recommend is saving out your really small samples as PNG's so there is no more compression/lossy conversion done on the image OR make sure you use little to none compression on the JPG if you really want it in JPG format. (Here is an articleon how to do it; it utilizes the ImageWriteParamclass)

这将替换您拥有的所有其余代码逻辑。我唯一建议的另一件事是将您的非常小的样本保存为 PNG,这样就不会对图像进行更多的压缩/有损转换,或者如果您真的想要 JPG 格式,请确保在 JPG 上几乎不使用压缩。(这是一篇关于如何做到这一点的文章;它利用了ImageWriteParam类)

imgscalr is licensed under an Apache 2 license and hosted on GitHubso you can do what you want with it; it also includes asynchronous scaling supportif you are using the library in a server-side app and queuing up huge numbers of scaling operations and don't want to kill the server.

imgscalr 在 Apache 2 许可下获得许可并托管在GitHub 上,因此您可以使用它做您想做的事情;如果您在服务器端应用程序中使用该库并排队大量缩放操作并且不想终止服务器,它还包括异步缩放支持

回答by Patrick Favre

As already stated, Java's Graphics2D does not provide a very good algorithm for down-scaling. If you don't want to implement a sophisticated algorithm yourself you could try out the current open source libs specialized for this: Thumbnailator, imgscalrand a Java interface for ImageMagick.

如前所述,Java 的 Graphics2D 没有提供非常好的缩小算法。如果你不想自己实现一个复杂的算法,你可以尝试专门为这个当前开源库:Thumbnailatorimgscalr和Java接口的ImageMagick

While researching for a private project I tried them out (except ImageMagick) and here are the visual results with Photoshop as reference:

在研究私人项目时,我尝试了它们(ImageMagick 除外),以下是使用 Photoshop 的视觉效果作为参考:

comparison

比较

A. Thumbnailator0.4.8 with default settings (no additional internal resizing)
B. imgscalr4.2 with ULTRA_QUALTY setting
C. PhotoshopCS5 bicubic filter (save for web)
D. Graphics2dwith all HQ render hints

A.带有默认设置的Thumbnailator0.4.8(没有额外的内部调整大小)
B.带有 ULTRA_QUALTY 设置的imgscalr4.2
C. PhotoshopCS5 双三次过滤器(保存为网络)
D. Graphics2d带有所有 HQ 渲染提示

Here is the used code

这是使用的代码

Thumbnailator and PS create similar results, while imgscalr seems to be softer. It is subjective which one of the libs creates the preferable results. Another point to consider though is the performance. While Thumbnailator and Graphics2d have similar runtime, imgscalr is considerably slower (with ULTRA_QUALITY) in my benchmarks.

Thumbnailator 和 PS 产生类似的结果,而 imgscalr 似乎更柔和。哪个库创建了更好的结果是主观的。但要考虑的另一点是性能。虽然 Thumbnailator 和 Graphics2d 具有相似的运行时间,但 imgscalr在我的基准测试中要慢得多(使用 ULTRA_QUALITY)。

For more info, read this post providing more detail on this matter.

有关更多信息,请阅读这篇文章,提供有关此问题的更多详细信息