mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
Improve extremely inefficient darkness-checking logic. (#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.
This commit is contained in:
parent
bc9d83a47c
commit
adf23c257f
9 changed files with 38 additions and 100 deletions
|
|
@ -88,6 +88,7 @@ dependencies {
|
||||||
implementation "androidx.browser:browser:1.0.0"
|
implementation "androidx.browser:browser:1.0.0"
|
||||||
implementation "androidx.cardview:cardview:1.0.0"
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation "androidx.exifinterface:exifinterface:1.0.0"
|
||||||
|
|
||||||
//swipe_layout
|
//swipe_layout
|
||||||
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
|
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,10 @@ package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.media.ExifInterface;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -17,6 +13,7 @@ import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
import fr.free.nrw.commons.caching.CacheController;
|
import fr.free.nrw.commons.caching.CacheController;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.mwapi.CategoryApi;
|
import fr.free.nrw.commons.mwapi.CategoryApi;
|
||||||
|
|
@ -96,22 +93,14 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
||||||
//Make sure the photos were taken within 20seconds
|
//Make sure the photos were taken within 20seconds
|
||||||
Timber.d("fild date:" + file.lastModified() + " time of creation" + timeOfCreation);
|
Timber.d("fild date:" + file.lastModified() + " time of creation" + timeOfCreation);
|
||||||
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
|
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
|
||||||
ParcelFileDescriptor descriptor = null;
|
|
||||||
try {
|
try {
|
||||||
descriptor = contentResolver.openFileDescriptor(Uri.fromFile(file), "r");
|
tempImageObj = new GPSExtractor(contentResolver.openInputStream(Uri.fromFile(file)));
|
||||||
} catch (FileNotFoundException e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (tempImageObj != null) {
|
||||||
if (descriptor != null) {
|
tempImageObj = new GPSExtractor(file.getAbsolutePath());
|
||||||
tempImageObj = new GPSExtractor(descriptor.getFileDescriptor());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (filePath != null) {
|
|
||||||
tempImageObj = new GPSExtractor(file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tempImageObj != null) {
|
if (tempImageObj != null) {
|
||||||
Timber.d("not null fild EXIF" + tempImageObj.imageCoordsExists + " coords" + tempImageObj.getCoords());
|
Timber.d("not null fild EXIF" + tempImageObj.imageCoordsExists + " coords" + tempImageObj.getCoords());
|
||||||
if (tempImageObj.getCoords() != null && tempImageObj.imageCoordsExists) {
|
if (tempImageObj.getCoords() != null && tempImageObj.imageCoordsExists) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.ExifInterface;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
|
@ -17,6 +16,7 @@ import java.math.BigInteger;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.media.ExifInterface;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -33,17 +32,11 @@ class GPSExtractor {
|
||||||
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Construct from the file descriptor of the image (only for API 24 or newer).
|
* Construct from a stream.
|
||||||
* @param fileDescriptor the file descriptor of the image
|
|
||||||
*/
|
*/
|
||||||
@RequiresApi(24)
|
GPSExtractor(@NonNull InputStream stream) throws IOException {
|
||||||
GPSExtractor(@NonNull FileDescriptor fileDescriptor) {
|
ExifInterface exif = new ExifInterface(stream);
|
||||||
try {
|
processCoords(exif);
|
||||||
ExifInterface exif = new ExifInterface(fileDescriptor);
|
|
||||||
processCoords(exif);
|
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
|
||||||
Timber.w(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import javax.inject.Singleton;
|
||||||
|
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper;
|
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
||||||
import fr.free.nrw.commons.utils.StringUtils;
|
import fr.free.nrw.commons.utils.StringUtils;
|
||||||
|
|
@ -30,7 +29,6 @@ import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ImageProcessingService {
|
public class ImageProcessingService {
|
||||||
private final FileUtilsWrapper fileUtilsWrapper;
|
private final FileUtilsWrapper fileUtilsWrapper;
|
||||||
private final BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper;
|
|
||||||
private final ImageUtilsWrapper imageUtilsWrapper;
|
private final ImageUtilsWrapper imageUtilsWrapper;
|
||||||
private final MediaWikiApi mwApi;
|
private final MediaWikiApi mwApi;
|
||||||
private final ReadFBMD readFBMD;
|
private final ReadFBMD readFBMD;
|
||||||
|
|
@ -38,11 +36,9 @@ public class ImageProcessingService {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
|
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
|
||||||
BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper,
|
|
||||||
ImageUtilsWrapper imageUtilsWrapper,
|
ImageUtilsWrapper imageUtilsWrapper,
|
||||||
MediaWikiApi mwApi, ReadFBMD readFBMD, EXIFReader EXIFReader) {
|
MediaWikiApi mwApi, ReadFBMD readFBMD, EXIFReader EXIFReader) {
|
||||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||||
this.bitmapRegionDecoderWrapper = bitmapRegionDecoderWrapper;
|
|
||||||
this.imageUtilsWrapper = imageUtilsWrapper;
|
this.imageUtilsWrapper = imageUtilsWrapper;
|
||||||
this.mwApi = mwApi;
|
this.mwApi = mwApi;
|
||||||
this.readFBMD = readFBMD;
|
this.readFBMD = readFBMD;
|
||||||
|
|
@ -161,10 +157,7 @@ public class ImageProcessingService {
|
||||||
*/
|
*/
|
||||||
private Single<Integer> checkDarkImage(String filePath) {
|
private Single<Integer> checkDarkImage(String filePath) {
|
||||||
Timber.d("Checking for dark image %s", filePath);
|
Timber.d("Checking for dark image %s", filePath);
|
||||||
return Single.fromCallable(() ->
|
return imageUtilsWrapper.checkIfImageIsTooDark(filePath);
|
||||||
fileUtilsWrapper.getFileInputStream(filePath))
|
|
||||||
.map(file -> bitmapRegionDecoderWrapper.newInstance(file, false))
|
|
||||||
.flatMap(imageUtilsWrapper::checkIfImageIsTooDark);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package fr.free.nrw.commons.utils;
|
|
||||||
|
|
||||||
import android.graphics.BitmapRegionDecoder;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class BitmapRegionDecoderWrapper {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public BitmapRegionDecoderWrapper() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmapRegionDecoder newInstance(FileInputStream file, boolean isSharable) throws IOException {
|
|
||||||
return BitmapRegionDecoder.newInstance(file, isSharable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,10 +3,10 @@ package fr.free.nrw.commons.utils;
|
||||||
import android.app.WallpaperManager;
|
import android.app.WallpaperManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapRegionDecoder;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
|
@ -24,6 +24,7 @@ import java.io.IOException;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
@ -87,33 +88,26 @@ public class ImageUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bitmapRegionDecoder BitmapRegionDecoder for the image we wish to process
|
* @return IMAGE_OK if image is not too dark
|
||||||
* @return IMAGE_OK if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null
|
|
||||||
* IMAGE_DARK if image is too dark
|
* IMAGE_DARK if image is too dark
|
||||||
*/
|
*/
|
||||||
static @Result
|
static @Result int checkIfImageIsTooDark(String imagePath) {
|
||||||
int checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
|
long millis = System.currentTimeMillis();
|
||||||
if (bitmapRegionDecoder == null) {
|
try {
|
||||||
Timber.e("Expected bitmapRegionDecoder was null");
|
Bitmap bmp = new ExifInterface(imagePath).getThumbnailBitmap();
|
||||||
return IMAGE_OK;
|
if (bmp == null) {
|
||||||
|
bmp = BitmapFactory.decodeFile(imagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkIfImageIsDark(bmp)) {
|
||||||
|
return IMAGE_DARK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Timber.d(e, "Error while checking image darkness.");
|
||||||
|
} finally {
|
||||||
|
Timber.d("Checking image darkness took " + (System.currentTimeMillis() - millis) + " ms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int loadImageHeight = bitmapRegionDecoder.getHeight();
|
|
||||||
int loadImageWidth = bitmapRegionDecoder.getWidth();
|
|
||||||
|
|
||||||
int checkImageTopPosition = 0;
|
|
||||||
int checkImageLeftPosition = 0;
|
|
||||||
|
|
||||||
Timber.v("left: " + checkImageLeftPosition + " right: " + loadImageWidth + " top: " + checkImageTopPosition + " bottom: " + loadImageHeight);
|
|
||||||
|
|
||||||
Rect rect = new Rect(checkImageLeftPosition,checkImageTopPosition, loadImageWidth, loadImageHeight);
|
|
||||||
|
|
||||||
Bitmap processBitmap = bitmapRegionDecoder.decodeRegion(rect,null);
|
|
||||||
|
|
||||||
if (checkIfImageIsDark(processBitmap)) {
|
|
||||||
return IMAGE_DARK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return IMAGE_OK;
|
return IMAGE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +141,6 @@ public class ImageUtils {
|
||||||
int bitmapHeight = bitmap.getHeight();
|
int bitmapHeight = bitmap.getHeight();
|
||||||
|
|
||||||
int allPixelsCount = bitmapWidth * bitmapHeight;
|
int allPixelsCount = bitmapWidth * bitmapHeight;
|
||||||
Timber.d("total %s", Integer.toString(allPixelsCount));
|
|
||||||
int numberOfBrightPixels = 0;
|
int numberOfBrightPixels = 0;
|
||||||
int numberOfMediumBrightnessPixels = 0;
|
int numberOfMediumBrightnessPixels = 0;
|
||||||
double brightPixelThreshold = 0.025 * allPixelsCount;
|
double brightPixelThreshold = 0.025 * allPixelsCount;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package fr.free.nrw.commons.utils;
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
import android.graphics.BitmapRegionDecoder;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
|
@ -17,9 +15,8 @@ public class ImageUtilsWrapper {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<Integer> checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
|
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
|
||||||
int isImageDark = ImageUtils.checkIfImageIsTooDark(bitmapRegionDecoder);
|
return Single.just(ImageUtils.checkIfImageIsTooDark(bitmapPath))
|
||||||
return Single.just(isImageDark)
|
|
||||||
.subscribeOn(Schedulers.computation())
|
.subscribeOn(Schedulers.computation())
|
||||||
.observeOn(Schedulers.computation());
|
.observeOn(Schedulers.computation());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import android.graphics.BitmapRegionDecoder
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import fr.free.nrw.commons.location.LatLng
|
import fr.free.nrw.commons.location.LatLng
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
||||||
import fr.free.nrw.commons.nearby.Place
|
import fr.free.nrw.commons.nearby.Place
|
||||||
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper
|
|
||||||
import fr.free.nrw.commons.utils.ImageUtils
|
import fr.free.nrw.commons.utils.ImageUtils
|
||||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper
|
import fr.free.nrw.commons.utils.ImageUtilsWrapper
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
@ -23,8 +21,6 @@ class u {
|
||||||
@Mock
|
@Mock
|
||||||
internal var fileUtilsWrapper: FileUtilsWrapper? = null
|
internal var fileUtilsWrapper: FileUtilsWrapper? = null
|
||||||
@Mock
|
@Mock
|
||||||
internal var bitmapRegionDecoderWrapper: BitmapRegionDecoderWrapper? = null
|
|
||||||
@Mock
|
|
||||||
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
|
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
|
||||||
@Mock
|
@Mock
|
||||||
internal var mwApi: MediaWikiApi? = null
|
internal var mwApi: MediaWikiApi? = null
|
||||||
|
|
@ -68,9 +64,7 @@ class u {
|
||||||
`when`(fileUtilsWrapper!!.getGeolocationOfFile(ArgumentMatchers.anyString()))
|
`when`(fileUtilsWrapper!!.getGeolocationOfFile(ArgumentMatchers.anyString()))
|
||||||
.thenReturn("latLng")
|
.thenReturn("latLng")
|
||||||
|
|
||||||
`when`(bitmapRegionDecoderWrapper!!.newInstance(any(FileInputStream::class.java), anyBoolean()))
|
`when`(imageUtilsWrapper?.checkIfImageIsTooDark(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(mock(BitmapRegionDecoder::class.java))
|
|
||||||
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
|
|
||||||
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
|
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
|
||||||
|
|
||||||
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
|
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
|
||||||
|
|
@ -113,7 +107,7 @@ class u {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun validateImageForDarkImage() {
|
fun validateImageForDarkImage() {
|
||||||
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
|
`when`(imageUtilsWrapper?.checkIfImageIsTooDark(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Single.just(ImageUtils.IMAGE_DARK))
|
.thenReturn(Single.just(ImageUtils.IMAGE_DARK))
|
||||||
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
|
||||||
assertEquals(ImageUtils.IMAGE_DARK, validateImage.blockingGet())
|
assertEquals(ImageUtils.IMAGE_DARK, validateImage.blockingGet())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue