Skip to content

Commit 8ef05fb

Browse files
committed
Add Long Exposure: light & dark modes
1 parent 20d7b71 commit 8ef05fb

4 files changed

Lines changed: 88 additions & 45 deletions

File tree

app/src/main/cpp/native-lib.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,50 @@ Java_com_dan_mergephotos_MainFragment_00024Companion_makeLongExposureNearestNati
125125
return makeLongExposureNearestNative(images, averageImage, outputImage);
126126
}
127127

128+
JNIEXPORT jboolean JNICALL
129+
Java_com_dan_mergephotos_MainFragment_00024Companion_makeLongExposureLightOrDarkNative(
130+
JNIEnv */*env*/, jobject /*thiz*/, jlong images_nativeObj, jlong outputImage_nativeObj, jboolean light) {
131+
132+
std::vector<Mat> images;
133+
Mat &imagesAsMat = *((Mat *) images_nativeObj);
134+
Mat_to_vector_Mat(imagesAsMat, images);
135+
Mat &outputImage = *((Mat *) outputImage_nativeObj);
136+
137+
std::vector<const Point3_<uchar>*> imageIterators;
138+
if (images.size() < 2) return false;
139+
140+
for (const auto& image: images) {
141+
if (!image.isContinuous()) return false;
142+
imageIterators.push_back(image.ptr<const Point3_<uchar>>(0));
143+
}
144+
145+
outputImage.create(images[0].rows, images[0].cols, images[0].type());
146+
if (outputImage.empty()) return false;
147+
148+
auto outputImageIt = outputImage.ptr<Point3_<uchar>>(0);
149+
int coef = light ? 1 : -1;
150+
151+
for (int row = 0; row < images[0].rows; row++) {
152+
for (int col = 0; col < images[0].cols; col++, outputImageIt++) {
153+
const Point3_<uchar>* nearestImageIt = nullptr;
154+
int bestValue = 0;
155+
156+
for (auto& imageIt: imageIterators) {
157+
int value = coef * (imageIt->x + imageIt->x + imageIt->z);
158+
159+
if (nullptr == nearestImageIt || bestValue < value) {
160+
bestValue = value;
161+
nearestImageIt = imageIt;
162+
}
163+
164+
imageIt++;
165+
}
166+
167+
if (nullptr != nearestImageIt) *outputImageIt = *nearestImageIt;
168+
}
169+
}
170+
171+
return true;
172+
}
173+
128174
}

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

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ import org.opencv.android.Utils
1717
import org.opencv.calib3d.Calib3d
1818
import org.opencv.core.*
1919
import org.opencv.video.Video.calcOpticalFlowPyrLK
20-
import org.opencv.features2d.BFMatcher
21-
import org.opencv.features2d.ORB
2220
import org.opencv.imgproc.Imgproc
2321
import org.opencv.imgproc.Imgproc.INTER_LANCZOS4
22+
import org.opencv.imgproc.Imgproc.INTER_NEAREST
2423
import org.opencv.photo.Photo
2524
import org.opencv.utils.Converters
2625
import java.io.File
@@ -55,8 +54,23 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
5554
)
5655
}
5756

57+
private fun makeLongExposureLightOrDark(
58+
images: List<Mat>,
59+
outputImage: Mat,
60+
light: Boolean
61+
): Boolean {
62+
if (images.size < 3) return false
63+
val imagesMat = Converters.vector_Mat_to_Mat(images)
64+
return makeLongExposureLightOrDarkNative(
65+
imagesMat.nativeObj,
66+
outputImage.nativeObj,
67+
light
68+
)
69+
}
70+
5871
private external fun makePanoramaNative(images: Long, panorama: Long, projection: Int): Boolean
5972
private external fun makeLongExposureNearestNative(images: Long, averageImage: Long, outputImage: Long): Boolean
73+
private external fun makeLongExposureLightOrDarkNative(images: Long, outputImage: Long, light: Boolean): Boolean
6074

6175
fun show(activity: MainActivity) {
6276
activity.pushView("Merge Photos", MainFragment(activity))
@@ -227,7 +241,7 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
227241
image, imageSmall,
228242
Size(widthSmall.toDouble(), heightSmall.toDouble()),
229243
0.0, 0.0,
230-
if (nearest) Imgproc.INTER_NEAREST else Imgproc.INTER_LANCZOS4
244+
if (nearest) INTER_NEAREST else INTER_LANCZOS4
231245
)
232246
return imageSmall
233247
}
@@ -269,27 +283,6 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
269283
return Pair(outputList.toList(), filePrefix)
270284
}
271285

272-
private fun toGrayImage(image: Mat): Mat {
273-
val grayMat = Mat()
274-
Imgproc.cvtColor(image, grayMat, Imgproc.COLOR_BGR2GRAY)
275-
return grayMat
276-
}
277-
278-
private fun toNormalizedImage(image: Mat): Mat {
279-
val grayMat = toGrayImage(image)
280-
val normalizedMat = Mat()
281-
Core.normalize(grayMat, normalizedMat, 0.0, 255.0, Core.NORM_MINMAX)
282-
return normalizedMat
283-
}
284-
285-
private fun orbDetectAndCompute(orbDetector: ORB, image: Mat, mask: Mat): Pair<MutableList<KeyPoint>, Mat> {
286-
val normalizedImage = toNormalizedImage(image)
287-
val keyPoints = MatOfKeyPoint()
288-
val descriptors = Mat()
289-
orbDetector.detectAndCompute(normalizedImage, mask, keyPoints, descriptors)
290-
return Pair(keyPoints.toList(), descriptors)
291-
}
292-
293286
private fun alignImages(prefix: String): Pair<List<Mat>, String> {
294287
val inputImages = cache[prefix] ?: mutableListOf()
295288

@@ -390,32 +383,43 @@ class MainFragment(activity: MainActivity) : AppFragment(activity) {
390383
}
391384

392385
private fun mergeLongExposure(prefix: String): Pair<List<Mat>, String> {
393-
val averageImages = calculateAverage(prefix)
386+
val alignImages = binding.checkBoxAlign.isChecked
394387
val mode = binding.longexposureAlgorithm.selectedItemPosition
395388
var resultImages: List<Mat> = listOf()
396389

397390
when(mode) {
398-
Settings.LONG_EXPOSURE_AVERAGE -> resultImages = averageImages
391+
Settings.LONG_EXPOSURE_AVERAGE -> {
392+
val averageImages = calculateAverage(prefix)
393+
resultImages = averageImages
394+
}
399395

400396
Settings.LONG_EXPOSURE_NEAREST_TO_AVERAGE -> {
397+
val averageImages = calculateAverage(prefix)
401398
if (averageImages.isNotEmpty()) {
402-
val alignedImages = cache[prefix + CACHE_IMAGES_ALIGNED_SUFFIX]
403-
if (null != alignedImages) {
399+
val inputImages = if (alignImages) cache[prefix + CACHE_IMAGES_ALIGNED_SUFFIX] else (cache[prefix] ?: listOf())
400+
if (null != inputImages && inputImages.isNotEmpty()) {
404401
val outputImage = Mat()
405402

406-
if (makeLongExposureNearest(
407-
alignedImages,
408-
averageImages[0],
409-
outputImage
410-
)
411-
) {
403+
if (makeLongExposureNearest(inputImages, averageImages[0], outputImage)) {
412404
if (!outputImage.empty()) {
413405
resultImages = listOf(outputImage)
414406
}
415407
}
416408
}
417409
}
418410
}
411+
412+
Settings.LONG_EXPOSURE_LIGHT, Settings.LONG_EXPOSURE_DARK -> {
413+
val inputImages = if (alignImages) alignImages(prefix).first else (cache[prefix] ?: listOf())
414+
if (inputImages.isNotEmpty()) {
415+
val outputImage = Mat()
416+
if (makeLongExposureLightOrDark(inputImages, outputImage, Settings.LONG_EXPOSURE_LIGHT == mode)) {
417+
if (!outputImage.empty()) {
418+
resultImages = listOf(outputImage)
419+
}
420+
}
421+
}
422+
}
419423
}
420424

421425
return Pair(

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class Settings( private val activity: Activity) {
2929

3030
const val LONG_EXPOSURE_AVERAGE = 0
3131
const val LONG_EXPOSURE_NEAREST_TO_AVERAGE = 1
32+
const val LONG_EXPOSURE_LIGHT = 2
33+
const val LONG_EXPOSURE_DARK = 3
3234
}
3335

3436
var mergeMode: Int = MERGE_PANORAMA

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,7 @@
2424
<string-array name="longexposure_algorithms">
2525
<item>Average</item>
2626
<item>Nearest to Average</item>
27-
</string-array>
28-
<string-array name="jpeg_quality">
29-
<item>60</item>
30-
<item>75</item>
31-
<item>90</item>
32-
<item>100</item>
33-
</string-array>
34-
<string-array name="depth">
35-
<item>Auto</item>
36-
<item>8 bits</item>
37-
<item>16 bits</item>
27+
<item>Light</item>
28+
<item>Dark</item>
3829
</string-array>
3930
</resources>

0 commit comments

Comments
 (0)