Почему ImageIO не сохраняет данные JPEG при многократной загрузке и сохранении? - PullRequest
0 голосов
/ 10 октября 2018

Первоначально я хотел попробовать, возможно ли восстановить отредактированные данные из изображения JPEG, учитывая, что это формат изображения с потерями и значения пикселей распространяются на соседние пиксели.

Чтобы проверить, сохраняются ли изагрузка изображений JPEG надежна, я написал следующую программу, которая неоднократно сохраняет и загружает изображение JPEG, пока оно не достигнет изображения, которое было замечено ранее.Вот код:

package de.roland_illig.jpg

import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.security.MessageDigest
import javax.imageio.ImageIO
import javax.xml.bind.DatatypeConverter

fun main(args: Array<String>) {

    fun loadJpeg(bytes: ByteArray) =
            ImageIO.read(ByteArrayInputStream(bytes))

    fun saveJpeg(img: BufferedImage) =
            ByteArrayOutputStream().apply { use { ImageIO.write(img, "jpg", it) } }.toByteArray()

    fun hash(bytes: ByteArray) =
            DatatypeConverter.printHexBinary(MessageDigest.getInstance("SHA-1").digest(bytes))

    var bytes = saveJpeg(ImageIO.read(File("000-original.png")))

    val log = mutableMapOf<String, Int>()
    for (n in 1..Int.MAX_VALUE) {
        Files.write(Paths.get("%03d.jpg".format(n)), bytes)
        val hash = hash(bytes)
        val prev = log.put(hash, n)
        if (prev != null) {
            println("After $n steps, the image is the same as after $prev steps.")
            break
        }

        bytes = saveJpeg(loadJpeg(bytes))
    }
}

Самое смешное, что для случайного скриншота требуется от 20 до 49 шагов, пока изображение не станет стабильным.В идеале я бы всегда ожидал 2 шага.

Несмотря на то, что JPEG - это формат с потерями, после сохранения и повторной загрузки каждый пиксель имеет определенное значение.Независимо от того, какое сжатие используется, при повторном сжатии тех же данных я ожидал, что сжатые данные также будут такими же:

val original = loadPng()              // Exact in-memory image
val jpeg0Bytes = saveJpeg(original)   // Saved with JPEG artifacts
val jpeg = loadJpeg(jpeg0Bytes)       // Lossy, loaded again
val jpeg1Bytes = saveJpeg(jpeg)       // Should be the same as jpeg0Bytes

Я пробовал только с настройками качества по умолчанию в Java ImageIO, но вручную поэкспериментировал сGIMP показал аналогичные результаты.

Теперь мне интересно, почему библиотеки изображений не реализуют сжатие JPEG, так что вышеуказанная программа остановится через 2 шага.Неужели так сложно устранить ошибки округления или что-то еще может создать эти артефакты?

...