Centralize JPEG compression to a single function

This commit is contained in:
Pierre-Yves Nicolas
2026-03-22 18:09:12 +01:00
parent f336680a69
commit a9edfa2b87
3 changed files with 56 additions and 36 deletions

View File

@@ -108,17 +108,7 @@ fun decodeJpeg(jpegBytes: ByteArray): Mat? {
} }
fun encodeJpeg(mat: Mat, jpegQuality: Int): ByteArray? { fun encodeJpeg(mat: Mat, jpegQuality: Int): ByteArray? {
val params = MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, jpegQuality.coerceIn(0, 100)) return runCatching {
val encoded = MatOfByte() org.fairscan.imageprocessing.encodeJpeg(mat, jpegQuality)
val ok = Imgcodecs.imencode(".jpg", mat, encoded, params) }.getOrNull()
params.release()
if (!ok) {
encoded.release()
return null
}
val result = encoded.toArray()
encoded.release()
return result
} }

View File

@@ -14,45 +14,55 @@
*/ */
package org.fairscan.app.platform package org.fairscan.app.platform
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.core.graphics.scale
import org.fairscan.app.data.ImageTransformations import org.fairscan.app.data.ImageTransformations
import org.opencv.core.MatOfInt import org.fairscan.imageprocessing.encodeJpeg
import org.opencv.core.Mat
import org.opencv.core.Size
import org.opencv.imgcodecs.Imgcodecs import org.opencv.imgcodecs.Imgcodecs
import org.opencv.imgproc.Imgproc
import java.io.File import java.io.File
import kotlin.math.min import kotlin.math.min
class OpenCvTransformations : ImageTransformations { class OpenCvTransformations : ImageTransformations {
override fun rotate( override fun rotate(
inputFile: File, inputFile: File,
outputFile: File, outputFile: File,
rotationDegrees: Int, rotationDegrees: Int,
jpegQuality: Int jpegQuality: Int
) { ) {
val src = Imgcodecs.imread(inputFile.absolutePath) transform(inputFile, outputFile, jpegQuality) {
require(!src.empty()) { "Could not load image from ${inputFile.absolutePath}" } org.fairscan.imageprocessing.rotate(it, rotationDegrees)
val dst = org.fairscan.imageprocessing.rotate(src, rotationDegrees)
val params = MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, jpegQuality)
if (!Imgcodecs.imwrite(outputFile.absolutePath, dst, params)) {
throw RuntimeException("Could not write image to ${outputFile.absolutePath}")
} }
params.release()
src.release()
dst.release()
} }
override fun resize(inputFile: File, outputFile: File, maxSize: Int) { override fun resize(inputFile: File, outputFile: File, maxSize: Int) {
val bitmap = BitmapFactory.decodeFile(inputFile.absolutePath) transform(inputFile, outputFile, 85) { src ->
val ratio = min(maxSize.toFloat() / bitmap.width, maxSize.toFloat() / bitmap.height) val ratio = min(maxSize.toFloat() / src.width(), maxSize.toFloat() / src.height())
val newW = (bitmap.width * ratio).toInt() val newW = (src.width() * ratio).toDouble()
val newH = (bitmap.height * ratio).toInt() val newH = (src.height() * ratio).toDouble()
val scaled = bitmap.scale(newW, newH) val scaled = Mat()
outputFile.outputStream().use { Imgproc.resize(src, scaled, Size(newW, newH))
scaled.compress(Bitmap.CompressFormat.JPEG, 85, it) scaled
}
}
private fun transform(
inputFile: File,
outputFile: File,
jpegQuality: Int,
transform: (Mat) -> Mat,
) {
val input = Imgcodecs.imread(inputFile.absolutePath)
var output: Mat? = null
try {
require(!input.empty()) { "Could not load image from ${inputFile.absolutePath}" }
output = transform.invoke(input)
val outputBytes = encodeJpeg(output, jpegQuality)
outputFile.writeBytes(outputBytes)
} finally {
input.release()
output?.release()
} }
} }
} }

View File

@@ -15,8 +15,12 @@
package org.fairscan.imageprocessing package org.fairscan.imageprocessing
import org.opencv.core.Mat import org.opencv.core.Mat
import org.opencv.core.MatOfByte
import org.opencv.core.MatOfInt
import org.opencv.core.Size import org.opencv.core.Size
import org.opencv.imgcodecs.Imgcodecs
import org.opencv.imgproc.Imgproc import org.opencv.imgproc.Imgproc
import java.io.IOException
import kotlin.math.sqrt import kotlin.math.sqrt
fun resizeForMaxPixels(img: Mat, maxPixels: Double): Mat { fun resizeForMaxPixels(img: Mat, maxPixels: Double): Mat {
@@ -30,3 +34,19 @@ fun resizeForMaxPixels(img: Mat, maxPixels: Double): Mat {
Imgproc.resize(img, resizedImg, size, 0.0, 0.0, Imgproc.INTER_AREA) Imgproc.resize(img, resizedImg, size, 0.0, 0.0, Imgproc.INTER_AREA)
return resizedImg return resizedImg
} }
fun encodeJpeg(mat: Mat, jpegQuality: Int): ByteArray {
val params = MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, jpegQuality.coerceIn(0, 100))
val encoded = MatOfByte()
val ok = Imgcodecs.imencode(".jpg", mat, encoded, params)
params.release()
if (!ok) {
encoded.release()
throw IOException("Failed to encode JPEG")
}
val result = encoded.toArray()
encoded.release()
return result
}