Help with loadImage() from phone's gallery and internal storage [SOLVED]


#1

I was wondering if anyone knew a good way to load an image from a phone’s gallery into a PImage in a sketch. Working with an application in Android Studio.

I’m trying to load an image from the photo gallery. I was using a photoPickerIntent to have the user select a photo from the gallery, saving the filepath to that image as a string, then passing the string into the sketch as a parameter, and attempting to use loadImage() with that string.

At that point I am getting the ‘File contains a path separator’ error, which from what I’ve seen in other threads is a common occurrence, but I can’t find a solution that matches this problem. Is it possible to loadImage() from outside of the ‘data’ folder, specifically the gallery, or is the solution some way to save the selected image to the data folder during runtime so the loadImage() function can access it easily?

I’ve tried using the code that this user got to work : https://discourse.processing.org/t/show-image-stored-in-sdcard-sdk-26-solved/4397/11 , however still no luck. In this attempt, I passed the selected image’s uri into the sketch, created a file object using the uri, .toString()-ed the file, and fed that to loadImage(), same path separator error.

I know I can get a Bitmap from the image selected in the gallery and pass that into the sketch (not memory efficient, but better than nothing), is there perhaps any good way to convert that into a PImage object?

I’m really open to any solution here.

Currently from the photoPickerIntent I’m getting the image uri in OnActivityResult:

Uri imageUri = data.getData();

Then I’m putting it as an extra to the next activity that runs the sketch:

Intent i = new Intent(SelectImageActivity.this, GenerateLevelActivity.class);
i.putExtra("image_file_path", passableUri.toString());
startActivity(i);

In that next activity I retrieve the extra and pass it into the sketch object:

Uri imageUri = Uri.parse(getIntent().getStringExtra("image_file_path"));
sketch = new level_generation_sketch(imageUri);

Then in the sketch I’m basically running the code of the previously mentioned user:

    PImage img;
    Uri imageUri;
    
    level_generation_sketch(Uri selectedImage){
        imageUri = selectedImage;
    }

    public void setup(){
        size(1280,720);
        requestPermission("android.permission.READ_EXTERNAL_STORAGE", "initRead");
        requestPermission("android.permission.WRITE_EXTERNAL_STORAGE", "initWrite");
    }

    public void draw(){ }

    void initRead(boolean granted) {
        if (granted) {
            println("init read storage OK");
            File f = new File(imageUri.getPath());
            String myPath = f.toString();
            img = loadImage(myPath);
            image(img, 0, 0);
        } else {
            println("Read storage is not available");
        }
    }
    void initWrite(boolean granted) {
        if (granted) {
            println("init write storage OK");
        } else {
            println("Write storage is not available");
        }
    }

Any help is appreciated.


#2

@ferrnw ===

Though there are other solutions, till now i have solved that using an imageView, adding it to the frameLayout, and setting its content to the retrieved file bitmap from the Gallery. It works.
Another solution is to use file inputStream for getting the file then file outputStream to copy it into the data folder fromP5 and finally use loadImage(). This also works but first one is much faster.


#3

Thanks for the response.
Could you elaborate a little further how to save files to the assets folder during runtime? It turns out that my problems run deeper than I thought and other code that I thought had been working wasn’t.

Like for instance, in a sketch I have code that saves a temporary background for use in a game level (loads all the background assets for just one frame, saves the screen using save(), then loads that using loadImage() (as an alternative to displaying 50 imgs every frame for background elements)). Now from the processing development environment running on desktop, that temp background is saved in the data folder for the project, from which I can load it just fine. Running on a phone connected to android studio, using processing’s save() function, the temp background saves on the device in : /data/data/[projectPackage]/files/temp_background.jpg. At which point I can’t get that image to load using loadImage() even when passing in that exact file path.

Loading images into an imageView isn’t really going to help me as I was planning on using processing’s loadPixels() in order to do some manipulations on the images.


#4

@ferrnw===

yes, but using inputStream && outputStream permits to save the file where you want (i can post code if you want) - Now, if you want to apply effects with loadPixels i 'm afraid that it could be very slow…


#5

I would really appreciate an example of how to use inputStream and outputStream for this.

The loadPixels() isn’t going to be used for anything real time, its in a different sketch that takes an image selected from the phone’s gallery, runs a threshold over it, then generates a game level based on that image (basically divides the image into squares, if >50% of the pixels in a square are black, add a block in that position. Levels are saved as a text csv.)

I have both the level generation sketch and the gameplay sketch working separately in the processing development environment, I’m now trying to get them both into an android app and file i/o seems to be my major hurdle here.

EDIT: So here’s some weird behavior I’m getting. Sometimes when running the app, loadImage() with the direct file path as a string:

"/data/data/projectPackage/files/temp_background.jpg"

will work, but it will only load the top 20% of the image and the rest will all be black. Then when restarting the app (or rotating the device and thus restarting the activity), it will crash immediately, citing:

java.lang.NullPointerException: Attempt to read from field 'int processing.core.PImage.width' on a null object reference

on the line

image(tempBackground, 0, 0);

in the draw function. I can’t seem to figure out what is causing it to partially work sometimes and completely bomb out other times. Sometimes off of a clean install it works with the partially loaded image (which is weird to begin with), then other times it just crashes outright.


#6

So I solved my initial problem of how to load images from the photo gallery. The following code runs in onActivityResult from the photoPickerIntent, saving the image to internal storage:

                final Uri imageUri = data.getData();
                final InputStream imageStream = getContentResolver().openInputStream(imageUri);
                final Bitmap selectedImage = BitmapFactory.decodeStream(imageStream);

                ContextWrapper wrapper = new ContextWrapper(getApplicationContext());
                File file = wrapper.getDir("Images",MODE_PRIVATE);
                file = new File(file, "levelToGenerate"+".jpg");
                try{
                    OutputStream stream = null;
                    stream = new FileOutputStream(file);
                    selectedImage.compress(Bitmap.CompressFormat.JPEG,100,stream);
                    stream.flush();
                    stream.close();
                }catch (IOException e)
                {
                    e.printStackTrace();
                }

Then in the sketch, this code runs loads the image and displays it.

filePath = "/data/data/processing.test.capstone_prototype_1/app_Images/levelToGenerate.jpg";

public void setup(){
        size(1280,720);
        requestPermission("android.permission.READ_EXTERNAL_STORAGE", "initRead");
    }

    public void draw(){
        text("Everythings fine", 200, 200);
        if (img != null) {
            image(img, 0, 0, 1280, 720);
        }
    }

    void initRead(boolean granted) {
        if (granted) {
            println("init read sdcard OK");
            img = loadImage(filePath);
        } else {
            println("Read storage is not available");
        }
    }

The weird thing is having to run that not null in draw(), for whatever reason img stays null for at least the first frame. That weirdness kind of runs into my next issue on a different sketch were I’m similarly loading in an image from local storage and displaying it as a background. Basically:

String path = "/data/data/processing.test.capstone_prototype_1/files/temp_background.jpg";

public void draw(){
  if (runOnce){
    background(0,100,255);
    for (int i = environment.size()-1; i>=0; i--){
      Block b = (Block) environment.get(i);
      b.show();
    }
    goal.show();
    save("temp_background.jpg");


    tempBackground = loadImage(path);
  }
  runOnce = false;
  if (tempBackground != null) {
      image(tempBackground, 0, 0);
  }
}

Basically on the first call of draw, all the environment is displayed, the background is then saved and loaded into a PImage, then is drawn as the background every frame. However, when I run the code one of two things happen: either the image only loads the top 20% of the background and the rest of the image is black, or the background displays perfectly but is only drawn once (I’m assuming on the first frame), so the character trails are visible as they aren’t being drawn over. One of these two outcomes happen randomly every time I run the app. Here’s the part that’s really driving me crazy: when I run the app with android studio’s debugger, and mute all the breakpoints, it functions perfectly. Loads the image fully and draws it every frame.
Has anyone heard of something like that, where the app behavior is different whether or not the debugger is running? I don’t even know where to start with that.

FINAL EDIT: I added an else clause if the PImage object gets set to null somewhere (still cant find what was causing that in my code). Mostly fixed the problem, at the cost of a slight performance hit (I can’t tell how often the image has to be reloaded, but its not that often). Couldn’t figure out the cause of the partially loading image bug or why the app performed differently with the debugger, but its working now so oh well.

if (tempBackground != null) {
      image(tempBackground, 0, 0);
    } else {
      tempBackground = loadImage(path);
    }

#7

@ferrnw===

some code for doing that; in order to avoid the partially loaded image i have a boolean (“charge” ); tell me if it works (possible that i must add some method to find the phone OS, because there are differences between 11, 19 && >>).


import java.io.FileInputStream;
import java.io.FileOutputStream;
import android.content.CursorLoader;
import android.content.Context;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.view.View;
import android.database.Cursor;

import android.provider.MediaStore;
import android.net.Uri;
import android.content.Intent;

private String imagePath="";

private static final int SELECT_IMAGE = 1;
PImage img;
Context context;
boolean charge = false;

public void setup() {
size(800, 1000);
background(255, 0, 0);
context = this.getActivity().getApplicationContext();
chercheImage();
};

public void draw() {
background(255, 0, 0);
if (charge) {///in order to avoid to display the image before completely read && write
image(img, 0, 0);
}
};

void chercheImage() {
Intent intent = new Intent();
intent.setType(“image/*”);
intent.setAction(Intent.ACTION_GET_CONTENT);
getActivity().startActivityForResult(Intent.createChooser(intent,
“Select Picture”), SELECT_IMAGE);
};

public void onActivityResult(int requestCode, int resultCode, Intent data) {

println( resultCode);
if (resultCode == this.getActivity().RESULT_OK) {
if (requestCode == SELECT_IMAGE) {
Uri selectedImageUri = data.getData();
getRealPathFromURI(context, selectedImageUri);
}
}
}

//cette fonction ci dessous ne marche que pour les API <19

public void getRealPathFromURI(Context context, Uri contentUri) {
String[] proj = { MediaStore.Images.Media.DATA };
String chemin = null;
CursorLoader cursorLoader = new CursorLoader(
context,
contentUri, proj, null, null, null);
Cursor cursor = cursorLoader.loadInBackground();

if (cursor != null) {
int column_index =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
chemin = cursor.getString(column_index);
System.out.println(“chemin================” + chemin);
}
File f = new File(chemin);
String monFichier = f.getAbsolutePath();
lireImage(monFichier);
};

public void lireImage(String che) {

charge = false;
FileOutputStream out = null;
FileInputStream in = null;
int cursor;
try {
in = new FileInputStream(new File(che));///reading the selected image
out = new FileOutputStream(dataPath("")+“Test.jpeg”);///copying it
while ((cursor = in.read())!=-1) {
out.write(cursor);
}
}
catch(IOException ex) {
println(ex);
}

finally {

if (in!=null) {
  try {
    in.close();
  }
  catch(IOException e) {
    println(e);
  }
}
if (out!=null) {
  try {
    out.close();
  }
  catch(IOException e) {
  }
}
System.out.println("Everything is ok, i put the boolean to true");

img = loadImage(dataPath("")+"Test.jpeg");
charge= true;

}
};