【问题标题】:How to scale PNG image until reaches the target file size如何缩放PNG图像直到达到目标文件大小
【发布时间】:2021-11-01 13:03:04
【问题描述】:

正如标题所说,我正在尝试调整 PNG 图像的大小以达到目标文件大小(以兆字节为单位)。
我在 SO 和网络上进行了很多搜索,发现了很多代码,但所有代码都没有考虑到最终文件的大小。
我已经安排了一些代码,但试图优化性能。


例子:

  • 源图像尺寸 = 30 MB
  • 目标文件输出大小 = 5 MB

当前流量:

  • 1 - 将PNG图像加载为BufferedImage
  • 2 - 递归使用Scalr.resize(...) 来调整图像大小
    • 2.1 - 对于每个步骤,使用 ImageIO.write 将压缩的 PNG 存储在临时文件中
    • 2.2 - 使用File.length 检查大小,如果磁盘大小> 5 MB 返回步骤2
  • 3 - 使用ImageIO.write(...) 保存图像

这个方法行得通,微调一些参数(比如比例因子)就可以完成任务了。

我试图了解是否可以通过计算/猜测最终文件大小而不将图像存储在临时文件中来改进所有方面。
有一个byte[] obj 存储在BufferedImage obj 中,我可以使用BufferedImage.getData().getDataBuffer() 获得它,它表示图像的内容,但显然这个数组的大小比文件的最终大小大2 倍或3 倍,因为PNG 压缩算法。

我尝试了一些公式来计算值,例如: w * h * bitDepth * 8 / 1024 / 1024 但我确信我丢失了很多数据并且帐户没有加起来!


目前我主要使用这段代码:

static void resize(BufferedImage image, String outPath, int scalingFactor) throws Exception {
    image = Scalr.resize(image, image.getWidth() - scalingFactor);

    // image.getData().getDataBuffer() - the byteArray containing image

    File tempFile = File.createTempFile("" + System.currentTimeMillis(), ".png");
    ImageIO.write(image, "png", tempFile);

    System.out.println("Calculated size in bytes is: " + tempFile.length() + " - factor: " + scalingFactor);

    // MAX_SIZE defined in bytes
    if (tempFile.length() > MAX_SIZE) {
        // recursion starts here
        resize(image, outPath, chooseFactor(tempFile, 4));
    } else {
        // break the recursive cycle
        ImageIO.write(image, "png", new File(outPath));
    }
}

static int chooseFactor(File image, int scale) {
    // MEGABYTE is 1024*1024
    double mbSize = (double) image.length() / MEGABYTE;
    return (int) ((mbSize / scale) * 100);
}

有没有办法从BufferedImage对象开始计算/猜测最终文件大小?

请告诉我我是否已经说清楚了,或者我可以提供更多信息。
如果您认为问题的解释性不够,也建议为该问题设置一个更合适的标题。

谢谢。

【问题讨论】:

  • 为了获得更好的图像质量,请调整原始文件而不是临时文件的大小。
  • 哦,你是对的,非常感谢你的建议!
  • 更改缩放策略以查看文件大小并预定义一些粗略的缩放。如果这些不起作用,请根据上次重新缩放后的临时文件大小尝试更高的缩放。
  • 是的,我已经考虑过对不同尺寸的图像集进行一些大规模测试,以获得基本的比例因子。最终我会将结果整合到线程中:)

标签: java bufferedimage


【解决方案1】:

任何沿图像宽度/高度的单调函数都可用于执行二分搜索。

这种方法适用于许多可能需要的更改 (从PNG更改为JPG,添加压缩,更改优化 目标)与临时解决方案(例如直接预测 PNG 的大小(例如,可以简单地根据 您的生产服务器或客户端上安装了什么库 您的应用程序使用)。

存储的字节应该是单调的(无论如何我的实现在没有单调函数的情况下是安全的[但不是最优的])。

此函数使用任何函数执行到较低域的二分搜索(例如,不放大图像):

static BufferedImage downScaleSearch(BufferedImage source, Function<BufferedImage, Boolean> downScale) {

    int initialSize = Math.max(source.getWidth(), source.getHeight());

    int a = 1;
    int b = initialSize;

    BufferedImage image = source;
    while(true) {
        int c = (a + b) / 2 - 1;

        // fix point
        if(c <= a)
            return image;

        BufferedImage scaled = Scalr.resize(source, c);
        if(downScale.apply(scaled)) {
            b = c;
        } else {
            // the last candidate will be the not greater than limit
            image = scaled;
            a = c;
        }
    }
}

如果我们对最终的 PNG 文件大小感兴趣,搜索函数将为 PNG 大小:

static final Path output = Paths.get("/tmp/downscaled.png");

static long persistAndReturnSize(BufferedImage image) {
    if(ImageIO.write(image, "png", output.toFile()))
        return Files.size(output);
    throw new RuntimeException("Cannot write PNG file!");
}

(你可以持久化到内存而不是文件系统)。

现在,我们可以生成大小不超过任何固定值的图像

public static void main(String... args) throws IOException {

    BufferedImage image = ImageIO.read(Paths.get("/home/josejuan/tmp/test.png").toFile());

    for(long sz: asList(10_000, 30_000, 80_000, 150_000)) {
        final long MAX_SIZE = sz;
        BufferedImage bestFit = downScaleSearch(image, i -> persistAndReturnSize(i) >= MAX_SIZE);
        ImageIO.write(bestFit, "png", output.toFile());
        System.out.println("Size: " + sz + " >= " + Files.size(output));
    }

}

有输出

Size: 10000 >= 9794
Size: 30000 >= 29518
Size: 80000 >= 79050
Size: 150000 >= 143277

注意:如果您不使用压缩或您承认近似值,则可能您可以将 persistAndReturnSize 函数替换为 estimator 而无需保留图像。

注意:我们的搜索空间是 size = 1, 2, ...,但您可以使用更多参数执行类似的搜索,例如压缩级别、像素颜色空间……(尽管您的域可能会是不是单调的,你应该使用https://en.wikipedia.org/wiki/Gradient_descent 或类似的)。

【讨论】:

  • 首先感谢您的宝贵时间。我尝试了代码,它似乎工作 - 非常好。这是另一种实现,但仍然与我的相似。即使基于我所做的测试,您的解决方案似乎不如我的解决方案执行 - 但似乎更准确(最终尺寸更接近 MAX_SIZE)。我可以询问有关估算器的更多线索吗?你有什么资源可以分享或相关信息吗?再次感谢
  • 估算器通过保证P{bad result} &lt; ε 快速为您提供值,并且它们基于经验值。它的正确使用与上下文密切相关(例如,使用的库!)。例如,如果最大限度地减少转换为 PNG 的次数非常重要(我不这么认为),您可以根据经验估计大小(和估计的质量!)。在这张糖尿病图表中,您可以看到估计量值的置信度如何变化laptrinhx.com/…
猜你喜欢
  • 1970-01-01
  • 2016-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-24
  • 2017-04-22
  • 2020-03-12
  • 2020-01-26
相关资源
最近更新 更多