Не удается получить ориентацию изображения из EXIF - PullRequest
0 голосов
/ 16 мая 2019

В настоящее время я пишу функцию для микробиблиотеки нашей компании в Котлине, которая считывает байты изображения и возвращает ориентацию в градусах. Я знаю, что в API 24 у нас есть ExifInterface и возможность его создания из InputStream, но проблема в том, что нам нужно поддерживать API 21, у которого нет такого конструктора.

Массив байтов, который передается функции getOrientation, всегда выглядит так:

-1, -40, -1, -32, 0, 16, 74, 70, 73, 70, 0, 1, 1, 0, 0, 72, 0, 72, 0, 0, -1, -31, 8, 82, 69, 120, 105, 102, 0, 0, 77, 77, 0, 42, 0, 0, 0, 8, 0, 12, 1, 15, 0, 2, 0, 0, 0, 6, 0, 0, 0, -98, 1, 16, 0, 2, 0, 0, 0, 9, 0, 0, 0, -92, 1, 18, 0, 3, 0, 0, 0, 1, 0, 6, 0, 0, 1, 26, 0, 5, 0, 0, 0, 1, 0, 0, 0, -82, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, -74, 1, 40, 0 and so on

Похоже, смещено, и это причина, почему я сместил его прямо на 256 в первой строке

Вот код, на котором я сейчас застрял:

object Exif {
    fun getOrientation(_bytes: ByteArray): Int {
        val bytes = _bytes.map { b -> b.toInt() + 256 }

        if (bytes[0] != 0xff && bytes[1] != 0xd8) {
            return 0
        }

        val length = bytes.size
        var offset = 2

        while (offset < length) {
            // TODO: extract all operations like the following
            // into separate function
            val marker = (bytes[offset] shl 8) or bytes[offset + 1]
            offset += 2

            if (marker == 0xffe1) {
                offset += 2
                val exifStr = (bytes[offset] shl 24) or (bytes[offset + 1] shl 16) or (bytes[offset + 2] shl 8) or bytes[offset + 3]

                if (exifStr != 0x45786966) {
                    return 0
                }

                offset += 6
                val little = (bytes[offset] shl 8) or bytes[offset + 1] == 0x4949
                offset += 4

                val inc = (bytes[offset] shl 24) or (bytes[offset + 1] shl 16) or (bytes[offset + 2] shl 8) or bytes[offset + 3]
                offset += if (little) inc.reverseBytes() else inc

                val tagsWOEndian = (bytes[offset] shl 8) or bytes[offset + 1]
                val tags = if (little) tagsWOEndian.reverseBytes() else tagsWOEndian
                offset += 2

                for (idx in 0..tags) {
                    val off = offset + idx * 12
                    val orientWOEndian = (bytes[off] shl 8) or bytes[off + 1]
                    val orient = if (little) orientWOEndian.reverseBytes() else orientWOEndian

                    if (orient == 0x0112) {
                        when ((bytes[off + 8] shl 8) or bytes[off + 8 + 1]) {
                            1 -> return 0
                            3 -> return 180
                            6 -> return 90
                            8 -> return 270
                        }
                    }
                }
            } else if (marker and 0xff00 != 0xff00) {
                break
            } else {
                offset += (bytes[offset] shl 8) or bytes[offset + 1]
            }
        }

        return 0
    }
}

fun Int.reverseBytes(): Int {
    val v0 = ((this ushr 0) and 0xFF)
    val v1 = ((this ushr 8) and 0xFF)
    val v2 = ((this ushr 16) and 0xFF)
    val v3 = ((this ushr 24) and 0xFF)
    return (v0 shl 24) or (v1 shl 16) or (v2 shl 8) or (v3 shl 0)
}

1 Ответ

0 голосов
/ 17 мая 2019

Решено! Я получил следующее решение:

fun getUint16(bytes: List<Int>, offset: Int, littleEndian: Boolean): Int {
    val value = (bytes[offset] shl 8) or bytes[offset + 1]
    return if (littleEndian) value.reverseBytes() else value
}

fun getUint32(bytes: List<Int>, offset: Int, littleEndian: Boolean): Int {
    val value = (bytes[offset] shl 24) or (bytes[offset + 1] shl 16) or (bytes[offset + 2] shl 8) or bytes[offset + 3]
    return if (littleEndian) value.reverseBytes() else value
}

fun Int.reverseBytes(): Int {
    val v0 = ((this ushr 0) and 0xFF)
    val v1 = ((this ushr 8) and 0xFF)
    val v2 = ((this ushr 16) and 0xFF)
    val v3 = ((this ushr 24) and 0xFF)
    return (v0 shl 24) or (v1 shl 16) or (v2 shl 8) or (v3 shl 0)
}

object Exif {
    fun getRotationDegrees(_bytes: ByteArray): Int {
        val bytes = _bytes.take(64 * 1024).map { b -> b.toInt() and 0xff }

        if (getUint16(bytes, 0, false) != 0xffd8) {
            return 0
        }

        val length = bytes.size
        var offset = 2

        while (offset < length) {
            val marker = getUint16(bytes, offset, false)
            offset += 2

            if (marker == 0xffe1) {
                if (getUint32(bytes, offset + 2, false) != 0x45786966) {
                    return 0
                }
                offset += 2

                val little = getUint16(bytes, offset + 6, false) == 0x4949
                offset += 6

                offset += getUint32(bytes, offset + 4, little)

                val tags = getUint16(bytes, offset, little)
                offset += 2

                for (idx in 0..tags) {
                    if (getUint16(bytes, offset + (idx * 12), little) == 0x0112) {
                        when (getUint16(bytes, offset + (idx * 12) + 8, little)) {
                            1 -> return 0
                            3 -> return 180
                            6 -> return 90
                            8 -> return 270
                        }
                    }
                }
            } else if (marker and 0xff00 != 0xff00) {
                break
            } else {
                offset += getUint16(bytes, offset, false)
            }
        }

        return 0
    }
}

Изменения по сравнению с кодом, указанным в вопросе:

  1. Преобразование между подписанным _bytes и неподписанным bytes выполнено неправильно. Теперь я получаю первые 65536 байтов, а затем преобразую их в unsigned, применяя побитовый and к каждому байту вместо простого добавления 256
  2. Все операции для получения значений UInt16 и UInt32 из байтового массива были перемещены в отдельные функции
  3. Исправлено неправильное offset увеличение внутри while цикла
...