Bugfix issue 711 heic format (#731)

* Android - issue/711 - support .heic format
This commit is contained in:
Pieter Van Poyer 2021-08-09 15:36:58 +02:00 committed by GitHub
parent abfbbd35d5
commit 75bf807261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 67 deletions

View File

@ -87,6 +87,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private static final String PNG_EXTENSION = "." + PNG_TYPE; private static final String PNG_EXTENSION = "." + PNG_TYPE;
private static final String PNG_MIME_TYPE = "image/png"; private static final String PNG_MIME_TYPE = "image/png";
private static final String JPEG_MIME_TYPE = "image/jpeg"; private static final String JPEG_MIME_TYPE = "image/jpeg";
private static final String HEIC_MIME_TYPE = "image/heic";
private static final String GET_PICTURE = "Get Picture"; private static final String GET_PICTURE = "Get Picture";
private static final String GET_VIDEO = "Get Video"; private static final String GET_VIDEO = "Get Video";
private static final String GET_All = "Get All"; private static final String GET_All = "Get All";
@ -197,7 +198,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
if(!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { if(!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE); PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
} else { } else {
this.getImage(this.srcType, destType, encodingType); this.getImage(this.srcType, destType);
} }
} }
} }
@ -273,9 +274,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
if (takePicturePermission && saveAlbumPermission) { if (takePicturePermission && saveAlbumPermission) {
takePicture(returnType, encodingType); takePicture(returnType, encodingType);
} else if (saveAlbumPermission && !takePicturePermission) { } else if (saveAlbumPermission) {
PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.CAMERA); PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.CAMERA);
} else if (!saveAlbumPermission && takePicturePermission) { } else if (takePicturePermission) {
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, PermissionHelper.requestPermissions(this, TAKE_PIC_SEC,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}); new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE});
} else { } else {
@ -356,11 +357,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* *
* @param srcType The album to get image from. * @param srcType The album to get image from.
* @param returnType Set the type of image to return. * @param returnType Set the type of image to return.
* @param encodingType
*/ */
// TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do!
// TODO: Images from kitkat filechooser not going into crop function // TODO: Images from kitkat filechooser not going into crop function
public void getImage(int srcType, int returnType, int encodingType) { public void getImage(int srcType, int returnType) {
Intent intent = new Intent(); Intent intent = new Intent();
String title = GET_PICTURE; String title = GET_PICTURE;
croppedUri = null; croppedUri = null;
@ -420,7 +420,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// set crop properties // set crop properties
cropIntent.putExtra("crop", "true"); cropIntent.putExtra("crop", "true");
// indicate output X and Y // indicate output X and Y
if (targetWidth > 0) { if (targetWidth > 0) {
cropIntent.putExtra("outputX", targetWidth); cropIntent.putExtra("outputX", targetWidth);
@ -439,7 +438,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cropIntent.putExtra("output", croppedUri); cropIntent.putExtra("output", croppedUri);
// start the activity - we handle returning in onActivityResult // start the activity - we handle returning in onActivityResult
if (this.cordova != null) { if (this.cordova != null) {
@ -450,9 +448,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
LOG.e(LOG_TAG, "Crop operation not supported on this device"); LOG.e(LOG_TAG, "Crop operation not supported on this device");
try { try {
processResultFromCamera(destType, cameraIntent); processResultFromCamera(destType, cameraIntent);
} } catch (IOException e) {
catch (IOException e)
{
e.printStackTrace(); e.printStackTrace();
LOG.e(LOG_TAG, "Unable to write to file"); LOG.e(LOG_TAG, "Unable to write to file");
} }
@ -475,7 +471,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.croppedFilePath : this.croppedFilePath :
this.imageFilePath; this.imageFilePath;
if (this.encodingType == JPEG) { if (this.encodingType == JPEG) {
try { try {
//We don't support PNG, so let's not pretend we do //We don't support PNG, so let's not pretend we do
@ -610,7 +605,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
ContentResolver resolver = this.cordova.getActivity().getContentResolver(); ContentResolver resolver = this.cordova.getActivity().getContentResolver();
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, galleryPathVO.getGalleryFileName()); contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, galleryPathVO.getGalleryFileName());
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, getMimetypeForFormat(encodingType)); contentValues.put(MediaStore.MediaColumns.MIME_TYPE, getMimetypeForEncodingType());
Uri galleryOutputUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); Uri galleryOutputUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
InputStream fileStream = org.apache.cordova.camera.FileHelper.getInputStreamFromUriString(imageUri.toString(), cordova); InputStream fileStream = org.apache.cordova.camera.FileHelper.getInputStreamFromUriString(imageUri.toString(), cordova);
@ -623,7 +618,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private GalleryPathVO getPicturesPath() { private GalleryPathVO getPicturesPath() {
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date()); String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION); String imageFileName = "IMG_" + timeStamp + getExtensionForEncodingType();
File storageDir = Environment.getExternalStoragePublicDirectory( File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES); Environment.DIRECTORY_PICTURES);
storageDir.mkdirs(); storageDir.mkdirs();
@ -639,28 +634,20 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/** /**
* Converts output image format int value to string value of mime type. * Converts output image format int value to string value of mime type.
* @param outputFormat int Output format of camera API.
* Must be value of either JPEG or PNG constant
* @return String String value of mime type or empty string if mime type is not supported * @return String String value of mime type or empty string if mime type is not supported
*/ */
private String getMimetypeForFormat(int outputFormat) { private String getMimetypeForEncodingType() {
if (outputFormat == PNG) return PNG_MIME_TYPE; if (encodingType == PNG) return PNG_MIME_TYPE;
if (outputFormat == JPEG) return JPEG_MIME_TYPE; if (encodingType == JPEG) return JPEG_MIME_TYPE;
return ""; return "";
} }
private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { private String outputModifiedBitmap(Bitmap bitmap, Uri uri, String mimeTypeOfOriginalFile) throws IOException {
// Some content: URIs do not map to file paths (e.g. picasa). // Some content: URIs do not map to file paths (e.g. picasa).
String realPath = FileHelper.getRealPath(uri, this.cordova); String realPath = FileHelper.getRealPath(uri, this.cordova);
String fileName = calculateModifiedBitmapOutputFileName(mimeTypeOfOriginalFile, realPath);
// Get filename from uri
String fileName = realPath != null ?
realPath.substring(realPath.lastIndexOf('/') + 1) :
"modified." + (this.encodingType == JPEG ? JPEG_TYPE : PNG_TYPE);
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
//String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
String modifiedPath = getTempDirectoryPath() + "/" + fileName; String modifiedPath = getTempDirectoryPath() + "/" + fileName;
OutputStream os = new FileOutputStream(modifiedPath); OutputStream os = new FileOutputStream(modifiedPath);
@ -684,6 +671,23 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
return modifiedPath; return modifiedPath;
} }
private String calculateModifiedBitmapOutputFileName(String mimeTypeOfOriginalFile, String realPath) {
if (realPath == null) {
return "modified" + getExtensionForEncodingType();
}
String fileName = realPath.substring(realPath.lastIndexOf('/') + 1);
if (getMimetypeForEncodingType().equals(mimeTypeOfOriginalFile)) {
return fileName;
}
// if the picture is not a jpeg or png, (a .heic for example) when processed to a bitmap
// the file extension is changed to the output format, f.e. an input file my_photo.heic could become my_photo.jpg
return fileName.substring(fileName.lastIndexOf(".") + 1) + getExtensionForEncodingType();
}
private String getExtensionForEncodingType() {
return this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION;
}
/** /**
* Applies all needed transformation to the image received from the gallery. * Applies all needed transformation to the image received from the gallery.
@ -707,24 +711,22 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
String uriString = uri.toString(); String uriString = uri.toString();
String finalLocation = fileLocation != null ? fileLocation : uriString; String finalLocation = fileLocation != null ? fileLocation : uriString;
String mimeType = FileHelper.getMimeType(uriString, this.cordova); String mimeTypeOfGalleryFile = FileHelper.getMimeType(uriString, this.cordova);
if (finalLocation == null) { if (finalLocation == null) {
this.failPicture("Error retrieving result."); this.failPicture("Error retrieving result.");
} else { } else {
// If you ask for video or the selected file cannot be processed
// If you ask for video or the selected file doesn't have JPEG or PNG mime type // there will be no attempt to resize any returned data.
// there will be no attempt to resize any returned data if (this.mediaType == VIDEO || !isImageMimeTypeProcessable(mimeTypeOfGalleryFile)) {
if (this.mediaType == VIDEO || !(JPEG_MIME_TYPE.equalsIgnoreCase(mimeType) || PNG_MIME_TYPE.equalsIgnoreCase(mimeType))) {
this.callbackContext.success(finalLocation); this.callbackContext.success(finalLocation);
} } else {
else {
// This is a special case to just return the path as no scaling, // This is a special case to just return the path as no scaling,
// rotating, nor compressing needs to be done // rotating, nor compressing needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 && if (this.targetHeight == -1 && this.targetWidth == -1 &&
destType == FILE_URI && !this.correctOrientation && destType == FILE_URI && !this.correctOrientation &&
mimeType != null && mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType))) getMimetypeForEncodingType().equalsIgnoreCase(mimeTypeOfGalleryFile))
{ {
this.callbackContext.success(finalLocation); this.callbackContext.success(finalLocation);
} else { } else {
@ -750,10 +752,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Did we modify the image? // Did we modify the image?
if ( (this.targetHeight > 0 && this.targetWidth > 0) || if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
(this.correctOrientation && this.orientationCorrected) || (this.correctOrientation && this.orientationCorrected) ||
!mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType))) !mimeTypeOfGalleryFile.equalsIgnoreCase(getMimetypeForEncodingType()))
{ {
try { try {
String modifiedPath = this.outputModifiedBitmap(bitmap, uri); String modifiedPath = this.outputModifiedBitmap(bitmap, uri, mimeTypeOfGalleryFile);
// The modified image is cached by the app in order to get around this and not have to delete you // The modified image is cached by the app in order to get around this and not have to delete you
// application cache I'm adding the current system time to the end of the file url. // application cache I'm adding the current system time to the end of the file url.
this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis()); this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis());
@ -777,6 +779,18 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
} }
/**
* JPEG, PNG and HEIC mime types (images) can be scaled, decreased in quantity, corrected by orientation.
* But f.e. an image/gif cannot be scaled, but is can be selected through the PHOTOLIBRARY.
*
* @param mimeType The mimeType to check
* @return if the mimeType is a processable image mime type
*/
private boolean isImageMimeTypeProcessable(String mimeType) {
return JPEG_MIME_TYPE.equalsIgnoreCase(mimeType) || PNG_MIME_TYPE.equalsIgnoreCase(mimeType)
|| HEIC_MIME_TYPE.equalsIgnoreCase(mimeType);
}
/** /**
* Called when the camera view exits. * Called when the camera view exits.
* *
@ -974,7 +988,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
if (fileStream != null) { if (fileStream != null) {
// Generate a temporary file // Generate a temporary file
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date()); String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION); String fileName = "IMG_" + timeStamp + (getExtensionForEncodingType());
localFile = new File(getTempDirectoryPath() + fileName); localFile = new File(getTempDirectoryPath() + fileName);
galleryUri = Uri.fromFile(localFile); galleryUri = Uri.fromFile(localFile);
writeUncompressedImage(fileStream, galleryUri); writeUncompressedImage(fileStream, galleryUri);
@ -998,15 +1012,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
rotate = 0; rotate = 0;
} }
} }
} } catch (Exception e) {
catch (Exception e)
{
LOG.e(LOG_TAG,"Exception while getting input stream: "+ e.toString()); LOG.e(LOG_TAG,"Exception while getting input stream: "+ e.toString());
return null; return null;
} }
try { try {
// figure out the original width and height of the image // figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
@ -1052,7 +1062,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// determine the correct aspect ratio // determine the correct aspect ratio
int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight); int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight);
// Load in the smallest bitmap possible that is closest to the size we want // Load in the smallest bitmap possible that is closest to the size we want
options.inJustDecodeBounds = false; options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight, widthHeight[0], widthHeight[1]); options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight, widthHeight[0], widthHeight[1]);
@ -1092,8 +1101,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
} }
} }
return scaledBitmap; return scaledBitmap;
} } finally {
finally {
// delete the temporary copy // delete the temporary copy
if (localFile != null) { if (localFile != null) {
localFile.delete(); localFile.delete();
@ -1305,9 +1313,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.conn.disconnect(); this.conn.disconnect();
} }
public void onRequestPermissionResult(int requestCode, String[] permissions, public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException { int[] grantResults) {
for (int r : grantResults) { for (int r : grantResults) {
if (r == PackageManager.PERMISSION_DENIED) { if (r == PackageManager.PERMISSION_DENIED) {
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR)); this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
@ -1319,7 +1326,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
takePicture(this.destType, this.encodingType); takePicture(this.destType, this.encodingType);
break; break;
case SAVE_TO_ALBUM_SEC: case SAVE_TO_ALBUM_SEC:
this.getImage(this.srcType, this.destType, this.encodingType); this.getImage(this.srcType, this.destType);
break; break;
} }
} }

View File

@ -142,22 +142,6 @@ public class FileHelper {
return null; return null;
} }
public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
String[] proj = { MediaStore.Images.Media.DATA };
String result = null;
try {
Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
result = cursor.getString(column_index);
} catch (Exception e) {
result = null;
}
return result;
}
/** /**
* Returns an input stream based on given URI string. * Returns an input stream based on given URI string.
* *
@ -233,7 +217,7 @@ public class FileHelper {
* @return the mime type of the specified data * @return the mime type of the specified data
*/ */
public static String getMimeType(String uriString, CordovaInterface cordova) { public static String getMimeType(String uriString, CordovaInterface cordova) {
String mimeType = null; String mimeType;
Uri uri = Uri.parse(uriString); Uri uri = Uri.parse(uriString);
if (uriString.startsWith("content://")) { if (uriString.startsWith("content://")) {