【问题标题】:Re-sizing an image without losing quality在不损失质量的情况下重新调整图像大小
【发布时间】:2011-12-18 13:46:27
【问题描述】:

我编写了这段代码来调整具有两个因素的图像大小。它可以工作,但是调整大小后图像质量很差!你能帮帮我吗?

这是代码

public class ImageTest {

private static final int factor1 = 3;
private static final int factor2 = 4;
public static void main(String [] args){

    JFileChooser cs = new JFileChooser();
    cs.setFileSelectionMode(cs.DIRECTORIES_ONLY);
    int i = cs.showOpenDialog(null);
    if(i==cs.APPROVE_OPTION){
        File f = cs.getSelectedFile();
        File[] ff = f.listFiles();
        for(int j=0;j<ff.length;j++){
            String end = ff[j].getName().substring(ff[j].getName().indexOf(".")+1);
            System.out.println(end);
            try{
                BufferedImage originalImage = ImageIO.read(ff[j]);
                int type = originalImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : originalImage.getType();
                BufferedImage resizeImageJpg = resizeImageWithHint(originalImage, type);
                ImageIO.write(resizeImageJpg, end, new File("pr/"+ff[j].getName()));
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }


}
private static BufferedImage resizeImageWithHint(BufferedImage originalImage, int type){
    int IMG_WIDTH = (originalImage.getWidth()*factor1)/factor2;
    int IMG_HEIGHT = (originalImage.getHeight()*factor1)/factor2;
    BufferedImage resizedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, type);
    Graphics2D g = resizedImage.createGraphics();
    g.drawImage(originalImage, 0, 0, IMG_WIDTH, IMG_HEIGHT, null);
    g.dispose();    
    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);

    return resizedImage;
}   
   }

我在网上看到 resizeImageWithHint 是在范围内完成的,以免损失质量..但确实如此!为什么?你能帮我解决这个问题吗?

【问题讨论】:

    标签: java resize bufferedimage


    【解决方案1】:

    是的,我遇到了同样的问题并解决了,请read my question(答案嵌入在问题中)。我尝试了 imgscalrjava-image-scaling 库,发现第二个质量更好。靠近显示器,欣赏缩略图示例之间的差异。

    尽管我最初的想法是,调整图像大小似乎是一件非常复杂的事情,你不想自己做。例如,我告诉 java-image-scaling 使用 ResampleFilters.getLanczos3Filter() 以获得更好的结果。

    它还解决了如何保存质量高于标准 75 的 JPG,这会产生糟糕的结果,尤其是缩略图。

    我还编写了一个名为MyImage 的小类来帮助完成常见任务,例如从字节数组、文件中读取图像、通过仅指定宽度或仅指定高度进行缩放、通过指定边界框进行缩放、通过指定宽度和高度并添加白色带进行缩放以使图像不失真并写入JPG文件。

    【讨论】:

      【解决方案2】:

      我读过的关于这个主题的最好的文章是The Perils of Image.getScaledInstance()(网络存档)。

      简而言之:您需要使用几个调整大小的步骤才能获得好的图像。文章中的辅助方法:

      public BufferedImage getScaledInstance(BufferedImage img,
                                             int targetWidth,
                                             int targetHeight,
                                             Object hint,
                                             boolean higherQuality)
      {
          int type = (img.getTransparency() == Transparency.OPAQUE) ?
              BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
          BufferedImage ret = (BufferedImage)img;
          int w, h;
          if (higherQuality) {
              // Use multi-step technique: start with original size, then
              // scale down in multiple passes with drawImage()
              // until the target size is reached
              w = img.getWidth();
              h = img.getHeight();
          } else {
              // Use one-step technique: scale directly from original
              // size to target size with a single drawImage() call
              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(w, h, 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);
      
          return ret;
      }
      

      【讨论】:

      • 是的,这篇文章不错,imgscalr 和 java-image-scaling 都引用了它,但结果又不同,我认为 java-image-scaling 可以应用的过滤器会有所不同。跨度>
      • 好文章,确实有助于提高缩放图像的质量。将 'w /= 2' 更改为 'w /= 1.2' 可以提高质量(将执行更多循环,因此质量更好)
      • 我尝试了 w /= 1.2 的建议,但我发现它使按比例缩小的图像更模糊,尤其是在小得多的尺寸下。我尝试了 1.5 并没有帮助,所以我坚持使用 w /= 2。
      【解决方案3】:

      以下代码在保留纵横比的情况下为我提供了最高质量的调整大小。 尝试了一些事情并阅读了其他答案中出现的几个条目。迷失了两天,最后我用普通的 Java 方法得到了最好的结果(也尝试了 ImageMagick 和 java-image-scaling 库):

      public static boolean resizeUsingJavaAlgo(String source, File dest, int width, int height) throws IOException {
        BufferedImage sourceImage = ImageIO.read(new FileInputStream(source));
        double ratio = (double) sourceImage.getWidth()/sourceImage.getHeight();
        if (width < 1) {
          width = (int) (height * ratio + 0.4);
        } else if (height < 1) {
          height = (int) (width /ratio + 0.4);
        }
      
        Image scaled = sourceImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
        BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = bufferedScaled.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g2d.drawImage(scaled, 0, 0, width, height, null);
        dest.createNewFile();
        writeJpeg(bufferedScaled, dest.getCanonicalPath(), 1.0f);
        return true;
      }
      
      
      /**
      * Write a JPEG file setting the compression quality.
      *
      * @param image a BufferedImage to be saved
      * @param destFile destination file (absolute or relative path)
      * @param quality a float between 0 and 1, where 1 means uncompressed.
      * @throws IOException in case of problems writing the file
      */
      private static void writeJpeg(BufferedImage image, String destFile, float quality)
            throws IOException {
        ImageWriter writer = null;
        FileImageOutputStream output = null;
        try {
          writer = ImageIO.getImageWritersByFormatName("jpeg").next();
          ImageWriteParam param = writer.getDefaultWriteParam();
          param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
          param.setCompressionQuality(quality);
          output = new FileImageOutputStream(new File(destFile));
          writer.setOutput(output);
          IIOImage iioImage = new IIOImage(image, null, null);
          writer.write(null, iioImage, param);
        } catch (IOException ex) {
          throw ex;
        } finally {
          if (writer != null) {
            writer.dispose();
          }
          if (output != null) {
            output.close();
          }
        }
      }
      

      【讨论】:

      • @nfechner 发布的代码是对您的改进。如果图片很大,则质量不会像您期望的那样好。正如给定文章所写:“造成这种质量差异的原因是由于使用了不同的过滤算法。如果缩小两倍以上,BILINEAR 和 BICUBIC 算法往往会由于从源图像中采样像素的方式而丢失信息。使用多步渲染方式,resized-image会更好。
      • 我用我的眼睛将图像缩小到 320 像素左右,我认为这产生了更好的质量。我明白你在说什么,但是,我对 @nfechner 算法得到的结果并不满意,我也尝试过......
      【解决方案4】:

      如果你的图片来源是png,那么使用如下:

      Image imgSmall = imgBig.getScaledInstance(
              targetWidth, targetHeight, Image.SCALE_SMOOTH);
      

      如果您想调整 jpeg 或 gif 的大小而不会丢失太多质量,我在 2010 年为此创建了一个库:beautylib on github,它在内部使用了另一个库:java-image-scaling。可以直接看源码找到有用的:https://github.com/felipelalli/beautylib/blob/master/src/br/eti/fml/beautylib/ResizeImage.java

      【讨论】:

      • 是否只推荐用于“png”?
      • 我不记得了,对不起。
      【解决方案5】:

      没有一个答案可以帮助您获得您想要的真正质量。在您的项目中包含 thumbailator.jar(在此处下载):

      https://code.google.com/p/thumbnailator/

      https://code.google.com/p/thumbnailator/wiki/Downloads?tm=2

      然后首先上传图片(作为文件,不带 Thumbnailator - 它用于创建缩略图,但您当然可以使用它创建大图像),并将其调整为您想要的每个尺寸(例如 Thumbnailator 800x600)。质量会非常好。找了这么久,这个.jar帮我实现了我想要的。

      【讨论】:

      • 您好,谢谢分享!这是一个旧帖子,但我会尝试并记住未来。谢谢!
      【解决方案6】:

      知道问题已经过时了...我已经尝试了不同的冲浪解决方案然后上网,我使用getScaledInstance() 得到了最好的结果,提供Image.SCALE_SMOOTH 作为参数。事实上,由此产生的图像质量确实更好。 我的代码如下:

      final int THUMB_SIDE = 140;
      try {
          BufferedImage masterImage = ImageIO.read(startingImage);
          BufferedImage thumbImage = new BufferedImage(THUMB_SIDE, THUMB_SIDE, BufferedImage.TYPE_INT_ARGB);
          Graphics2D g2d = thumbImage.createGraphics();
          g2d.drawImage(masterImage.getScaledInstance(THUMB_SIDE, THUMB_SIDE, Image.SCALE_SMOOTH), 0, 0, THUMB_SIDE, THUMB_SIDE, null);
          g2d.dispose();
          String thumb_path = path.substring(0, path.indexOf(".png")) + "_thumb.png";
          ImageIO.write(thumbImage, "png", new File(thumb_path));
      } catch (IOException e) {
          e.printStackTrace();
      }
      

      【讨论】:

      • 是否只推荐用于“png”?
      【解决方案7】:

      答案:去掉提示VALUE_INTERPOLATION_BILINEARVALUE_RENDERING_QUALITY

      示例:

      public static BufferedImage resizeImage(BufferedImage image, int width, int height) {
      
          // Temporary image
      
          BufferedImage tmp = image;
      
          // Result image
      
          BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
      
          // Graphics object
      
          Graphics2D graphics = (Graphics2D)result.createGraphics();
      
          // Add rendering hints
      
          graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
          graphics.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
      
          // Draw tmp
      
          graphics.drawImage(tmp, 0, 0, width, height, null);
      
          // Dispose of graphics object
      
          graphics.dispose();
      
          // Return image result
      
          return result;
      }
      

      注意:出于某种原因,提示 VALUE_INTERPOLATION_BILINEARVALUE_RENDERING_QUALITY em> 模糊正在调整大小的图像。

      【讨论】:

        【解决方案8】:

        所有发布的方法都对我不起作用,我必须减小 QrCode 的大小,但是使用上述方法质量很差,扫描仪无法工作,如果我拍摄原始照片并在油漆中调整它的大小,扫描仪正在工作。

        【讨论】:

          【解决方案9】:

          在给定这些值的情况下,The Perils of Image.getScaledInstance() 中使用的 do while 循环将陷入无限循环, w = 606; h = 505,目标宽度 = 677,目标高度 = 505

          这是一个简单的测试代码,你可以试试。

          public class LoopTest {
          
              public static void main(String[] args) {
                  new LoopTest(true, 606, 505, 677, 505);
              }
          
              public LoopTest(boolean higherQuality, int w, int h, int targetWidth, int targetHeight) {
                  do {
                      if (higherQuality && w > targetWidth) {
                          w /= 2;
                          if (w < targetWidth) {
                              w = targetWidth;
                          }
                      }
          
                      if (higherQuality && h > targetHeight) {
                          h /= 2;
                          if (h < targetHeight) {
                              h = targetHeight;
                          }
                      }
                  } while (w != targetWidth || h != targetHeight);        // TODO Auto-generated constructor stub
              }
          }
          

          快速解决方法:为循环计数定义一个索引。如果索引>=10,则跳出循环。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多