Первоначально я хотел попробовать, возможно ли восстановить отредактированные данные из изображения 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 шага.Неужели так сложно устранить ошибки округления или что-то еще может создать эти артефакты?