Skip to content

Commit ed513d3

Browse files
committed
Us android system to load / save images and reduce OpenCV library to keep only the minimum needed
1 parent e95510d commit ed513d3

File tree

8 files changed

+87
-300
lines changed

8 files changed

+87
-300
lines changed

app/src/main/java/com/dan/perspective/AppFragment.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.dan.perspective
33
import androidx.fragment.app.Fragment
44

55
open class AppFragment(val activity: MainActivity) : Fragment() {
6+
val settings = activity.settings
7+
68
open fun onBack(homeButton: Boolean) {
79
}
810

app/src/main/java/com/dan/perspective/ExifTools.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.dan.perspective
33
import android.content.ContentResolver
44
import androidx.exifinterface.media.ExifInterface
55
import android.net.Uri
6+
import java.io.File
67
import java.io.InputStream
78
import java.lang.Exception
89

@@ -82,7 +83,7 @@ class ExifTools {
8283
ExifInterface.TAG_XMP,
8384
)
8485

85-
fun copyExif(contentResolver: ContentResolver, sourceUri: Uri, destinationFile: String ) {
86+
fun copyExif(contentResolver: ContentResolver, sourceUri: Uri, destinationFile: File) {
8687
var inputStream: InputStream? = null
8788

8889
try {
@@ -101,12 +102,14 @@ class ExifTools {
101102
exifDestination.saveAttributes()
102103
}
103104
} catch(e: Exception) {
105+
e.printStackTrace()
104106
}
105107

106108
//clean up
107109
try {
108110
inputStream?.close()
109111
} catch (e: Exception) {
112+
e.printStackTrace()
110113
}
111114
}
112115
}

app/src/main/java/com/dan/perspective/MainFragment.kt

Lines changed: 47 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.dan.perspective
22

33
import android.content.Intent
44
import android.graphics.Bitmap
5+
import android.graphics.BitmapFactory
56
import android.graphics.Point
67
import android.graphics.PointF
78
import android.media.MediaScannerConnection
@@ -17,10 +18,8 @@ import org.opencv.core.Core.mean
1718
import org.opencv.core.Core.minMaxLoc
1819
import org.opencv.core.CvType.*
1920
import org.opencv.core.Mat
20-
import org.opencv.core.MatOfInt
2121
import org.opencv.core.Scalar
2222
import org.opencv.core.Size
23-
import org.opencv.imgcodecs.Imgcodecs
2423
import org.opencv.imgproc.Imgproc.*
2524
import org.opencv.xphoto.Xphoto
2625
import java.io.File
@@ -39,9 +38,6 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
3938

4039
const val INTENT_OPEN_IMAGE = 2
4140

42-
const val ALPHA_8_TO_16 = 256.0
43-
const val ALPHA_16_TO_8 = 1.0 / ALPHA_8_TO_16
44-
4541
const val AUTO_DETECT_WORK_SIZE = 1024
4642

4743
fun show(activity: MainActivity) {
@@ -65,7 +61,7 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
6561
menuSave?.isEnabled = !inputImage.empty()
6662

6763
menuPrevPerspective = menu.findItem(R.id.prevPerspective)
68-
menuPrevPerspective?.isEnabled = !inputImage.empty() && activity.settings.prevHeight > 0
64+
menuPrevPerspective?.isEnabled = !inputImage.empty() && settings.prevHeight > 0
6965
}
7066

7167
override fun onDestroyOptionsMenu() {
@@ -92,24 +88,24 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
9288
}
9389

9490
R.id.prevPerspective -> {
95-
if (activity.settings.prevHeight < 1) return true
91+
if (settings.prevHeight < 1) return true
9692
if (inputImage.empty()) return true
9793

9894
binding.imageEdit.setPerspective(
99-
PointF(activity.settings.prevLeftTopX * inputImage.width() / activity.settings.prevWidth,
100-
activity.settings.prevLeftTopY * inputImage.height() / activity.settings.prevHeight
95+
PointF(settings.prevLeftTopX * inputImage.width() / settings.prevWidth,
96+
settings.prevLeftTopY * inputImage.height() / settings.prevHeight
10197
),
10298
PointF(
103-
activity.settings.prevRightTopX * inputImage.width() / activity.settings.prevWidth,
104-
activity.settings.prevRightTopY * inputImage.height() / activity.settings.prevHeight
99+
settings.prevRightTopX * inputImage.width() / settings.prevWidth,
100+
settings.prevRightTopY * inputImage.height() / settings.prevHeight
105101
),
106102
PointF(
107-
activity.settings.prevLeftBottomX * inputImage.width() / activity.settings.prevWidth,
108-
activity.settings.prevLeftBottomY * inputImage.height() / activity.settings.prevHeight
103+
settings.prevLeftBottomX * inputImage.width() / settings.prevWidth,
104+
settings.prevLeftBottomY * inputImage.height() / settings.prevHeight
109105
),
110106
PointF(
111-
activity.settings.prevRightBottomX * inputImage.width() / activity.settings.prevWidth,
112-
activity.settings.prevRightBottomY * inputImage.height() / activity.settings.prevHeight
107+
settings.prevRightBottomX * inputImage.width() / settings.prevWidth,
108+
settings.prevRightBottomY * inputImage.height() / settings.prevHeight
113109
)
114110
)
115111

@@ -139,28 +135,6 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
139135
startActivityForResult(intent, INTENT_OPEN_IMAGE)
140136
}
141137

142-
private fun convertToDepth(image: Mat, depth: Int) : Mat {
143-
when( depth ) {
144-
Settings.DEPTH_8_BITS -> {
145-
if (CV_16UC3 == image.type()) {
146-
val newImage = Mat()
147-
image.convertTo(newImage, CV_8UC3, ALPHA_16_TO_8)
148-
return newImage
149-
}
150-
}
151-
152-
Settings.DEPTH_16_BITS -> {
153-
if (CV_8UC3 == image.type()) {
154-
val newImage = Mat()
155-
image.convertTo(newImage, CV_16UC3, ALPHA_8_TO_16)
156-
return newImage
157-
}
158-
}
159-
}
160-
161-
return image
162-
}
163-
164138
private fun matToBitmap(image: Mat): Bitmap? {
165139
if (image.empty()) return null
166140

@@ -170,7 +144,7 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
170144
Bitmap.Config.ARGB_8888
171145
)
172146

173-
Utils.matToBitmap( convertToDepth( image, Settings.DEPTH_8_BITS ) , bitmap)
147+
Utils.matToBitmap( image, bitmap)
174148
return bitmap
175149
}
176150

@@ -220,93 +194,70 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
220194
binding.checkBoxInpaint.isEnabled = enabled
221195

222196
menuSave?.isEnabled = enabled
223-
menuPrevPerspective?.isEnabled = enabled && activity.settings.prevHeight > 0
197+
menuPrevPerspective?.isEnabled = enabled && settings.prevHeight > 0
224198
}
225199

226200
private fun loadImage(uri: Uri) : Mat {
227-
// Can't create MatOfByte from kotlin ByteArray, but works correctly from java byte[]
228-
val image = OpenCVLoadImageFromUri.load(uri, activity.contentResolver)
229-
if (null == image || image.empty()) return Mat()
230-
231-
inputUri = uri
232-
val imageRGB = Mat()
233-
234-
when(image.type()) {
235-
CV_8UC3, CV_16UC3 -> cvtColor(image, imageRGB, COLOR_BGR2RGB)
236-
CV_8UC4, CV_16UC4 -> cvtColor(image, imageRGB, COLOR_BGRA2RGB)
237-
else -> return Mat()
201+
try {
202+
val inputStream = requireContext().contentResolver.openInputStream(uri) ?: return Mat()
203+
val bitmap = BitmapFactory.decodeStream(inputStream)
204+
val image = Mat()
205+
Utils.bitmapToMat(bitmap, image)
206+
inputStream.close()
207+
return image
208+
} catch (e: Exception) {
209+
e.printStackTrace()
238210
}
239211

240-
return convertToDepth(imageRGB, activity.settings.engineDepth)
212+
return Mat()
241213
}
242214

243215
private fun saveImageAsync() {
244216
if (inputImage.empty()) return
245217

246218
warpImageAsync( binding.checkBoxInpaint.isChecked )
247219
if (outputImage.empty()) return
220+
val bitmap = matToBitmap(outputImage) ?: return
248221

249222
setBusyDialogTitleAsync(MSG_SAVE)
250223

251-
val outputExtension = activity.settings.outputExtension()
252-
253224
try {
254-
var fileName = "${outputName}.${outputExtension}"
255-
var fileFullPath = Settings.SAVE_FOLDER + "/" + fileName
225+
var fileName = "${outputName}.jpg"
226+
var file = File(Settings.SAVE_FOLDER, fileName)
256227
var counter = 0
257-
while (File(fileFullPath).exists() && counter < 998) {
228+
while (file.exists() && counter < 998) {
258229
counter++
259230
val counterStr = "%03d".format(counter)
260-
fileName = "${outputName}_${counterStr}.${outputExtension}"
261-
fileFullPath = Settings.SAVE_FOLDER + "/" + fileName
231+
fileName = "${outputName}_${counterStr}.jpg"
232+
file = File(Settings.SAVE_FOLDER, fileName)
262233
}
263234

264-
val outputRGB = Mat()
265-
cvtColor(outputImage, outputRGB, COLOR_BGR2RGB)
266-
267-
var outputDepth = Settings.DEPTH_AUTO
268-
269-
if (Settings.OUTPUT_TYPE_JPEG == activity.settings.outputType
270-
|| (Settings.OUTPUT_TYPE_PNG == activity.settings.outputType && Settings.DEPTH_8_BITS == activity.settings.pngDepth)
271-
|| (Settings.OUTPUT_TYPE_TIFF == activity.settings.outputType && Settings.DEPTH_8_BITS == activity.settings.tiffDepth)
272-
) {
273-
outputDepth = Settings.DEPTH_8_BITS
274-
} else if ((Settings.OUTPUT_TYPE_PNG == activity.settings.outputType && Settings.DEPTH_16_BITS == activity.settings.pngDepth)
275-
|| (Settings.OUTPUT_TYPE_TIFF == activity.settings.outputType && Settings.DEPTH_16_BITS == activity.settings.tiffDepth)
276-
) {
277-
outputDepth = Settings.DEPTH_16_BITS
278-
}
279-
280-
File(fileFullPath).parentFile?.mkdirs()
281-
282-
val outputParams = MatOfInt()
283-
284-
if (Settings.OUTPUT_TYPE_JPEG == activity.settings.outputType) {
285-
outputParams.fromArray(Imgcodecs.IMWRITE_JPEG_QUALITY, activity.settings.jpegQuality)
286-
}
235+
file.parentFile?.mkdirs()
287236

288-
Imgcodecs.imwrite(fileFullPath, convertToDepth(outputRGB, outputDepth), outputParams)
237+
val outputStream = file.outputStream()
238+
bitmap.compress(Bitmap.CompressFormat.JPEG, settings.jpegQuality, outputStream)
239+
outputStream.close()
289240

290241
inputUri?.let { uri ->
291-
ExifTools.copyExif( activity.contentResolver, uri, fileFullPath )
242+
ExifTools.copyExif( activity.contentResolver, uri, file )
292243
}
293244

294245
runOnUiThread {
295246
//Add it to gallery
296-
MediaScannerConnection.scanFile(context, arrayOf(fileFullPath), null, null)
247+
MediaScannerConnection.scanFile(context, arrayOf(file.absolutePath), null, null)
297248

298249
val perspectivePoints = binding.imageEdit.getPerspective()
299-
activity.settings.prevWidth = outputImage.width()
300-
activity.settings.prevHeight = outputImage.height()
301-
activity.settings.prevLeftTopX = perspectivePoints.pointLeftTop.x
302-
activity.settings.prevLeftTopY = perspectivePoints.pointLeftTop.y
303-
activity.settings.prevRightTopX = perspectivePoints.pointRightTop.x
304-
activity.settings.prevRightTopY = perspectivePoints.pointRightTop.y
305-
activity.settings.prevLeftBottomX = perspectivePoints.pointLeftBottom.x
306-
activity.settings.prevLeftBottomY = perspectivePoints.pointLeftBottom.y
307-
activity.settings.prevRightBottomX = perspectivePoints.pointRightBottom.x
308-
activity.settings.prevRightBottomY = perspectivePoints.pointRightBottom.y
309-
activity.settings.saveProperties()
250+
settings.prevWidth = outputImage.width()
251+
settings.prevHeight = outputImage.height()
252+
settings.prevLeftTopX = perspectivePoints.pointLeftTop.x
253+
settings.prevLeftTopY = perspectivePoints.pointLeftTop.y
254+
settings.prevRightTopX = perspectivePoints.pointRightTop.x
255+
settings.prevRightTopY = perspectivePoints.pointRightTop.y
256+
settings.prevLeftBottomX = perspectivePoints.pointLeftBottom.x
257+
settings.prevLeftBottomY = perspectivePoints.pointLeftBottom.y
258+
settings.prevRightBottomX = perspectivePoints.pointRightBottom.x
259+
settings.prevRightBottomY = perspectivePoints.pointRightBottom.y
260+
settings.saveProperties()
310261

311262
menuPrevPerspective?.isEnabled = true
312263
}
@@ -433,7 +384,7 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
433384

434385
val image = Mat()
435386
resize(
436-
convertToDepth( inputImage, Settings.DEPTH_8_BITS ),
387+
inputImage,
437388
image,
438389
Size( AUTO_DETECT_WORK_SIZE.toDouble(), AUTO_DETECT_WORK_SIZE.toDouble()) ,
439390
0.0,

app/src/main/java/com/dan/perspective/OpenCVLoadImageFromUri.java

Lines changed: 0 additions & 28 deletions
This file was deleted.

app/src/main/java/com/dan/perspective/Settings.kt

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.dan.perspective
44
import android.app.Activity
55
import android.content.Context
66
import android.os.Environment
7+
import java.io.File
78
import kotlin.reflect.KMutableProperty
89
import kotlin.reflect.KVisibility
910
import kotlin.reflect.full.createType
@@ -15,27 +16,11 @@ Settings: all public var fields will be saved
1516
class Settings( private val activity: Activity) {
1617

1718
companion object {
18-
val SAVE_FOLDER = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath + "/Perspective"
19+
val SAVE_FOLDER = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Perspective")
1920
const val DEFAULT_NAME = "output"
20-
21-
const val EXT_JPEG = "jpeg"
22-
const val EXT_PNG = "png"
23-
const val EXT_TIFF = "tiff"
24-
25-
const val OUTPUT_TYPE_JPEG = 0
26-
const val OUTPUT_TYPE_PNG = 1
27-
const val OUTPUT_TYPE_TIFF = 2
28-
29-
const val DEPTH_AUTO = 0
30-
const val DEPTH_8_BITS = 1
31-
const val DEPTH_16_BITS = 2
3221
}
3322

34-
var outputType = OUTPUT_TYPE_PNG
3523
var jpegQuality = 95
36-
var pngDepth = DEPTH_AUTO
37-
var tiffDepth = DEPTH_AUTO
38-
var engineDepth = DEPTH_AUTO
3924
var hapticFeedback = true
4025
var prevLeftTopX = -1f
4126
var prevLeftTopY = -1f
@@ -90,12 +75,4 @@ class Settings( private val activity: Activity) {
9075

9176
editor.apply()
9277
}
93-
94-
fun outputExtension() : String {
95-
return when(outputType) {
96-
OUTPUT_TYPE_PNG -> EXT_PNG
97-
OUTPUT_TYPE_TIFF -> EXT_TIFF
98-
else -> EXT_JPEG
99-
}
100-
}
10178
}

0 commit comments

Comments
 (0)