From a9edfa2b87c8627eedaff727a97c7c438cdb6685 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Sun, 22 Mar 2026 18:09:12 +0100 Subject: [PATCH] Centralize JPEG compression to a single function --- .../fairscan/app/domain/ExportPreparation.kt | 16 +----- .../platform/OpenCvImageTransformations.kt | 56 +++++++++++-------- .../org/fairscan/imageprocessing/Utils.kt | 20 +++++++ 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt b/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt index 1d2c308..bb3369f 100644 --- a/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt +++ b/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt @@ -108,17 +108,7 @@ fun decodeJpeg(jpegBytes: ByteArray): Mat? { } 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() - return null - } - - val result = encoded.toArray() - encoded.release() - return result + return runCatching { + org.fairscan.imageprocessing.encodeJpeg(mat, jpegQuality) + }.getOrNull() } diff --git a/app/src/main/java/org/fairscan/app/platform/OpenCvImageTransformations.kt b/app/src/main/java/org/fairscan/app/platform/OpenCvImageTransformations.kt index 58e7376..bc621b3 100644 --- a/app/src/main/java/org/fairscan/app/platform/OpenCvImageTransformations.kt +++ b/app/src/main/java/org/fairscan/app/platform/OpenCvImageTransformations.kt @@ -14,45 +14,55 @@ */ 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.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.imgproc.Imgproc import java.io.File import kotlin.math.min class OpenCvTransformations : ImageTransformations { + override fun rotate( inputFile: File, outputFile: File, rotationDegrees: Int, jpegQuality: Int ) { - val src = Imgcodecs.imread(inputFile.absolutePath) - require(!src.empty()) { "Could not load image from ${inputFile.absolutePath}" } - - 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}") + transform(inputFile, outputFile, jpegQuality) { + org.fairscan.imageprocessing.rotate(it, rotationDegrees) } - - params.release() - src.release() - dst.release() } override fun resize(inputFile: File, outputFile: File, maxSize: Int) { - val bitmap = BitmapFactory.decodeFile(inputFile.absolutePath) - val ratio = min(maxSize.toFloat() / bitmap.width, maxSize.toFloat() / bitmap.height) - val newW = (bitmap.width * ratio).toInt() - val newH = (bitmap.height * ratio).toInt() - val scaled = bitmap.scale(newW, newH) - outputFile.outputStream().use { - scaled.compress(Bitmap.CompressFormat.JPEG, 85, it) + transform(inputFile, outputFile, 85) { src -> + val ratio = min(maxSize.toFloat() / src.width(), maxSize.toFloat() / src.height()) + val newW = (src.width() * ratio).toDouble() + val newH = (src.height() * ratio).toDouble() + val scaled = Mat() + Imgproc.resize(src, scaled, Size(newW, newH)) + 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() } } } diff --git a/imageprocessing/src/main/java/org/fairscan/imageprocessing/Utils.kt b/imageprocessing/src/main/java/org/fairscan/imageprocessing/Utils.kt index 6371e95..25dfd9f 100644 --- a/imageprocessing/src/main/java/org/fairscan/imageprocessing/Utils.kt +++ b/imageprocessing/src/main/java/org/fairscan/imageprocessing/Utils.kt @@ -15,8 +15,12 @@ package org.fairscan.imageprocessing import org.opencv.core.Mat +import org.opencv.core.MatOfByte +import org.opencv.core.MatOfInt import org.opencv.core.Size +import org.opencv.imgcodecs.Imgcodecs import org.opencv.imgproc.Imgproc +import java.io.IOException import kotlin.math.sqrt 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) 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 +}