Pith - varnish
package com.vgmlr.varnish

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ImageDecoder
import android.graphics.Rect
import android.media.MediaMetadataRetriever
import androidx.core.graphics.createBitmap
import androidx.exifinterface.media.ExifInterface
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.ConcurrentHashMap

object VarnishCodec {
    private val EXIF_TAGS = listOf(
        ExifInterface.TAG_DATETIME,
        ExifInterface.TAG_DATETIME_ORIGINAL,
        ExifInterface.TAG_DATETIME_DIGITIZED,
        ExifInterface.TAG_OFFSET_TIME,
        ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
        ExifInterface.TAG_OFFSET_TIME_DIGITIZED,
        ExifInterface.TAG_SUBSEC_TIME,
        ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
        ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
        ExifInterface.TAG_MAKE,
        ExifInterface.TAG_MODEL,
        ExifInterface.TAG_SOFTWARE,
        ExifInterface.TAG_ARTIST,
        ExifInterface.TAG_COPYRIGHT,
        ExifInterface.TAG_IMAGE_DESCRIPTION,
        ExifInterface.TAG_USER_COMMENT,
        ExifInterface.TAG_F_NUMBER,
        ExifInterface.TAG_APERTURE_VALUE,
        ExifInterface.TAG_EXPOSURE_TIME,
        ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
        ExifInterface.TAG_EXPOSURE_PROGRAM,
        ExifInterface.TAG_EXPOSURE_MODE,
        ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
        ExifInterface.TAG_FOCAL_LENGTH,
        ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM,
        ExifInterface.TAG_FLASH,
        ExifInterface.TAG_WHITE_BALANCE,
        ExifInterface.TAG_METERING_MODE,
        ExifInterface.TAG_LENS_MAKE,
        ExifInterface.TAG_LENS_MODEL,
        ExifInterface.TAG_GPS_LATITUDE,
        ExifInterface.TAG_GPS_LATITUDE_REF,
        ExifInterface.TAG_GPS_LONGITUDE,
        ExifInterface.TAG_GPS_LONGITUDE_REF,
        ExifInterface.TAG_GPS_ALTITUDE,
        ExifInterface.TAG_GPS_ALTITUDE_REF,
        ExifInterface.TAG_GPS_TIMESTAMP,
        ExifInterface.TAG_GPS_DATESTAMP,
        ExifInterface.TAG_GPS_PROCESSING_METHOD
    )

    private val runtimes = ConcurrentHashMap<String, Long>()

    fun runtime(path: String, modified: Long): Long? {
        val k = "$modified:$path"
        runtimes[k]?.let { return it }
        val ms = runCatching {
            MediaMetadataRetriever().use { r ->
                r.setDataSource(path)
                r.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull()
            }
        }.getOrNull() ?: return null
        runtimes[k] = ms
        return ms
    }

    fun bounds(f: File): Pair<Int, Int>? {
        val o = BitmapFactory.Options().apply { inJustDecodeBounds = true }
        runCatching { BitmapFactory.decodeFile(f.absolutePath, o) }
        if (o.outWidth <= 0 || o.outHeight <= 0) return null
        val rot = runCatching { ExifInterface(f).rotationDegrees }.getOrDefault(0)
        return if (rot == 90 || rot == 270) o.outHeight to o.outWidth else o.outWidth to o.outHeight
    }

    fun crop(src: File, rect: Rect, format: OutFormat, quality: Int, keepExif: Boolean): File? {
        val dir = src.parentFile ?: return null
        val full = decode(src) ?: return null
        val r = Rect(rect)
        if (!r.intersect(0, 0, full.width, full.height) || r.isEmpty) {
            full.recycle()
            return null
        }
        val cut = Bitmap.createBitmap(full, r.left, r.top, r.width(), r.height())
        val out = if (format == OutFormat.JPG && cut.hasAlpha()) flatten(cut) else cut
        val dst = freeFile(dir, "${src.nameWithoutExtension}_cropped", format.ext)
        val ok = runCatching {
            FileOutputStream(dst).use { out.compress(format.compress, quality, it) }
        }.getOrDefault(false)
        listOf(out, cut, full).forEach { it.recycle() }
        if (!ok) {
            dst.delete()
            return null
        }
        if (keepExif) copyExif(src, dst, r.width(), r.height())
        return dst
    }

    private fun decode(f: File): Bitmap? = runCatching {
        ImageDecoder.decodeBitmap(ImageDecoder.createSource(f)) { d, _, _ ->
            d.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
        }
    }.getOrNull()

    private fun flatten(b: Bitmap): Bitmap {
        val o = createBitmap(b.width, b.height)
        Canvas(o).apply {
            drawColor(Color.WHITE)
            drawBitmap(b, 0f, 0f, null)
        }
        return o
    }

    private fun copyExif(src: File, dst: File, w: Int, h: Int) {
        runCatching {
            val a = ExifInterface(src)
            val b = ExifInterface(dst)
            EXIF_TAGS.forEach { t -> a.getAttribute(t)?.let { b.setAttribute(t, it) } }
            b.setAttribute(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL.toString())
            b.setAttribute(ExifInterface.TAG_PIXEL_X_DIMENSION, w.toString())
            b.setAttribute(ExifInterface.TAG_PIXEL_Y_DIMENSION, h.toString())
            b.saveAttributes()
        }
    }
}

internal fun freeFile(dir: File, base: String, ext: String): File {
    fun name(n: Int) = (if (n == 0) base else "$base($n)") + if (ext.isEmpty()) "" else ".$ext"
    var n = 0
    while (File(dir, name(n)).exists()) n++
    return File(dir, name(n))
}