package com.imagepicker.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.react.bridge.ReadableMap;
import com.imagepicker.ImagePickerModule;
import com.imagepicker.ResponseHelper;
import com.imagepicker.media.ImageConfig;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.UUID;
import static com.imagepicker.ImagePickerModule.REQUEST_LAUNCH_IMAGE_CAPTURE;
/**
* Created by rusfearuth on 15.03.17.
*/
public class MediaUtils
{
public static @Nullable File createNewFile(@NonNull final Context reactContext,
@NonNull final ReadableMap options,
@NonNull final boolean forceLocal)
{
final String filename = new StringBuilder("image-")
.append(UUID.randomUUID().toString())
.append(".jpg")
.toString();
final File path = ReadableMapUtils.hasAndNotNullReadableMap(options, "storageOptions") && !forceLocal
? Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
: reactContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File result = new File(path, filename);
try
{
path.mkdirs();
result.createNewFile();
}
catch (IOException e)
{
e.printStackTrace();
result = null;
}
return result;
}
/**
* Create a resized image to fulfill the maxWidth/maxHeight, quality and rotation values
*
* @param context
* @param options
* @param imageConfig
* @param initialWidth
* @param initialHeight
* @return updated ImageConfig
*/
public static @NonNull ImageConfig getResizedImage(@NonNull final Context context,
@NonNull final ReadableMap options,
@NonNull final ImageConfig imageConfig,
final int initialWidth,
final int initialHeight,
final int requestCode)
{
BitmapFactory.Options imageOptions = new BitmapFactory.Options();
imageOptions.inScaled = false;
// FIXME: OOM here
Bitmap photo = BitmapFactory.decodeFile(imageConfig.original.getAbsolutePath(), imageOptions);
if (photo == null)
{
return null;
}
ImageConfig result = imageConfig;
Bitmap scaledPhoto = null;
if (imageConfig.maxWidth == 0 || imageConfig.maxWidth > initialWidth)
{
result = result.withMaxWidth(initialWidth);
}
if (imageConfig.maxHeight == 0 || imageConfig.maxWidth > initialHeight)
{
result = result.withMaxHeight(initialHeight);
}
double widthRatio = (double) result.maxWidth / initialWidth;
double heightRatio = (double) result.maxHeight / initialHeight;
double ratio = (widthRatio < heightRatio)
? widthRatio
: heightRatio;
Matrix matrix = new Matrix();
matrix.postRotate(result.rotation);
matrix.postScale((float) ratio, (float) ratio);
ExifInterface exif;
try
{
exif = new ExifInterface(result.original.getAbsolutePath());
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
switch (orientation)
{
case 6:
matrix.postRotate(90);
break;
case 3:
matrix.postRotate(180);
break;
case 8:
matrix.postRotate(270);
break;
}
}
catch (IOException e)
{
e.printStackTrace();
}
scaledPhoto = Bitmap.createBitmap(photo, 0, 0, photo.getWidth(), photo.getHeight(), matrix, true);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
scaledPhoto.compress(Bitmap.CompressFormat.JPEG, result.quality, bytes);
final boolean forceLocal = requestCode == REQUEST_LAUNCH_IMAGE_CAPTURE;
final File resized = createNewFile(context, options, !forceLocal);
if (resized == null)
{
if (photo != null)
{
photo.recycle();
photo = null;
}
if (scaledPhoto != null)
{
scaledPhoto.recycle();
scaledPhoto = null;
}
return imageConfig;
}
result = result.withResizedFile(resized);
FileOutputStream fos;
try
{
fos = new FileOutputStream(result.resized);
fos.write(bytes.toByteArray());
}
catch (IOException e)
{
e.printStackTrace();
}
if (photo != null)
{
photo.recycle();
photo = null;
}
if (scaledPhoto != null)
{
scaledPhoto.recycle();
scaledPhoto = null;
}
return result;
}
public static void removeUselessFiles(final int requestCode,
@NonNull final ImageConfig imageConfig)
{
if (requestCode != ImagePickerModule.REQUEST_LAUNCH_IMAGE_CAPTURE)
{
return;
}
if (imageConfig.original != null && imageConfig.original.exists())
{
imageConfig.original.delete();
}
if (imageConfig.resized != null && imageConfig.resized.exists())
{
imageConfig.resized.delete();
}
}
public static void fileScan(@Nullable final Context reactContext,
@NonNull final String path)
{
if (reactContext == null)
{
return;
}
MediaScannerConnection.scanFile(reactContext,
new String[] { path }, null,
new MediaScannerConnection.OnScanCompletedListener()
{
public void onScanCompleted(String path, Uri uri)
{
Log.i("TAG", new StringBuilder("Finished scanning ").append(path).toString());
}
});
}
public static ReadExifResult readExifInterface(@NonNull ResponseHelper responseHelper,
@NonNull final ImageConfig imageConfig)
{
ReadExifResult result;
int currentRotation = 0;
try
{
ExifInterface exif = new ExifInterface(imageConfig.original.getAbsolutePath());
// extract lat, long, and timestamp and add to the response
float[] latlng = new float[2];
exif.getLatLong(latlng);
float latitude = latlng[0];
float longitude = latlng[1];
if(latitude != 0f || longitude != 0f)
{
responseHelper.putDouble("latitude", latitude);
responseHelper.putDouble("longitude", longitude);
}
final String timestamp = exif.getAttribute(ExifInterface.TAG_DATETIME);
final SimpleDateFormat exifDatetimeFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
final DateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
try
{
final String isoFormatString = new StringBuilder(isoFormat.format(exifDatetimeFormat.parse(timestamp)))
.append("Z").toString();
responseHelper.putString("timestamp", isoFormatString);
}
catch (Exception e) {}
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
boolean isVertical = true;
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_270:
isVertical = false;
currentRotation = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
isVertical = false;
currentRotation = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
currentRotation = 180;
break;
}
responseHelper.putInt("originalRotation", currentRotation);
responseHelper.putBoolean("isVertical", isVertical);
result = new ReadExifResult(currentRotation, null);
}
catch (IOException e)
{
e.printStackTrace();
result = new ReadExifResult(currentRotation, e);
}
return result;
}
public static @Nullable RolloutPhotoResult rolloutPhotoFromCamera(@NonNull final ImageConfig imageConfig)
{
RolloutPhotoResult result = null;
final File oldFile = imageConfig.resized == null ? imageConfig.original: imageConfig.resized;
final File newDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
final File newFile = new File(newDir.getPath(), oldFile.getName());
try
{
moveFile(oldFile, newFile);
ImageConfig newImageConfig;
if (imageConfig.resized != null)
{
newImageConfig = imageConfig.withResizedFile(newFile);
}
else
{
newImageConfig = imageConfig.withOriginalFile(newFile);
}
result = new RolloutPhotoResult(newImageConfig, null);
}
catch (IOException e)
{
e.printStackTrace();
result = new RolloutPhotoResult(imageConfig, e);
}
return result;
}
/**
* Move a file from one location to another.
*
* This is done via copy + deletion, because Android will throw an error
* if you try to move a file across mount points, e.g. to the SD card.
*/
private static void moveFile(@NonNull final File oldFile,
@NonNull final File newFile) throws IOException
{
FileChannel oldChannel = null;
FileChannel newChannel = null;
try
{
oldChannel = new FileInputStream(oldFile).getChannel();
newChannel = new FileOutputStream(newFile).getChannel();
oldChannel.transferTo(0, oldChannel.size(), newChannel);
oldFile.delete();
}
finally
{
try
{
if (oldChannel != null) oldChannel.close();
if (newChannel != null) newChannel.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public static class RolloutPhotoResult
{
public final ImageConfig imageConfig;
public final Throwable error;
public RolloutPhotoResult(@NonNull final ImageConfig imageConfig,
@Nullable final Throwable error)
{
this.imageConfig = imageConfig;
this.error = error;
}
}
public static class ReadExifResult
{
public final int currentRotation;
public final Throwable error;
public ReadExifResult(int currentRotation,
@Nullable final Throwable error)
{
this.currentRotation = currentRotation;
this.error = error;
}
}
}