【问题标题】:Java AWT/ImageIO: Bilinear and Bicubic scaling of a JPEG image result in completely black outputJava AWT/ImageIO:JPEG 图像的双线性和双三次缩放导致完全黑色的输出
【发布时间】:2015-07-23 19:31:09
【问题描述】:

最近邻缩放有效:当我使用TYPE_NEAREST_NEIGHBOR 时,整个图片保持不变。

虽然是 Scala 代码,但所有使用的库都是标准 Java 库。

功能:

def getBufferedImage(imageFile: java.io.File): BufferedImage = {
    ImageIO.read(imageFile)
}

def scaleImage(image: BufferedImage, minSize: Double): BufferedImage = {
    val before: BufferedImage = image
    val w = before.getWidth()
    val h = before.getHeight()
    val affit = new AffineTransform()
    var scale = 1.0
    if(h < w) {
      if(h > 0) {
        scale = minSize / h
      }
    } else {
      if(w > 0) {
        scale = minSize / w
      }
    }
    affit.scale(scale, scale)
    val affitop = new AffineTransformOp(affit, AffineTransformOp.TYPE_BICUBIC)
    affitop.filter(before, null)
}

def getImageJpegByteArray(image: BufferedImage): Array[Byte] = {
    val baos = new java.io.ByteArrayOutputStream()
    val mcios = new MemoryCacheImageOutputStream(baos)
    ImageIO.write(image, "jpeg", mcios)
    mcios.close()
    baos.toByteArray
}

调用代码sn-p:

val img = getBufferedImage(imageFile)
val scaledImg = scaleImage(img, 512)
val result = getImageJpegByteArray(scaledImg)
// result is written to SQLite database

result 被写入 SQLite 数据库。如果我从数据库中下载它并将其保存为 JPEG 文件,则生成的 JPEG 是

  • 如果我使用AffineTransformOp.TYPE_NEAREST_NEIGHBOR,正如预期的那样
  • 完全黑如果我使用AffineTransformOp.TYPE_BILINEAR
  • 完全黑如果我使用AffineTransformOp.TYPE_BICUBIC

因此,我指责 AffineTransformOp 有问题... 我该如何解决这个问题?

result 的文件幻数始终为 ff d8 ff,正如 JPEG 所期望的那样。

详情

Java 版本:Java HotSpot(TM) 64 位服务器虚拟机,Java 1.7.0_71

操作系统:Apple、OS X 10.9.5

测试图片:http://www.photos-public-domain.com/wp-content/uploads/2012/05/thundercloud-plum-blossoms.jpg

【问题讨论】:

  • 我尝试了所有三种组合以​​及AffineTransformOp 的所有三种组合,它们都输出了一些东西(纯 Java)
  • 所有图片都会出现这种情况,还是只出现一些?无论如何,您可以链接示例图像以进行测试吗?
  • 试试这个或任何其他 JPEG:photos-public-domain.com/wp-content/uploads/2012/05/… 双线性会导致完全黑色的输出。双三次会产生完全黑色的输出。
  • 使用 Oracle Java 8u25 对其进行了测试,并且可以正常工作。您使用哪个 Java 版本?也许这个错误,如果它是一个,已经被修复了?!

标签: java jpeg javax.imageio affinetransform bicubic


【解决方案1】:

我能够在 OS X 10.10.4 上的 Java 1.7.0_71 上重现您的问题(我用 Java 重写了您的代码,如果您有兴趣,我可以发布完整的代码)。

无论如何,问题不是AffineTransformOp 本身有问题。在我的测试程序中,我使用最小的 Swing JFrame 显示图像,并且缩放后的图像看起来都很好。这可能是 cmets 中大多数人不理解问题的原因。

问题的一部分是 AffineTransformOp 返回的 BufferedImage 当您没有为 filter 方法提供目标时(​​第二个参数,null 在您的情况下),它将为你。此图像将输入BufferedImage.TYPE_INT_ARGB。以下是来自AffineTransformOp.createCompatibleDestImage() 的相关代码(第 456-468 行,我保留了格式,以便更容易发现):

ColorModel cm = src.getColorModel();
if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
    (cm instanceof IndexColorModel ||
     cm.getTransparency() == Transparency.OPAQUE)
{
    image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
else {
    image = new BufferedImage(cm,
              src.getRaster().createCompatibleWritableRaster(w,h),
              cm.isAlphaPremultiplied(), null);
}

注意TYPE_NEAREST_NEIGHBOR 的特殊情况,它解释了为什么在使用最近邻算法时会出现不同的行为。 但是,通常这一切都很好(正如我所说,图像在 Swing 组件中显示得很好)。

当您尝试将此图像存储为 JPEG 时会出现问题。这些年来,存在很多与 ImageIO JPEG 插件相关的困惑和问题,以及它是否允许您使用 alpha 通道编写图像(例如您的 TYPE_INT_ARGB 图像)。它确实允许这样做。但是,大多数情况下,ARGB JPEG 会被误解为 CMYK JPEG(因为它们是 4 个通道,并且将 ARGB 数据存储在 JPEG 中非常奇特)并且将以所有时髦的颜色显示。不过,在你的情况下,它似乎全是黑色的......

所以,有两种可能的解决方案:

  • 将图像写入支持 Alpha 通道的文件格式,例如 PNG 或 TIFF(TIFF 需要额外的插件,因此它可能不是最佳选择)。像这样:

    ImageIO.write(image, "PNG", mcios);
    
  • 或者,在存储为 JPEG 之前,确保您的 BufferedImage 是没有 Alpha 通道的像素格式。您可以在缩放后执行此操作,但最简单(也是最快)的方法是为 AffineTransformOp 提供明确的目标图像,如下所示:

    Rectangle newSize = affitop.getBounds2D(before).getBounds();
    return affitop.filter(before, 
          new BufferedImage(newSize.width,  newSize.height, BufferedImage.TYPE_3BYTE_BGR));
    

这是您的图像,由程序缩放,使用 JPEG 格式和TYPE_3BYTE_BGR

我确信您可以将我的 Java 代码重写回 Scala。 :-)

【讨论】:

  • 很好的解释,谢谢 :-) 不幸的是,我需要 JPEG 输出。我在文档中遇到了getBounds2D,但它返回浮点值 => ceil 还是 floor?你怎么知道选择哪个?你怎么知道getBounds 在内部(天花板或地板)做了什么?我没有在文档中找到答案。
  • 没问题,然后选择第二个选项保持JPEG格式。我通常会推荐此选项,但我不知道您的确切要求。 :-) getBounds() 楼层 x/y 和 ceils w/h(我查看了源代码)。此外,这正是 AffineTransform.createCompatibleDestImage 所做的,所以你不会出错(除非你也翻译,否则你不需要考虑负 x/y)。
  • 确实如此。我也能找到源代码,我看到了Rectangle r = getBounds2D(src).getBounds();这一行。
  • 那么一切都好!如果您觉得这很有用,请不要忘记投票(如果您认为这是 the 答案,请接受)! ;-)
  • 我测试成功了。当然这是 the 答案 :-) 您也将在 4-5 小时内收到赏金。再次感谢您的宝贵意见。
猜你喜欢
  • 2018-09-27
  • 2018-09-23
  • 1970-01-01
  • 2013-02-17
  • 1970-01-01
  • 2015-11-14
  • 1970-01-01
  • 2016-06-22
  • 1970-01-01
相关资源
最近更新 更多