ImageIO - Чтение разных параллельных файлов? - PullRequest
0 голосов
/ 22 мая 2018

В настоящее время я пишу программу, которая обрабатывает различные изображения.Поэтому я подумал, что было бы разумно выполнить операцию (масштабирование / добавление водяного знака) параллельно.

Проблема в том, что я получаю следующую ошибку:

    Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException
    at java.base/java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:1006)
    at eu.reisihub.soft.watermarking.Main$main$2$2$2$2.invoke(Main.kt:62)
    at eu.reisihub.soft.watermarking.Main$main$2$2$2$2.invoke(Main.kt:19)
    at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149)
    at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149)
    at kotlin.sequences.SequencesKt___SequencesKt.count(_Sequences.kt:1006)
    at eu.reisihub.soft.watermarking.Main.main(Main.kt:66)
Caused by: java.lang.NullPointerException
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:488)
    at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:603)
    ... 7 more
Caused by: java.lang.NullPointerException
    at java.desktop/java.awt.color.ICC_Profile.intFromBigEndian(ICC_Profile.java:1784)
    at java.desktop/java.awt.color.ICC_Profile.getNumComponents(ICC_Profile.java:1476)
    at java.desktop/sun.java2d.cmm.lcms.LCMSTransform.<init>(LCMSTransform.java:93)
    at java.desktop/sun.java2d.cmm.lcms.LCMS.createTransform(LCMS.java:173)
    at java.desktop/java.awt.color.ICC_ColorSpace.fromRGB(ICC_ColorSpace.java:230)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.setImageData(JPEGImageReader.java:808)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readImageHeader(Native Method)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readNativeHeader(JPEGImageReader.java:723)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.checkTablesOnly(JPEGImageReader.java:347)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.gotoImage(JPEGImageReader.java:493)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readHeader(JPEGImageReader.java:716)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1173)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1153)
    at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1468)
    at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1363)
    at eu.reisihub.shot.UtilsKt.readImage(Utils.kt:19)
    at eu.reisihub.soft.watermarking.WatermarkUtils$create$1.invoke(WatermarkUtils.kt:18)
    at eu.reisihub.soft.watermarking.WatermarkUtils$create$1.invoke(WatermarkUtils.kt:8)
    at eu.reisihub.soft.watermarking.Main$sam$java_util_concurrent_Callable$0.call(Main.kt)
    at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1448)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1603)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)

обратите внимание, что язнать, что в java.desktop/java.awt.color.ICC_Profile.intFromBigEndian(ICC_Profile.java:1784) есть NPE.И поверьте мне, это НЕ изображение.

Я попробовал 2 подхода с Java 8 и Java 10. У меня есть пул потоков фиксированного размера с 6 потоками для 4 ядер.Я использую Kotlin для реализации и создал функцию расширения fun Path.readImage(): BufferedImage.Для людей, которые не знают Kotlin, только для целей этого примера, будет примерно соответствовать public BufferedImage readImage(Path this).

Наивный подход Чтение файла с использованием Files.newInputStream(this, StandardOpenOption.READ).use { ImageIO.read(it) }.Это открывает новый InputStream из Path и говорит ImageIO прочитать изображение из этого InputStream.Выглядит хорошо?Не работает ~ каждый четвертый раз на Java 8 и примерно на 90% на Java 10. Кстати: если я оберну синхронизированный блок вокруг этого, это будет работать - всегда.За 13 секунд нужно обработать 17 изображений вместо 7 секунд.

Тем временем я обновил Gradle с 4.4 до 4.7.До того, как я настроил IntelliJ для использования Java 10 и Gradle для использования Java 8. Возможно, причина различий отмечена выше.Примечание.Я всегда использовал synchronized(System.err)!

Сложный подход Знание того, что работает при синхронизации всего, мой метод теперь выглядит более сложным и выглядит так:

fun Path.readImage(): BufferedImage = Files.newInputStream(this, StandardOpenOption.READ).buffered().use {
    var nStream: ImageInputStream? = null
    var nReader: ImageReader? = null
    synchronized(System.err) {
        nStream = ImageIO.createImageInputStream(it) ?: throw IIOException("Can't create an ImageInputStream!")

        val iter = ImageIO.getImageReaders(nStream)
        if (!iter.hasNext()) {
            throw IIOException("No image nReader found!")
        }
        nReader = iter.next()
    }

    nStream!!.let { stream ->
        nReader!!.let { reader ->
            reader.setInput(stream, true, true)
            try {
                println(reader)
                return reader.read(0, null)
            } finally {
                reader.dispose()
                stream.close()
            }
        }
    }
}

См. println в коде?Я знаю, что для каждого изображения создается новый объект com.sun.imageio.plugins.jpeg.JPEGImageReader. Это повышает производительность до 7 секунд, с которыми я разговаривал.Обычно не работает 2-4 раза, а затем работает 6-10 раз.

Трассировка стека для сложной версии выглядит следующим образом:

Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at eu.reisihub.soft.watermarking.Main$main$1$2$2$2$2.invoke(Main.kt:64)
    at eu.reisihub.soft.watermarking.Main$main$1$2$2$2$2.invoke(Main.kt:20)
    at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149)
    at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149)
    at kotlin.sequences.SequencesKt___SequencesKt.count(_Sequences.kt:1006)
    at eu.reisihub.soft.watermarking.Main$main$1.invoke(Main.kt:68)
    at eu.reisihub.soft.watermarking.Main$main$1.invoke(Main.kt:20)
    at eu.reisihub.shot.UtilsKt.measured(Utils.kt:54)
    at eu.reisihub.soft.watermarking.Main.main(Main.kt:24)
Caused by: java.lang.NullPointerException
    at java.awt.color.ICC_Profile.intFromBigEndian(ICC_Profile.java:1782)
    at java.awt.color.ICC_Profile.getNumComponents(ICC_Profile.java:1474)
    at sun.java2d.cmm.lcms.LCMSTransform.<init>(LCMSTransform.java:98)
    at sun.java2d.cmm.lcms.LCMS.createTransform(LCMS.java:173)
    at java.awt.color.ICC_ColorSpace.fromRGB(ICC_ColorSpace.java:218)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.setImageData(JPEGImageReader.java:694)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.readImageHeader(Native Method)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.readNativeHeader(JPEGImageReader.java:609)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.checkTablesOnly(JPEGImageReader.java:347)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.gotoImage(JPEGImageReader.java:481)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.readHeader(JPEGImageReader.java:602)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1059)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1039)
    at eu.reisihub.shot.UtilsKt.readImage(Utils.kt:39)
    at eu.reisihub.soft.watermarking.WatermarkUtils$create$1.invoke(WatermarkUtils.kt:16)
    at eu.reisihub.soft.watermarking.WatermarkUtils$create$1.invoke(WatermarkUtils.kt:8)
    at eu.reisihub.soft.watermarking.Main$sam$java_util_concurrent_Callable$0.call(Main.kt)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:748)

Где Main:64 равно Future#get ИMain:68 is kotlin.Sequence#count

Расположение файла Main.kt: https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/Watermarking/src/main/kotlin/eu/reisihub/soft/watermarking/Main.kt

Расположение файла Utils.kt: https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/base/src/main/kotlin/eu/reisihub/shot/Utils.kt

Я попытался улучшитькод в течение нескольких часов, и моя голова перестала порождать новые идеи.Я знаю, что ошибка также происходит с 2 картинками.Поскольку это не всегда терпит неудачу, профиль ICC должен быть правильным.Честно говоря, я согласен с тем, что мой жесткий диск не работает и иногда портится.Я также попытался прочитать файлы с моего SSD.Согласно https://stackoverflow.com/a/26300361/1870799 метод чтения должен быть потокобезопасным.Я понятия не имею, что происходит.Каждое изображение на диске (представленное путем) получает свой собственный Callable<Task>.Я только читаю эти изображения и записываю их в другую папку.Итак, для каждой задачи один файл читается, один файл записывается.Эти два файла НЕ одинаковы, каждая задача имеет свои собственные файлы.

Единственное, что они разделяют, это BufferedImage в формате PNG, который рисуется на считанном изображении (водяной знак).Но это случится позже.Как можно видеть на трассировке стека, это происходит при чтении изображения JPEG.

at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1039)
at eu.reisihub.shot.UtilsKt.readImage(Utils.kt:39)

Если вы хотите выполнить программу, настройку JSON, путь к которой нужно основному приложению, можно создать с помощью https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/Watermarking/src/main/kotlin/eu/reisihub/soft/watermarking/SettingsCreator.kt

Я ценю любой вклад.На данный момент я не знаю, что не так ...

Кстати: для некоторых задач я использую: https://github.com/coobird/thumbnailator

Ответы [ 2 ]

0 голосов
/ 22 мая 2018

ОБНОВЛЕНИЕ 2018-05-23

@ haraldK упомянул в комментарии, что загрузка профилей изображений перед кодом:

import java.awt.color.ColorSpace
import java.awt.color.ICC_Profile
import java.awt.image.BufferedImage
import java.nio.file.Path
import javax.imageio.ImageIO

object ImgLoadUtils {
    init {
        // Load deferred color space profiles to avoid 
        // ConcurrentModificationException due to JDK
        // Use in public static main void or prior to application initialization
        // https://github.com/haraldk/TwelveMonkeys/issues/402
        // https://bugs.openjdk.java.net/browse/JDK-6986863
        // https://stackoverflow.com/questions/26297491/imageio-thread-safety
        ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData();
        ICC_Profile.getInstance(ColorSpace.CS_PYCC).getData();
        ICC_Profile.getInstance(ColorSpace.CS_GRAY).getData();
        ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ).getData();
        ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB).getData();
    }

    fun loadImage(p: Path): BufferedImage = ImageIO.read(p.toFile())
}

ИСТОЧНИК


Спасибо за @Alex Taylor за ссылку на ошибку OpenJDK.Это заверило меня, что я не полный идиот.

Вместо

fun Path.readImage(): BufferedImage =
    Files.newInputStream(this, StandardOpenOption.READ).use { ImageIO.read(it) }

Я сейчас использую

fun Path.readImage(): BufferedImage =
    ImageIcon(toUri().toURL()).let {
        BufferedImage(it.iconWidth, it.iconHeight, BufferedImage.TYPE_INT_ARGB).apply {
            it.paintIcon(null, createGraphics(), 0, 0)
        }
    }

Это за https://aacsinia.wordpress.com/2010/12/21/java-how-to-create-buffered-image-from-inputstream/.Скорость в норме и ошибка ушла [ 20 + пробеги ]!Поэтому IMHO ImageIO.read не следует использовать в многопоточной среде ....

0 голосов
/ 22 мая 2018

Имеется отчет об ошибке для OpenJDK под названием «NullPointerException из ICC_Profile.getInstance () в многопоточном приложении».На момент написания резолюции нет разрешения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...