mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-28 05:13:53 +01:00
Merge branch 'master' into master
This commit is contained in:
commit
6ab4520927
48 changed files with 497 additions and 72 deletions
|
|
@ -197,6 +197,10 @@ public class Contribution extends Media {
|
|||
this.localUri = localUri;
|
||||
}
|
||||
|
||||
public void setDecimalCoords(String decimalCoords) {
|
||||
this.decimalCoords = decimalCoords;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String licenseTemplateFor(String license) {
|
||||
switch (license) {
|
||||
|
|
@ -215,6 +219,7 @@ public class Contribution extends Media {
|
|||
case Prefs.Licenses.CC_BY_SA:
|
||||
return "{{self|cc-by-sa-3.0}}";
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unrecognized license value: " + license);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Created by bluesir9 on 16/9/17.
|
||||
*
|
||||
* <p>Responsible for checking if the picture that the user is trying to upload is useful or not. Will attempt to filter
|
||||
* away completely black,fuzzy/blurry pictures(for now).
|
||||
*
|
||||
* <p>todo: Detect selfies?
|
||||
*/
|
||||
|
||||
public class DetectUnwantedPicturesAsync extends AsyncTask<Void, Void, ImageUtils.Result> {
|
||||
|
||||
interface Callback {
|
||||
void onResult(ImageUtils.Result result);
|
||||
}
|
||||
|
||||
private final Callback callback;
|
||||
private final String imageMediaFilePath;
|
||||
|
||||
DetectUnwantedPicturesAsync(String imageMediaFilePath, Callback callback) {
|
||||
this.callback = callback;
|
||||
this.imageMediaFilePath = imageMediaFilePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageUtils.Result doInBackground(Void... voids) {
|
||||
try {
|
||||
Timber.d("FilePath: " + imageMediaFilePath);
|
||||
if (imageMediaFilePath == null) {
|
||||
return ImageUtils.Result.IMAGE_OK;
|
||||
}
|
||||
|
||||
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(imageMediaFilePath,false);
|
||||
|
||||
return ImageUtils.checkIfImageIsTooDark(decoder);
|
||||
} catch (IOException ioe) {
|
||||
Timber.e(ioe, "IO Exception");
|
||||
return ImageUtils.Result.IMAGE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ImageUtils.Result result) {
|
||||
super.onPostExecute(result);
|
||||
//callback to UI so that it can take necessary decision based on the result obtained
|
||||
callback.onResult(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,20 +3,25 @@ package fr.free.nrw.commons.upload;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Date;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
@ -36,6 +41,7 @@ public class FileUtils {
|
|||
@Nullable
|
||||
public static String getPath(Context context, Uri uri) {
|
||||
|
||||
String returnPath = null;
|
||||
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
|
||||
// DocumentProvider
|
||||
|
|
@ -47,7 +53,7 @@ public class FileUtils {
|
|||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
returnPath = Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
}
|
||||
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
|
||||
|
||||
|
|
@ -55,8 +61,9 @@ public class FileUtils {
|
|||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
returnPath = getDataColumn(context, contentUri, null, null);
|
||||
} else if (isMediaDocument(uri)) { // MediaProvider
|
||||
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
|
@ -81,16 +88,55 @@ public class FileUtils {
|
|||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
returnPath = getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
}
|
||||
// MediaStore (and general)
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
return getDataColumn(context, uri, null, null);
|
||||
returnPath = getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
returnPath = uri.getPath();
|
||||
}
|
||||
|
||||
if(returnPath == null) {
|
||||
//fetching path may fail depending on the source URI and all hope is lost
|
||||
//so we will create and use a copy of the file, which seems to work
|
||||
String copyPath = null;
|
||||
try {
|
||||
ParcelFileDescriptor descriptor
|
||||
= context.getContentResolver().openFileDescriptor(uri, "r");
|
||||
if (descriptor != null) {
|
||||
|
||||
SharedPreferences sharedPref = PreferenceManager
|
||||
.getDefaultSharedPreferences(context);
|
||||
boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
|
||||
if (useExtStorage) {
|
||||
copyPath = Environment.getExternalStorageDirectory().toString()
|
||||
+ "/CommonsApp/" + new Date().getTime() + ".jpg";
|
||||
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
||||
newFile.mkdir();
|
||||
FileUtils.copy(
|
||||
descriptor.getFileDescriptor(),
|
||||
copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
copyPath = context.getCacheDir().getAbsolutePath()
|
||||
+ "/" + new Date().getTime() + ".jpg";
|
||||
FileUtils.copy(
|
||||
descriptor.getFileDescriptor(),
|
||||
copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.w(e, "Error in file " + copyPath);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return returnPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -111,7 +157,7 @@ public class FileUtils {
|
|||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
final String column = "_data";
|
||||
final String column = MediaStore.Images.ImageColumns.DATA;
|
||||
final String[] projection = {
|
||||
column
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import android.database.DataSetObserver;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
|
@ -21,6 +23,7 @@ import android.view.inputmethod.InputMethodManager;
|
|||
import android.widget.AdapterView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -69,6 +72,8 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
private MediaDetailPagerFragment mediaDetails;
|
||||
private CategorizationFragment categorizationFragment;
|
||||
|
||||
private boolean locationPermitted = false;
|
||||
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return photosList.get(i);
|
||||
|
|
@ -213,6 +218,14 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
requestAuthToken();
|
||||
|
||||
//TODO: 15/10/17 should location permission be explicitly requested if not provided?
|
||||
//check if location permission is enabled
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
locationPermitted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -258,6 +271,11 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
up.setTag("sequence", i);
|
||||
up.setSource(Contribution.SOURCE_EXTERNAL);
|
||||
up.setMultiple(true);
|
||||
String imageGpsCoordinates = extractImageGpsData(uri);
|
||||
if (imageGpsCoordinates != null) {
|
||||
Timber.d("GPS data for image found!");
|
||||
up.setDecimalCoords(imageGpsCoordinates);
|
||||
}
|
||||
photosList.add(up);
|
||||
}
|
||||
}
|
||||
|
|
@ -287,4 +305,46 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to extract the gps coordinates using exif data or by using the current
|
||||
* location if available for the image who's imageUri has been provided.
|
||||
* @param imageUri The uri of the image who's GPS coordinates data we wish to extract
|
||||
* @return GPS coordinates as a String as is returned by {@link GPSExtractor}
|
||||
*/
|
||||
@Nullable
|
||||
private String extractImageGpsData(Uri imageUri) {
|
||||
Timber.d("Entering extractImagesGpsData");
|
||||
|
||||
if (imageUri == null) {
|
||||
//now why would you do that???
|
||||
return null;
|
||||
}
|
||||
|
||||
GPSExtractor gpsExtractor = null;
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(imageUri,"r");
|
||||
if (fd != null) {
|
||||
gpsExtractor = new GPSExtractor(fd.getFileDescriptor(),this,prefs);
|
||||
}
|
||||
} else {
|
||||
String filePath = FileUtils.getPath(this,imageUri);
|
||||
if (filePath != null) {
|
||||
gpsExtractor = new GPSExtractor(filePath,this,prefs);
|
||||
}
|
||||
}
|
||||
|
||||
if (gpsExtractor != null) {
|
||||
//get image coordinates from exif data or user location
|
||||
return gpsExtractor.getCoords(locationPermitted);
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
Timber.w(fnfe);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.upload;
|
|||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
|
|
@ -18,6 +19,7 @@ import android.support.graphics.drawable.VectorDrawableCompat;
|
|||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
|
@ -47,11 +49,14 @@ import fr.free.nrw.commons.caching.CacheController;
|
|||
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
import fr.free.nrw.commons.mwapi.EventLog;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
@ -287,7 +292,7 @@ public class ShareActivity
|
|||
REQUEST_PERM_ON_CREATE_LOCATION);
|
||||
}
|
||||
}
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
|
||||
|
||||
SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
||||
|
|
@ -311,7 +316,7 @@ public class ShareActivity
|
|||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
backgroundImageView.setImageURI(mediaUri);
|
||||
storagePermitted = true;
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -319,7 +324,7 @@ public class ShareActivity
|
|||
if (grantResults.length >= 1
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
locationPermitted = true;
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -328,12 +333,12 @@ public class ShareActivity
|
|||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
backgroundImageView.setImageURI(mediaUri);
|
||||
storagePermitted = true;
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
}
|
||||
if (grantResults.length >= 2
|
||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
||||
locationPermitted = true;
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -344,7 +349,7 @@ public class ShareActivity
|
|||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
//It is OK to call this at both (1) and (4) because if perm had been granted at
|
||||
//snackbar, user should not be prompted at submit button
|
||||
performPreuploadProcessingOfFile();
|
||||
performPreUploadProcessingOfFile();
|
||||
|
||||
//Uploading only begins if storage permission granted from arrow icon
|
||||
uploadBegins();
|
||||
|
|
@ -355,7 +360,7 @@ public class ShareActivity
|
|||
}
|
||||
}
|
||||
|
||||
private void performPreuploadProcessingOfFile() {
|
||||
private void performPreUploadProcessingOfFile() {
|
||||
if (!useNewPermissions || storagePermitted) {
|
||||
if (!duplicateCheckPassed) {
|
||||
//Test SHA1 of image to see if it matches SHA1 of a file on Commons
|
||||
|
|
@ -370,7 +375,17 @@ public class ShareActivity
|
|||
Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
|
||||
duplicateCheckPassed = (result == DUPLICATE_PROCEED
|
||||
|| result == NO_DUPLICATE);
|
||||
}, mwApi);
|
||||
/*
|
||||
TODO: 16/9/17 should we run DetectUnwantedPicturesAsync if DUPLICATE_PROCEED is returned? Since that means
|
||||
we are processing images that are already on server???...
|
||||
*/
|
||||
|
||||
if (duplicateCheckPassed) {
|
||||
//image can be uploaded, so now check if its a useless picture or not
|
||||
performUnwantedPictureDetectionProcess();
|
||||
}
|
||||
|
||||
},mwApi);
|
||||
fileAsyncTask.execute();
|
||||
} catch (IOException e) {
|
||||
Timber.d(e, "IO Exception: ");
|
||||
|
|
@ -384,6 +399,37 @@ public class ShareActivity
|
|||
}
|
||||
}
|
||||
|
||||
private void performUnwantedPictureDetectionProcess() {
|
||||
String imageMediaFilePath = FileUtils.getPath(this,mediaUri);
|
||||
DetectUnwantedPicturesAsync detectUnwantedPicturesAsync = new DetectUnwantedPicturesAsync(imageMediaFilePath, result -> {
|
||||
|
||||
if (result != ImageUtils.Result.IMAGE_OK) {
|
||||
//show appropriate error message
|
||||
String errorMessage = result == ImageUtils.Result.IMAGE_DARK ? getString(R.string.upload_image_too_dark) : getString(R.string.upload_image_blurry);
|
||||
AlertDialog.Builder errorDialogBuilder = new AlertDialog.Builder(this);
|
||||
errorDialogBuilder.setMessage(errorMessage);
|
||||
errorDialogBuilder.setTitle(getString(R.string.warning));
|
||||
errorDialogBuilder.setPositiveButton(getString(R.string.no), (dialogInterface, i) -> {
|
||||
//user does not wish to upload the picture, take them back to ContributionsActivity
|
||||
Intent intent = new Intent(ShareActivity.this, ContributionsActivity.class);
|
||||
dialogInterface.dismiss();
|
||||
startActivity(intent);
|
||||
});
|
||||
errorDialogBuilder.setNegativeButton(getString(R.string.yes), (dialogInterface, i) -> {
|
||||
//user wishes to go ahead with the upload of this picture, just dismiss this dialog
|
||||
dialogInterface.dismiss();
|
||||
});
|
||||
|
||||
AlertDialog errorDialog = errorDialogBuilder.create();
|
||||
if (!isFinishing()) {
|
||||
errorDialog.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
detectUnwantedPicturesAsync.execute();
|
||||
}
|
||||
|
||||
private Snackbar requestPermissionUsingSnackBar(String rationale,
|
||||
final String[] perms,
|
||||
final int code) {
|
||||
|
|
|
|||
135
app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.java
Normal file
135
app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.java
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Created by bluesir9 on 3/10/17.
|
||||
*/
|
||||
|
||||
public class ImageUtils {
|
||||
//atleast 50% of the image in question should be considered dark for the entire image to be dark
|
||||
private static final double MINIMUM_DARKNESS_FACTOR = 0.50;
|
||||
//atleast 50% of the image in question should be considered blurry for the entire image to be blurry
|
||||
private static final double MINIMUM_BLURRYNESS_FACTOR = 0.50;
|
||||
private static final int LAPLACIAN_VARIANCE_THRESHOLD = 70;
|
||||
|
||||
public enum Result {
|
||||
IMAGE_DARK,
|
||||
IMAGE_OK
|
||||
}
|
||||
|
||||
/**
|
||||
* BitmapRegionDecoder allows us to process a large bitmap by breaking it down into smaller rectangles. The rectangles
|
||||
* are obtained by setting an initial width, height and start position of the rectangle as a factor of the width and
|
||||
* height of the original bitmap and then manipulating the width, height and position to loop over the entire original
|
||||
* bitmap. Each individual rectangle is independently processed to check if its too dark. Based on
|
||||
* the factor of "bright enough" individual rectangles amongst the total rectangles into which the image
|
||||
* was divided, we will declare the image as wanted/unwanted
|
||||
*
|
||||
* @param bitmapRegionDecoder BitmapRegionDecoder for the image we wish to process
|
||||
* @return Result.IMAGE_OK if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null
|
||||
* Result.IMAGE_DARK if image is too dark
|
||||
*/
|
||||
public static Result checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
|
||||
if (bitmapRegionDecoder == null) {
|
||||
Timber.e("Expected bitmapRegionDecoder was null");
|
||||
return Result.IMAGE_OK;
|
||||
}
|
||||
|
||||
int loadImageHeight = bitmapRegionDecoder.getHeight();
|
||||
int loadImageWidth = bitmapRegionDecoder.getWidth();
|
||||
|
||||
int checkImageTopPosition = 0;
|
||||
int checkImageBottomPosition = loadImageHeight / 10;
|
||||
int checkImageLeftPosition = 0;
|
||||
int checkImageRightPosition = loadImageWidth / 10;
|
||||
|
||||
int totalDividedRectangles = 0;
|
||||
int numberOfDarkRectangles = 0;
|
||||
|
||||
while ((checkImageRightPosition <= loadImageWidth) && (checkImageLeftPosition < checkImageRightPosition)) {
|
||||
while ((checkImageBottomPosition <= loadImageHeight) && (checkImageTopPosition < checkImageBottomPosition)) {
|
||||
Timber.d("left: " + checkImageLeftPosition + " right: " + checkImageRightPosition + " top: " + checkImageTopPosition + " bottom: " + checkImageBottomPosition);
|
||||
|
||||
Rect rect = new Rect(checkImageLeftPosition,checkImageTopPosition,checkImageRightPosition,checkImageBottomPosition);
|
||||
totalDividedRectangles++;
|
||||
|
||||
Bitmap processBitmap = bitmapRegionDecoder.decodeRegion(rect,null);
|
||||
|
||||
if (checkIfImageIsDark(processBitmap)) {
|
||||
numberOfDarkRectangles++;
|
||||
}
|
||||
|
||||
checkImageTopPosition = checkImageBottomPosition;
|
||||
checkImageBottomPosition += (checkImageBottomPosition < (loadImageHeight - checkImageBottomPosition)) ? checkImageBottomPosition : (loadImageHeight - checkImageBottomPosition);
|
||||
}
|
||||
|
||||
checkImageTopPosition = 0; //reset to start
|
||||
checkImageBottomPosition = loadImageHeight / 10; //reset to start
|
||||
checkImageLeftPosition = checkImageRightPosition;
|
||||
checkImageRightPosition += (checkImageRightPosition < (loadImageWidth - checkImageRightPosition)) ? checkImageRightPosition : (loadImageWidth - checkImageRightPosition);
|
||||
}
|
||||
|
||||
Timber.d("dark rectangles count = " + numberOfDarkRectangles + ", total rectangles count = " + totalDividedRectangles);
|
||||
|
||||
if (numberOfDarkRectangles > totalDividedRectangles * MINIMUM_DARKNESS_FACTOR) {
|
||||
return Result.IMAGE_DARK;
|
||||
}
|
||||
|
||||
return Result.IMAGE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls the pixels into an array and then runs through it while checking the brightness of each pixel.
|
||||
* The calculation of brightness of each pixel is done by extracting the RGB constituents of the pixel
|
||||
* and then applying the formula to calculate its "Luminance". If this brightness value is less than
|
||||
* 50 then the pixel is considered to be dark. Based on the MINIMUM_DARKNESS_FACTOR if enough pixels
|
||||
* are dark then the entire bitmap is considered to be dark.
|
||||
*
|
||||
* <p>For more information on this brightness/darkness calculation technique refer the accepted answer
|
||||
* on this -> https://stackoverflow.com/questions/35914461/how-to-detect-dark-photos-in-android/35914745
|
||||
* SO question and follow the trail.
|
||||
*
|
||||
* @param bitmap The bitmap that needs to be checked.
|
||||
* @return true if bitmap is dark or null, false if bitmap is bright
|
||||
*/
|
||||
private static boolean checkIfImageIsDark(Bitmap bitmap) {
|
||||
if (bitmap == null) {
|
||||
Timber.e("Expected bitmap was null");
|
||||
return true;
|
||||
}
|
||||
|
||||
int bitmapWidth = bitmap.getWidth();
|
||||
int bitmapHeight = bitmap.getHeight();
|
||||
|
||||
int allPixelsCount = bitmapWidth * bitmapHeight;
|
||||
int[] bitmapPixels = new int[allPixelsCount];
|
||||
|
||||
bitmap.getPixels(bitmapPixels,0,bitmapWidth,0,0,bitmapWidth,bitmapHeight);
|
||||
boolean isImageDark = false;
|
||||
int darkPixelsCount = 0;
|
||||
|
||||
for (int pixel : bitmapPixels) {
|
||||
int r = Color.red(pixel);
|
||||
int g = Color.green(pixel);
|
||||
int b = Color.blue(pixel);
|
||||
|
||||
int brightness = (int) (0.2126 * r + 0.7152 * g + 0.0722 * b);
|
||||
if (brightness < 50) {
|
||||
//pixel is dark
|
||||
darkPixelsCount++;
|
||||
if (darkPixelsCount > allPixelsCount * MINIMUM_DARKNESS_FACTOR) {
|
||||
isImageDark = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isImageDark;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue