I was adding a new feature to my drawing app project where users could open any picture from the phone gallery and draw on it. But I am having issues saving the new bitmap. Here are the details below :
My app has 3 activities :
Main Activity as a menu,
RecyclerView activity that acts as a gallery,
Drawing activity where users draw
Up until recently whenever a user saved an image, it would create two copies of it. One private that would be stored in application's personal storage that is not available to phone's gallery to use but is displayed in applications own gallery activity, and one public picture in media which will be displayed in the phones gallery. My gallery class checks the internal folder of the application and lists all files in it to display, that is why I also save in private app directory too. Save image method has two sections that handles the scoped storage issues with api 28+ and <28 respectively.
Recently I added a feature where users can pick any picture from phone's gallery and edit it (both images that app created and those that it did not).It uses intent picker function.
Expected results are :
Open a picture from the gallery in the drawing activity and draw on top of it
Save edited version as a new image both privately in app's storage and a public shared copy in phones main gallery
Current issues :
Image opens and is drawn upon without problems (both images made by the app and foreign pictures)
Saving images causes the app to save an empty bitmap in phones private gallery and changes (edits drawn) in shared storage on the phone without actual image that was meant to be edited.
Here is the code :
From MainActivty, code that opens the images from phone's gallery in order to edit them.
// checks permissions for reading external and scoped storage before opening image picker
private void pickAnImage() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "SDK >= Q , requesting permission");
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1000);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "SDK < Q , requesting permission");
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1000);
} else {
Log.d(TAG, "Permission exists, starting pick intent");
Intent gallery = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(gallery, IMAGE);
}
}
// handles results from permissions and begins intent picker
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1000) {
Log.d(TAG, "result code good");
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Permission is granted, starting pick");
Intent gallery = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(gallery, IMAGE);
} else {
Log.d(TAG, "While result code is good, permission was denied");
Toast.makeText(MainActivity.this, "Permission Denied !", Toast.LENGTH_SHORT).show();
}
}
}
// handles results from intent picker and feeds the image path to the drawing activity for image editing
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == IMAGE && data != null) {
Log.d(TAG, "Data from picked image is not null, creating uri and path");
Uri selectedImageUri = data.getData();
String picturePath = getPath(getApplicationContext(), selectedImageUri);
// Log.d("Picture Path", picturePath);
if (picturePath != null) {
Log.d(TAG, "Path creating success, calling art activity");
Intent intent = new Intent(getApplicationContext(), ArtActivity.class);
intent.putExtra("image", picturePath);
startActivity(intent);
} else {
Log.d(TAG, "Path was null");
finish();
}
}else{
Log.d(TAG, "Data seems to be null, aborting");
}
}
// obtains the path from the picked image
private static String getPath(Context context, Uri uri) {
String result = null;
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
int column_index = cursor.getColumnIndexOrThrow(proj[0]);
result = cursor.getString(column_index);
Toast.makeText(context, "" + result, Toast.LENGTH_SHORT).show();
}
cursor.close();
}
else {
Toast.makeText(context.getApplicationContext(), "Failed to get image path , result is null or permission problem ?", Toast.LENGTH_SHORT).show();
result = "Not found";
Toast.makeText(context, "" + result, Toast.LENGTH_SHORT).show();
}
return result;
}
Below are methods that handle bitmap editing process :
// Inside initialization method that sets up paint and bitmap objects before drawing
if (bitmap != null) {
// if bitmap object is not null it means that we are feeding existing bitmap to be edited, therefore we just scale it to screen size before giving it to canvas
loadedBitmap = scale(bitmap, width, height);
} else {
// bitmap fed to this method is null therefore it means we are not editing existing picture so we create a new object to draw on
this.bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true);
}
Inside onDraw method that handles drawing as it happens :
if (loadedBitmap != null) {
// loadedBitmap not being null means we are editing existing image from above, we previously scaled it so now we feed it to the canvas for editing
canvas.drawBitmap(loadedBitmap, 0, 0, paintLine);
canvas.clipRect(0, 0, loadedBitmap.getWidth(), loadedBitmap.getHeight());
} else {
// we are not editing an image so we draw new empty bitmap to use
canvas.drawBitmap(bitmap, 0, 0, tPaintline);
}
And finally, here is the infamous save image method that is the root of current problems :
#SuppressLint("WrongThread") // not sure what to do otherwise about this lint
public void saveImage() throws IOException {
//create a filename and canvas
String filename = "appName" + System.currentTimeMillis();
Canvas canvas;
// loadedBitmap is the edited one, bitmap is a new one if we did not edit anything
if(loadedBitmap != null){
canvas = new Canvas(loadedBitmap);
} else{
canvas = new Canvas(bitmap);
}
// save image handle for newer api that has scoped storage and updated code
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// feed all the data to content values
OutputStream fos;
ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, filename + ".jpg");
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
fos = resolver.openOutputStream(Objects.requireNonNull(imageUri));
draw(canvas);
// compress correct bitmap, loaded is edited, bitmap is new art
if(loadedBitmap != null){
loadedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
}else{
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
}
Objects.requireNonNull(fos).close();
imageSaved = true;
} else {
// for api older then 28 before scoped storage
// create app directory and a new image file
ContextWrapper cw = new ContextWrapper(getContext());
File directory = cw.getDir("files", Context.MODE_PRIVATE);
pathString = cw.getDir("files", Context.MODE_PRIVATE).toString();
File myPath = new File(directory, filename + ".jpg");
FileOutputStream fileOutputStream = new FileOutputStream(myPath);
// check permissions
try {
if(ContextCompat.checkSelfPermission(
context, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_GRANTED){
draw(canvas);
// save edited or new bitmap to private internal storage
if(loadedBitmap != null){
MediaStore.Images.Media.insertImage(context.getContentResolver(), loadedBitmap, filename, "made with appName");
}else{
MediaStore.Images.Media.insertImage(context.getContentResolver(), bitmap, filename, "made with appName");
}
// now also add a copy to shared storage so it will show up in phone's gallery
addImageToGallery(myPath.getPath(), context);
imageSaved = true;
}else{
// request permissions if not available
requestPermissions((Activity) context,
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
100);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.flush();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Save image to gallery method used above :
// for old api <28, it adds the image to shared storage and phone's gallery
public static void addImageToGallery(final String filePath, final Context context) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.MediaColumns.DATA, filePath);
context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
That is all, if anyone knows bitmap, canvas and permissions (old and new) feel free to help. I am sure tha changes I need are small. Check the current issues above in order to know what to look for.
I have built an Camera application following a Tutorial from Youtube. It saves the Files on External Storage with following code
public void takePicture(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
file = Uri.fromFile(getOutputMediaFile());
intent.putExtra(MediaStore.EXTRA_OUTPUT, file);
startActivityForResult(intent, 100);
}
private static File getOutputMediaFile() {
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "CameraDemo");
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Log.d("CameraDemo", "failed to create directory");
return null;
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
return new File(mediaStorageDir.getPath() + File.separator +
"IMG_" + timeStamp + ".jpg");
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 100) {
if (resultCode == RESULT_OK) {
imageView.setImageURI(file);
}
}
}
But, I would like to show a Dialog box to user when app starts with directory chooser dialog where they want to save images which I followed the Tutorial from this link https://www.codeproject.com/Articles/547636/Android-Ready-to-use-simple-directory-chooser-dial
The code for Directory choose dialog is below
DirectoryChooserDialog directoryChooserDialog =
new DirectoryChooserDialog(MainActivity.this,
new DirectoryChooserDialog.ChosenDirectoryListener()
{
#Override
public void onChosenDir(String chosenDir)
{
m_chosenDir = chosenDir;
Toast.makeText(
MainActivity.this, "Chosen directory: " +
chosenDir, Toast.LENGTH_LONG).show();
}
});
// Toggle new folder button enabling
directoryChooserDialog.setNewFolderEnabled(m_newFolderEnabled);
// Load directory chooser dialog for initial 'm_chosenDir' directory.
// The registered callback will be called upon final directory selection.
directoryChooserDialog.chooseDirectory(m_chosenDir);
m_newFolderEnabled = ! m_newFolderEnabled;
And also indeed, I have a separate class of DirectoryChooserDialog.
Now how do I merge the first code with second one such that when Image taken from camera will save to the folder that users selected from second code not to external storage Directory as specified by first code.
Image file for DirectoryChooserDialog appears as
Having some trouble figuring out how to set the imageview to the picture I just captured with the camera. Would be a bonus if there was some way to display multiple captured pictures at once. Whenever I click the button, a previous image captured appears, then the camera opens, which isn't right. Id like the imageview to be blank, I click the button, take a picture, then that picture is displayed in the imageview. I believe that this line is out of place, but i'm unsure as to how/ where to move it. mimageView.setImageURI(outputFileUri);
public class cameraclass extends Activity {
int TAKE_PHOTO_CODE = 0;
public static int count = 0;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.camera);
final ImageView mimageView;
mimageView = (ImageView) this.findViewById(R.id.image_from_camera);
// Here, we are making a folder named picFolder to store
// pics taken by the camera using this application.
final String dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/picFolder/";
File newdir = new File(dir);
newdir.mkdirs();
Button capture = (Button) findViewById(R.id.take_image_from_camera);
capture.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Here, the counter will be incremented each time, and the
// picture taken by camera will be stored as 1.jpg,2.jpg
// and likewise.
count++;
String file = dir+count+".jpg";
File newfile = new File(file);
try {
newfile.createNewFile();
}
catch (IOException e)
{
}
//Uri outputFileUri = Uri.fromFile(newfile);
Uri outputFileUri = FileProvider.getUriForFile(getApplicationContext() , "com.example.android.com220finalapp.provider", newfile);
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(cameraIntent, TAKE_PHOTO_CODE);
mimageView.setImageURI(outputFileUri);
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == TAKE_PHOTO_CODE && resultCode == RESULT_OK) {
Log.d("CameraDemo", "Pic saved");
}
}
}
I believe that this line is out of place, but i'm unsure as to how/ where to move it.
startActivityForResult() is asynchronous. Your photo will not have been taken by the time that method returns. You need to load the image into the ImageView in onActivityResult(), if you got a RESULT_OK response.
However, while setImageURI() may work, it has never been an especially good idea, as it will freeze your app for a while as it loads the photo. There are many image loading libraries for Android that will handle loading your ImageView asynchronously.
I have UI which contains the four thing
1) Date
2) Message
3) Socialnetworking
4) Attachment with image
1)For Date ,when i click on the date ,then DateTimePicker comes and i select the date
2)For Message ,when i click on the message ,then new activity is there to write the
message and then press the done then message comes back to the previous UI with message contains
3)For Social Networking,same thing as 2 points is happening
4) But for Image attachment ,when i click the attachment ,it opens the new Activity with UI for selecting the image form the gallery ,The image is selected ,but i want to take the image back to the previous UI .
For Coming new activity and for getting back to the previous UI,i am plying with the Visibilty gone and VISIBLE.
please suggest ,what i can do for fetching the image and get back to the previous screen.
Try this in your first activity from which u will pass intent for gallery:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) {
Uri SelectedImage = data.getData();
String filePath = null;
try {
// OI FILE Manager
String filemanagerstring = SelectedImage.getPath();
// MEDIA GALLERY
String selectedImagePath = getPath(SelectedImage);
if (selectedImagePath != null) {
filePath = selectedImagePath;
} else if (filemanagerstring != null) {
filePath = filemanagerstring;
} else {
Toast.makeText(getApplicationContext(), "Unknown path",
Toast.LENGTH_LONG).show();
Log.e("Bitmap", "Unknown path");
}
if (filePath != null) {
ProfilePic.setImageURI(SelectedImage);
//or decode if u want to reduce the size
} else {
bitmap = null;
}
FROM_GALLERY = true;
} catch (Exception e) {
Log.e("Uploaderror", e.getLocalizedMessage());
}
}
}
I am new to Android programming and I'm writing an application in Java that opens the camera take a photo and save it. I made it via Intents but I can't see onActivityResult running.
I have tested it into my phone (Samsung Galaxy S) and when I take the photo I receive a preview of that photo having two buttons one Save and the other Cancel. I haven't added something to my code to do this so I think it's something that camera does. I want after capturing the image to run onActivityResult (after I press the Save button on the preview).
But how I'm going to return a result to start onActivityResult after pressing the Button Save on the preview?
I FORGOT to tell that after i press save my entire app is terminated.
Here is my Code
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TakePicButton = (Button) findViewById(R.id.TakePicture);
TakePicButton.setOnClickListener((android.view.View.OnClickListener) this);
}
#Override
public void onDestroy(){
super.onDestroy();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
// Image captured and saved to fileUri specified in the Intent
Toast.makeText(this, "Image saved to:\n" + data.getData(), Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, "Picture was not taken", Toast.LENGTH_SHORT);
} else {
Toast.makeText(this, "Picture was not taken", Toast.LENGTH_SHORT);
}
}
public void onClick(View v) {
// TODO Auto-generated method stub
if(v.getId() == R.id.TakePicture){
// create Intent to take a picture and return control to the calling application
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name
// start the image capture Intent
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
}
try the below code, you will have to modify it a bit, it will help you get From Library and From Camera both, the SELECT_PICTURE is used for getting image from library
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case SELECT_PICTURE:
Uri selectedImageUri = data.getData();
filemanagerstring = selectedImageUri.getPath();
selectedImagePath = getPath(selectedImageUri);
if (selectedImagePath != null)
myFile = new File(selectedImagePath);
else if (filemanagerstring != null)
myFile = new File(filemanagerstring);
if (myFile != null) {
Bitmap bmp_fromGallery = decodeImageFile(selectedImagePath);
break;
case CAMERA_REQUEST:
Bitmap bmp_Camera = (Bitmap) data.getExtras().get("data");
break;
default:
break;
}
}