Skip to content

Commit adf23c2

Browse files
dbrantmaskaravivek
authored andcommitted
Improve extremely inefficient darkness-checking logic. (commons-app#2639)
* Improve extremely inefficient darkness-checking logic. * Use ExifInterface from AndroidX instead of android.media. * Fix false-positive detekt check. * Fix false-positive detekt check.
1 parent bc9d83a commit adf23c2

File tree

9 files changed

+36
-98
lines changed

9 files changed

+36
-98
lines changed

app/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ dependencies {
8888
implementation "androidx.browser:browser:1.0.0"
8989
implementation "androidx.cardview:cardview:1.0.0"
9090
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
91+
implementation "androidx.exifinterface:exifinterface:1.0.0"
9192

9293
//swipe_layout
9394
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'

app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.java

+5-16
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,18 @@
22

33
import android.annotation.SuppressLint;
44
import android.content.ContentResolver;
5-
import android.media.ExifInterface;
65
import android.net.Uri;
7-
import android.os.Build;
8-
import android.os.ParcelFileDescriptor;
96
import androidx.annotation.NonNull;
107

118
import java.io.File;
12-
import java.io.FileNotFoundException;
139
import java.io.IOException;
1410
import java.util.List;
1511

1612
import javax.inject.Inject;
1713
import javax.inject.Named;
1814
import javax.inject.Singleton;
1915

16+
import androidx.exifinterface.media.ExifInterface;
2017
import fr.free.nrw.commons.caching.CacheController;
2118
import fr.free.nrw.commons.kvstore.JsonKvStore;
2219
import fr.free.nrw.commons.mwapi.CategoryApi;
@@ -96,22 +93,14 @@ private void findOtherImages(SimilarImageInterface similarImageInterface) {
9693
//Make sure the photos were taken within 20seconds
9794
Timber.d("fild date:" + file.lastModified() + " time of creation" + timeOfCreation);
9895
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
99-
ParcelFileDescriptor descriptor = null;
10096
try {
101-
descriptor = contentResolver.openFileDescriptor(Uri.fromFile(file), "r");
102-
} catch (FileNotFoundException e) {
97+
tempImageObj = new GPSExtractor(contentResolver.openInputStream(Uri.fromFile(file)));
98+
} catch (Exception e) {
10399
e.printStackTrace();
104100
}
105-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
106-
if (descriptor != null) {
107-
tempImageObj = new GPSExtractor(descriptor.getFileDescriptor());
108-
}
109-
} else {
110-
if (filePath != null) {
111-
tempImageObj = new GPSExtractor(file.getAbsolutePath());
112-
}
101+
if (tempImageObj != null) {
102+
tempImageObj = new GPSExtractor(file.getAbsolutePath());
113103
}
114-
115104
if (tempImageObj != null) {
116105
Timber.d("not null fild EXIF" + tempImageObj.imageCoordsExists + " coords" + tempImageObj.getCoords());
117106
if (tempImageObj.getCoords() != null && tempImageObj.imageCoordsExists) {

app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import android.content.ContentResolver;
44
import android.content.Context;
5-
import android.media.ExifInterface;
65
import android.net.Uri;
76
import android.webkit.MimeTypeMap;
87

@@ -17,6 +16,7 @@
1716
import java.security.MessageDigest;
1817
import java.security.NoSuchAlgorithmException;
1918

19+
import androidx.exifinterface.media.ExifInterface;
2020
import timber.log.Timber;
2121

2222
public class FileUtils {

app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package fr.free.nrw.commons.upload;
22

3-
import android.media.ExifInterface;
43
import androidx.annotation.NonNull;
54
import androidx.annotation.Nullable;
6-
import androidx.annotation.RequiresApi;
75

8-
import java.io.FileDescriptor;
96
import java.io.IOException;
7+
import java.io.InputStream;
108

9+
import androidx.exifinterface.media.ExifInterface;
1110
import timber.log.Timber;
1211

1312
/**
@@ -33,17 +32,11 @@ private GPSExtractor(){
3332

3433
}
3534
/**
36-
* Construct from the file descriptor of the image (only for API 24 or newer).
37-
* @param fileDescriptor the file descriptor of the image
35+
* Construct from a stream.
3836
*/
39-
@RequiresApi(24)
40-
GPSExtractor(@NonNull FileDescriptor fileDescriptor) {
41-
try {
42-
ExifInterface exif = new ExifInterface(fileDescriptor);
43-
processCoords(exif);
44-
} catch (IOException | IllegalArgumentException e) {
45-
Timber.w(e);
46-
}
37+
GPSExtractor(@NonNull InputStream stream) throws IOException {
38+
ExifInterface exif = new ExifInterface(stream);
39+
processCoords(exif);
4740
}
4841

4942
/**

app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java

+1-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import fr.free.nrw.commons.mwapi.MediaWikiApi;
1212
import fr.free.nrw.commons.nearby.Place;
13-
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper;
1413
import fr.free.nrw.commons.utils.ImageUtils;
1514
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
1615
import fr.free.nrw.commons.utils.StringUtils;
@@ -30,19 +29,16 @@
3029
@Singleton
3130
public class ImageProcessingService {
3231
private final FileUtilsWrapper fileUtilsWrapper;
33-
private final BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper;
3432
private final ImageUtilsWrapper imageUtilsWrapper;
3533
private final MediaWikiApi mwApi;
3634
private final ReadFBMD readFBMD;
3735
private final EXIFReader EXIFReader;
3836

3937
@Inject
4038
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
41-
BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper,
4239
ImageUtilsWrapper imageUtilsWrapper,
4340
MediaWikiApi mwApi, ReadFBMD readFBMD, EXIFReader EXIFReader) {
4441
this.fileUtilsWrapper = fileUtilsWrapper;
45-
this.bitmapRegionDecoderWrapper = bitmapRegionDecoderWrapper;
4642
this.imageUtilsWrapper = imageUtilsWrapper;
4743
this.mwApi = mwApi;
4844
this.readFBMD = readFBMD;
@@ -161,10 +157,7 @@ private Single<Integer> checkDuplicateImage(String filePath) {
161157
*/
162158
private Single<Integer> checkDarkImage(String filePath) {
163159
Timber.d("Checking for dark image %s", filePath);
164-
return Single.fromCallable(() ->
165-
fileUtilsWrapper.getFileInputStream(filePath))
166-
.map(file -> bitmapRegionDecoderWrapper.newInstance(file, false))
167-
.flatMap(imageUtilsWrapper::checkIfImageIsTooDark);
160+
return imageUtilsWrapper.checkIfImageIsTooDark(filePath);
168161
}
169162

170163
/**

app/src/main/java/fr/free/nrw/commons/utils/BitmapRegionDecoderWrapper.java

-22
This file was deleted.

app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.java

+18-25
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import android.app.WallpaperManager;
44
import android.content.Context;
55
import android.graphics.Bitmap;
6-
import android.graphics.BitmapRegionDecoder;
6+
import android.graphics.BitmapFactory;
77
import android.graphics.Color;
8-
import android.graphics.Rect;
98
import android.net.Uri;
9+
1010
import androidx.annotation.IntDef;
1111
import androidx.annotation.Nullable;
1212

@@ -24,6 +24,7 @@
2424
import java.lang.annotation.Retention;
2525
import java.lang.annotation.RetentionPolicy;
2626

27+
import androidx.exifinterface.media.ExifInterface;
2728
import fr.free.nrw.commons.R;
2829
import fr.free.nrw.commons.location.LatLng;
2930
import timber.log.Timber;
@@ -87,33 +88,26 @@ public class ImageUtils {
8788
}
8889

8990
/**
90-
* @param bitmapRegionDecoder BitmapRegionDecoder for the image we wish to process
91-
* @return IMAGE_OK if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null
91+
* @return IMAGE_OK if image is not too dark
9292
* IMAGE_DARK if image is too dark
9393
*/
94-
static @Result
95-
int checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
96-
if (bitmapRegionDecoder == null) {
97-
Timber.e("Expected bitmapRegionDecoder was null");
98-
return IMAGE_OK;
99-
}
100-
101-
int loadImageHeight = bitmapRegionDecoder.getHeight();
102-
int loadImageWidth = bitmapRegionDecoder.getWidth();
103-
104-
int checkImageTopPosition = 0;
105-
int checkImageLeftPosition = 0;
106-
107-
Timber.v("left: " + checkImageLeftPosition + " right: " + loadImageWidth + " top: " + checkImageTopPosition + " bottom: " + loadImageHeight);
108-
109-
Rect rect = new Rect(checkImageLeftPosition,checkImageTopPosition, loadImageWidth, loadImageHeight);
94+
static @Result int checkIfImageIsTooDark(String imagePath) {
95+
long millis = System.currentTimeMillis();
96+
try {
97+
Bitmap bmp = new ExifInterface(imagePath).getThumbnailBitmap();
98+
if (bmp == null) {
99+
bmp = BitmapFactory.decodeFile(imagePath);
100+
}
110101

111-
Bitmap processBitmap = bitmapRegionDecoder.decodeRegion(rect,null);
102+
if (checkIfImageIsDark(bmp)) {
103+
return IMAGE_DARK;
104+
}
112105

113-
if (checkIfImageIsDark(processBitmap)) {
114-
return IMAGE_DARK;
106+
} catch (Exception e) {
107+
Timber.d(e, "Error while checking image darkness.");
108+
} finally {
109+
Timber.d("Checking image darkness took " + (System.currentTimeMillis() - millis) + " ms.");
115110
}
116-
117111
return IMAGE_OK;
118112
}
119113

@@ -147,7 +141,6 @@ private static boolean checkIfImageIsDark(Bitmap bitmap) {
147141
int bitmapHeight = bitmap.getHeight();
148142

149143
int allPixelsCount = bitmapWidth * bitmapHeight;
150-
Timber.d("total %s", Integer.toString(allPixelsCount));
151144
int numberOfBrightPixels = 0;
152145
int numberOfMediumBrightnessPixels = 0;
153146
double brightPixelThreshold = 0.025 * allPixelsCount;

app/src/main/java/fr/free/nrw/commons/utils/ImageUtilsWrapper.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package fr.free.nrw.commons.utils;
22

3-
import android.graphics.BitmapRegionDecoder;
4-
53
import javax.inject.Inject;
64
import javax.inject.Singleton;
75

@@ -17,9 +15,8 @@ public ImageUtilsWrapper() {
1715

1816
}
1917

20-
public Single<Integer> checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
21-
int isImageDark = ImageUtils.checkIfImageIsTooDark(bitmapRegionDecoder);
22-
return Single.just(isImageDark)
18+
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
19+
return Single.just(ImageUtils.checkIfImageIsTooDark(bitmapPath))
2320
.subscribeOn(Schedulers.computation())
2421
.observeOn(Schedulers.computation());
2522
}

app/src/test/kotlin/fr/free/nrw/commons/upload/ImageProcessingServiceTest.kt

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package fr.free.nrw.commons.upload
22

3-
import android.graphics.BitmapRegionDecoder
43
import android.net.Uri
54
import fr.free.nrw.commons.location.LatLng
65
import fr.free.nrw.commons.mwapi.MediaWikiApi
76
import fr.free.nrw.commons.nearby.Place
8-
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper
97
import fr.free.nrw.commons.utils.ImageUtils
108
import fr.free.nrw.commons.utils.ImageUtilsWrapper
119
import io.reactivex.Single
@@ -23,8 +21,6 @@ class u {
2321
@Mock
2422
internal var fileUtilsWrapper: FileUtilsWrapper? = null
2523
@Mock
26-
internal var bitmapRegionDecoderWrapper: BitmapRegionDecoderWrapper? = null
27-
@Mock
2824
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
2925
@Mock
3026
internal var mwApi: MediaWikiApi? = null
@@ -68,9 +64,7 @@ class u {
6864
`when`(fileUtilsWrapper!!.getGeolocationOfFile(ArgumentMatchers.anyString()))
6965
.thenReturn("latLng")
7066

71-
`when`(bitmapRegionDecoderWrapper!!.newInstance(any(FileInputStream::class.java), anyBoolean()))
72-
.thenReturn(mock(BitmapRegionDecoder::class.java))
73-
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
67+
`when`(imageUtilsWrapper?.checkIfImageIsTooDark(ArgumentMatchers.anyString()))
7468
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
7569

7670
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
@@ -113,7 +107,7 @@ class u {
113107

114108
@Test
115109
fun validateImageForDarkImage() {
116-
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
110+
`when`(imageUtilsWrapper?.checkIfImageIsTooDark(ArgumentMatchers.anyString()))
117111
.thenReturn(Single.just(ImageUtils.IMAGE_DARK))
118112
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
119113
assertEquals(ImageUtils.IMAGE_DARK, validateImage.blockingGet())

0 commit comments

Comments
 (0)