SubwayTooter-Android-App/exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.kt

379 lines
12 KiB
Kotlin

/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.sephiroth.android.library.exif2
import android.util.Log
import it.sephiroth.android.library.exif2.utils.OrderedDataOutputStream
import java.io.BufferedOutputStream
import java.io.IOException
import java.io.OutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.*
@Suppress("unused")
internal class ExifOutputStream(
private val mInterface : ExifInterface,
// the Exif header to be written into the JPEG file.
private val exifData : ExifData
) {
private val mBuffer = ByteBuffer.allocate(4)
private fun requestByteToBuffer(
requestByteCount : Int, buffer : ByteArray, offset : Int, length : Int
) : Int {
val byteNeeded = requestByteCount - mBuffer.position()
val byteToRead = if(length > byteNeeded) byteNeeded else length
mBuffer.put(buffer, offset, byteToRead)
return byteToRead
}
@Throws(IOException::class)
fun writeExifData(out : OutputStream) {
Log.v(TAG, "Writing exif data...")
val nullTags = stripNullValueTags(exifData)
createRequiredIfdAndTag()
val exifSize = calculateAllOffset()
// Log.i(TAG, "exifSize: " + (exifSize + 8));
if(exifSize + 8 > MAX_EXIF_SIZE) {
throw IOException("Exif header is too large (>64Kb)")
}
val outputStream = BufferedOutputStream(out, STREAMBUFFER_SIZE)
val dataOutputStream =
OrderedDataOutputStream(outputStream)
dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN)
dataOutputStream.write(0xFF)
dataOutputStream.write(JpegHeader.TAG_M_EXIF)
dataOutputStream.writeShort((exifSize + 8).toShort())
dataOutputStream.writeInt(EXIF_HEADER)
dataOutputStream.writeShort(0x0000.toShort())
if(exifData.byteOrder == ByteOrder.BIG_ENDIAN) {
dataOutputStream.writeShort(TIFF_BIG_ENDIAN)
} else {
dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN)
}
dataOutputStream.setByteOrder(exifData.byteOrder)
dataOutputStream.writeShort(TIFF_HEADER)
dataOutputStream.writeInt(8)
writeAllTags(dataOutputStream)
writeThumbnail(dataOutputStream)
for(t in nullTags) {
exifData.addTag(t)
}
dataOutputStream.flush()
}
// strip tags that has null value
// return list of removed tags
private fun stripNullValueTags(data : ExifData) =
ArrayList<ExifTag>()
.apply {
for(t in data.allTags) {
if(t.getValue() == null && ! ExifInterface.isOffsetTag(t.tagId)) {
data.removeTag(t.tagId, t.ifd)
add(t)
}
}
}
@Throws(IOException::class)
private fun writeThumbnail(dataOutputStream : OrderedDataOutputStream) {
val compressedThumbnail = exifData.compressedThumbnail
if(compressedThumbnail != null) {
Log.d(TAG, "writing thumbnail..")
dataOutputStream.write(compressedThumbnail)
} else {
val stripList = exifData.stripList
if(stripList != null) {
Log.d(TAG, "writing uncompressed strip..")
stripList.forEach {
dataOutputStream.write(it)
}
}
}
}
@Throws(IOException::class)
private fun writeAllTags(dataOutputStream : OrderedDataOutputStream) {
writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_0), dataOutputStream)
writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_EXIF), dataOutputStream)
writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY), dataOutputStream)
writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_GPS), dataOutputStream)
writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_1), dataOutputStream)
}
@Throws(IOException::class)
private fun writeIfd(ifd : IfdData?, dataOutputStream : OrderedDataOutputStream) {
ifd ?: return
val tags = ifd.allTagsCollection
dataOutputStream.writeShort(tags.size.toShort())
for(tag in tags) {
dataOutputStream.writeShort(tag.tagId)
dataOutputStream.writeShort(tag.dataType)
dataOutputStream.writeInt(tag.componentCount)
// Log.v( TAG, "\n" + tag.toString() );
if(tag.dataSize > 4) {
dataOutputStream.writeInt(tag.offset)
} else {
writeTagValue(tag, dataOutputStream)
var i = 0
val n = 4 - tag.dataSize
while(i < n) {
dataOutputStream.write(0)
i ++
}
}
}
dataOutputStream.writeInt(ifd.offsetToNextIfd)
for(tag in tags) {
if(tag.dataSize > 4) {
writeTagValue(tag, dataOutputStream)
}
}
}
private fun calculateOffsetOfIfd(ifd : IfdData, offsetArg : Int) : Int {
var offset = offsetArg
offset += 2 + ifd.tagCount * TAG_SIZE + 4
for(tag in ifd.allTagsCollection) {
if(tag.dataSize > 4) {
tag.offset = offset
offset += tag.dataSize
}
}
return offset
}
@Throws(IOException::class)
private fun createRequiredIfdAndTag() {
// IFD0 is required for all file
var ifd0 = exifData.getIfdData(IfdData.TYPE_IFD_0)
if(ifd0 == null) {
ifd0 = IfdData(IfdData.TYPE_IFD_0)
exifData.addIfdData(ifd0)
}
val exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD)
?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_EXIF_IFD)
ifd0.setTag(exifOffsetTag)
// Exif IFD is required for all files.
var exifIfd = exifData.getIfdData(IfdData.TYPE_IFD_EXIF)
if(exifIfd == null) {
exifIfd = IfdData(IfdData.TYPE_IFD_EXIF)
exifData.addIfdData(exifIfd)
}
// GPS IFD
val gpsIfd = exifData.getIfdData(IfdData.TYPE_IFD_GPS)
if(gpsIfd != null) {
val gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_GPS_IFD}")
ifd0.setTag(gpsOffsetTag)
}
// Interoperability IFD
val interIfd = exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY)
if(interIfd != null) {
val interOffsetTag =
mInterface.buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_INTEROPERABILITY_IFD}")
exifIfd.setTag(interOffsetTag)
}
var ifd1 = exifData.getIfdData(IfdData.TYPE_IFD_1)
// thumbnail
val compressedThumbnail = exifData.compressedThumbnail
val stripList = exifData.stripList
when {
compressedThumbnail != null -> {
if(ifd1 == null) {
ifd1 = IfdData(IfdData.TYPE_IFD_1)
exifData.addIfdData(ifd1)
}
val offsetTag =
mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT}")
ifd1.setTag(offsetTag)
val lengthTag =
mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH}")
lengthTag.setValue(compressedThumbnail.size)
ifd1.setTag(lengthTag)
// Get rid of tags for uncompressed if they exist.
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS))
}
stripList != null -> {
if(ifd1 == null) {
ifd1 = IfdData(IfdData.TYPE_IFD_1)
exifData.addIfdData(ifd1)
}
val offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_OFFSETS}")
val lengthTag =
mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_BYTE_COUNTS}")
val bytesList = LongArray(stripList.size)
stripList.forEachIndexed { index, bytes -> bytesList[index] = bytes.size.toLong() }
lengthTag.setValue(bytesList)
ifd1.setTag(offsetTag)
ifd1.setTag(lengthTag)
// Get rid of tags for compressed if they exist.
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH))
}
ifd1 != null -> {
// Get rid of offset and length tags if there is no thumbnail.
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS))
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH))
}
}
}
private fun calculateAllOffset() : Int {
var offset = TIFF_HEADER_SIZE.toInt()
val ifd0 = exifData.getIfdData(IfdData.TYPE_IFD_0)?.also {
offset = calculateOffsetOfIfd(it, offset)
}
val exifIfd = exifData.getIfdData(IfdData.TYPE_IFD_EXIF)?.also { it ->
ifd0?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD))
?.setValue(offset)
offset = calculateOffsetOfIfd(it, offset)
}
exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY)?.also { it ->
exifIfd?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
?.setValue(offset)
offset = calculateOffsetOfIfd(it, offset)
}
exifData.getIfdData(IfdData.TYPE_IFD_GPS)?.also {
ifd0?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD))
?.setValue(offset)
offset = calculateOffsetOfIfd(it, offset)
}
val ifd1 = exifData.getIfdData(IfdData.TYPE_IFD_1)?.also {
ifd0?.offsetToNextIfd = offset
offset = calculateOffsetOfIfd(it, offset)
}
val compressedThumbnail = exifData.compressedThumbnail
if(compressedThumbnail != null) {
ifd1
?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
?.setValue(offset)
offset += compressedThumbnail.size
} else {
// uncompressed thumbnail
val stripList = exifData.stripList
if(stripList != null) {
val offsets = LongArray(stripList.size)
stripList.forEachIndexed { index, bytes ->
offsets[index] = offset.toLong()
offset += bytes.size
}
ifd1
?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
?.setValue(offsets)
}
}
return offset
}
companion object {
private const val TAG = "ExifOutputStream"
private const val STREAMBUFFER_SIZE = 0x00010000 // 64Kb
private const val STATE_SOI = 0
private const val EXIF_HEADER = 0x45786966
private const val TIFF_HEADER : Short = 0x002A
private const val TIFF_BIG_ENDIAN : Short = 0x4d4d
private const val TIFF_LITTLE_ENDIAN : Short = 0x4949
private const val TAG_SIZE : Short = 12
private const val TIFF_HEADER_SIZE : Short = 8
private const val MAX_EXIF_SIZE = 65535
@Throws(IOException::class)
fun writeTagValue(tag : ExifTag, dataOutputStream : OrderedDataOutputStream) {
when(tag.dataType) {
ExifTag.TYPE_ASCII -> {
val buf = tag.stringByte !!
if(buf.size == tag.componentCount) {
buf[buf.size - 1] = 0
dataOutputStream.write(buf)
} else {
dataOutputStream.write(buf)
dataOutputStream.write(0)
}
}
ExifTag.TYPE_LONG, ExifTag.TYPE_UNSIGNED_LONG -> run {
for(i in 0 until tag.componentCount) {
dataOutputStream.writeInt(tag.getValueAt(i).toInt())
}
}
ExifTag.TYPE_RATIONAL, ExifTag.TYPE_UNSIGNED_RATIONAL -> run {
for(i in 0 until tag.componentCount) {
dataOutputStream.writeRational(tag.getRational(i) !!)
}
}
ExifTag.TYPE_UNDEFINED, ExifTag.TYPE_UNSIGNED_BYTE -> {
val buf = ByteArray(tag.componentCount)
tag.getBytes(buf)
dataOutputStream.write(buf)
}
ExifTag.TYPE_UNSIGNED_SHORT -> {
for(i in 0 until tag.componentCount) {
dataOutputStream.writeShort(tag.getValueAt(i).toShort())
}
}
}
}
}
}