Use an AsyncTask to load PImages

edited March 13 in Android Mode

Hi

Working in Android Studio with Processing for Android, I'm trying help the start of my app by having the loading of images transferred to a parallel thread

It seem an AsyncTask class is the way to go and I found a good reference to see how it is done The thing is, instead of 'Bitmap' and 'ImageView' I'm here working with PImage

Any idea on how to turn this Class

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    @ Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @ Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

and this code

public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

into something that would work for PImage ?

Answers

  • PImaget.getNative() returns a bitmap object. Would that help?

    Check also the setNative() in the above reference. Lastly, I was working with ImageView objects in this recent discussion. Hopefully this helps:

    https://forum.processing.org/two/discussion/comment/118853/#Comment_118853

    Kf

  • edited March 7

    thanks for the answer @kfrajer

    My point here, is to offload the many loadImage("myImage.png") work to another thread.

    I am playing around with a backgroundWorker function like so

        class backgroundWorker extends AsyncTask<MyTaskParams, Void, PImage> {
    
    
            @ Override
            protected PImage doInBackground(MyTaskParams... params) {
            params[0].pImage = loadImage(params[0].file + ".png");
            return params[0].pImage;
            }
    
    
            @ Override
            protected void onPostExecute(PImage result) {
            println("loaded image is : " + result );
            ressourceCounter++;
            }
        }
    

    but with no success because, even though I send my PImage variable inside the pramas like so

    ...setup(){
             ...
            MyTaskParams params = new MyTaskParams(imgDemiCadranDA, "DemiCadranDA");
            backgroundWorker myTask = new backgroundWorker();
            myTask.execute(params);
        }
    
        private static class MyTaskParams {
            PImage pImage ;
            String file;
    
            MyTaskParams(PImage pImage, String file ) {
                this.pImage = pImage;
                this.file = file;
            }
        }
    

    params[0].pImage is always null

  • edited March 9

    ok , I got it to work so here is how it goes:

    inside my main I added 2 Classes

    • One to create the params to feed my AsyncTask

    • One is my AsyncTask

    First one goes:

        private static class MyTaskParams {
            PImage[] wichArray;
            int pImageNum ;
            String file;
    
            MyTaskParams(PImage[] wichArray, int pImageNum, String file ) {
                this.wichArray = wichArray;
                this.pImageNum = pImageNum;
                this.file = file;
            }
        }
    

    Then the AsyncTask goes:

            private class BitmapWorkerTask extends AsyncTask<MyTaskParams, Void, PImage> {
    
                @ Override
                protected PImage doInBackground(MyTaskParams... params) {
                    params[0].wichArray[params[0].pImageNum] = loadImage(params[0].file);
                    return params[0].wichArray[params[0].pImageNum];
                }
    
                @ Override
                protected void onPostExecute(PImage result) {
                    ressourceCounter++;
                }
            }
    

    then I have to use some Arrays, One with the file names (discretImgString[]) and one with the PImage variable names (discretIMG[]) (which won't be used but help my keep track of my ressources)

    Here is how I call the image loading from my setup()

                    for(int i = 0; i < discretIMG.length; i++){
                        MyTaskParams params = new MyTaskParams(discretIMG, i, discretImgString[i]);
                        BitmapWorkerTask myTask = new BitmapWorkerTask();
                        myTask.execute(params);
                    }
    

    So, using this trick I can start my sketch with a neat little loading screen and display a loading percentage animation using this ressourceCounter int

  • Answer ✓

    Out of interest, what's missing with requestImage()?

  • edited March 13

    ** insert facepalm gif here **

    oh my ! it took me 2 days to work out this AsyncTask thing and now you come and tell me that :S It's like painfully forcing a lock and having someone showing you the door is actually open

    Well, at least I learned a lot of AsyncTask thing in the process and I guess it'll be useful for things beyond images, like sounds, json stuff etc they say alway look at the bright side right ?

  • edited March 13

    There's also thread(): https://Processing.org/reference/thread_.html

    You should 1st look Processing's API reference to check whether the functionality you need is already there. ;)

  • wow, another cool function !

    and how nice it is to have everyone come to give me the answer to my question one week after I asked, and tell me all the work I have put into solving my problem alone was just a waste of time

    do you suggest learning the reference by heart and ditch the forum altogether ?

    anyway, if anyone of you is interested in criticizing my work, you can find it here now : https://play.google.com/store/apps/details?id=gg.rich.me.fuzo

    :D

  • ** insert facepalm gif here **

    @phoebus ha ha, don't worry - we've all done it - generally at least once a week! :-)

    @GoToLoop - generally agree with you about the reference, but thread() is quite limited compared to what the original code is doing. Not to mention, don't get me started on how broken that thread() example actually is! ;-)

  • edited March 13

    ... but thread() is quite limited compared to what the original code is doing.

    Almost all of Processing's API is a simplification of how things are done on bare metal Java. ~O)

    It's not a question of how limited the API is, but whether it's enough for what we wanna lazily accomplish. :\">

    ... , don't get me started on how broken that thread() example actually is!

    Many Processing's reference examples can be broken, incomplete or even wrong. :-O

    I just post links to them b/c I'm lazy. (:|

  • @GoToLoop - yes, and it isn't (easily) in this case!

    btw - the example is broken because it doesn't use synchronized or volatile to set the shared variable, which means the JVM may (or at least is free to) make time = json.getString("time"); a no-op. There's also no checking for whether a load is already in progress, which would be ideally handled in thread(), so has the potential to crash.

    I'm all for simplifying things over bare metal Java - that's kind of my key interest - but not encouraging completely broken practise in the process. There are much better ways to simplify this!

  • edited March 13

    There's also no checking for whether a load is already in progress, ...

    • The variable time is the only data that example is interested in.
    • Which is then displayed via function text().
    • At 1st, time is merely an empty String "".
    • But once the 1st loadJSONObject() completes, time is reassigned via its method getString().
    • I can see 2 ways for it to crash though:
    1. loadJSONObject() fails. Gonna need a try/catch () block for it.
    2. The field "timer" isn't in the JSON. So time is assigned null. Fix, use defaultValue:
      time = json.getString("time", "");
      https://Processing.org/reference/JSONObject_getString_.html
  • edited March 13

    ... because it doesn't use synchronized or volatile to set the shared variable, ...

    In my experience w/ threads in Java, I've never had a thread-shared field failing to get updated and seen by all other threads b/c it wasn't declared as volatile or wasn't inside a synchronized block or method. :-\"

    When dealing w/ shared fields, each Java Thread caches them for performance reasons. \m/

    And when a thread reassigns a shared field, of course there's some latency for the other threads to actually "see" that change. :-SS

    But eventually, all threads end up seeing the new assigned value.
    And it doesn't take that much. It's almost instantly! :-bd

    What volatile does to a field is to guarantee that all threads sharing that field sees any change to its stored value immediately.

    But in that particular example, we've got 2 threads sharing variable time:
    The "Animation Thread" and the 1s created by thread().

    I'm taking into consideration that each thread() dies before a new 1 is created at about half second rate. :ar!

    So each thread() reassigns the shared variable time w/ another String object reference value.

    And the main "Animation Thread" simply displays the current String assigned to time when text() is invoked at about 60 FPS.

    I dunno how much latency the "Animation Thread" takes to "see" the latest current String assigned to time.

    Does it take 1, 2, 3, 4, or 5 milliseconds for the "Animation Thread" to realize the new reference value stored in variable time by the other thread()? I dunno! 3:-O

    What I know is that the update is so fast that for me it is instantly!
    Whether time is volatile or not doesn't make an iota diff. for this particular example! =P~

    ... which means the JVM may (or at least is free to) make time = json.getString("time"); a no-op.

    Ru sure Java would be so irresponsible to no-op an assignment operation, so the other threads would never eventually see the changed value in a shared non-volatile field??? @-)

    So if I change the index 0 of some ArrayList in a thread, another thread would never see that change??? :-SS

    Sorry @neilcsmith_net; that doesn't make any sense to me. :-/

    Actually, that would break many Java programs, including Processing sketches which doesn't care about volatile nor synchronized blocks if what you've stated above could be true! =;

  • edited March 13

    What I don't like about that thread() example is that it keeps creating a new Thread over & over for something that is supposed to keep ongoing during the whole time the sketch is running. 8-X

    How about invoking thread() once in setup(), and have its code inside an infinite loop w/ delay(): *-:)

    https://Processing.org/reference/thread_.html

    String time = "";
    
    void setup() {
      fill(#FFFF00);
      textAlign(CENTER, CENTER);
      textSize(14);
      thread("requestData");
    }
    
    void draw() {
      background(0);
      text(time, width>>1, height>>1);
    }
    
    void requestData() {
      for (;; delay(1000)) {
        final JSONObject json = loadJSONObject("http://" + "Time.JsonTest.com/");
        time = json.getString("time", "FAILED!");
        println(time);
      }
    }
    
  • I love how I couldn't get 2 lines of tips to get unstuck yesterday, and now that I have my thing working, you come and write books about the damn topic bite me

  • @GoToLoop

    Ru sure Java would be so irresponsible to no-op an assignment operation, so the other threads would never eventually see the changed value in a shared non-volatile field???

    Yes! And no it's not irresponsible, it's how optimization works. ;-)

    As one of many (many) links on this - have a read of https://stackoverflow.com/questions/9580605/is-it-possible-to-modify-a-non-volatile-variable-such-that-another-thread-is-abl

    This might work fine for you, always, never, or just for a while. It might work fine on one Java version but not the next, one OS but not the next, one CPU but not the next. Or work until HotSpot kicks in with some extra optimizations. You might be surprised how different the code emitted by HotSpot is from what you write.

    Incidentally, the thread termination rules might actually make the multiple threads option more stable, but only because of other things going on around the animation thread.

    Not sure this has much to do with solving @phoebus original problem! :-)

  • @phoebus -- unfortunately it was two kinds of busy - busy at work, and lots of threads pouring in eg for GSOC2018, so some things blew by.

    That said, this might be a case where framing what you want in title and top line in terms of your goal would get better attention. I saw this question as: how do you use AsyncTask. :-?? But it was actually: how can I use threads for x.

  • edited March 13

    volatile can be needed in a number of situations, but I don't believe you have one of them.

    This optimisation can happen after a loop iterates 10,000 times in quick succession. In your case, it is not quick succession, but over 2.7 hours so the JIT might never optimise the code.

    • Therefore, according to that Java expert, Java's JIT would only optimize away (no-op the whole field) when it's inside a big very tight loop. ~:>

    It might work fine on one Java version but not the next, one OS but not the next, one CPU but not the next.

    • I've been using Java since Processing 1.5.1, which I believe used Java 5 or 6.
    • Never seen any issues about field change visibility across multiple threads so far.
    • Actually I've lied. :^o I recall now I had such bug in 1 of my oldest sketches.
    • But that happened exactly as Peter Lawrey described: tight loop!
    • Back then I didn't know I needed to use delay() in order to not burn my CPU! X_X
    • Once I've added delay() inside the tight loop, the field update became visible to the other Thread! =P~
    • Another consideration to take is that the Java specification is much more vague than the actual implementation.
    • A very good example is that the Java specification doesn't say how many bytes a boolean datatype occupies. @-)
    • But I highly doubt there is any Java implementation out there whose boolean would occupy more than 1 byte of memory. That'd be stupid for many reasons! :@)
  • edited March 13

    @GoToLoop - you can believe what you want! ;-)

    Another consideration to take is that the Java specification is much more vague than the actual implementation.

    All specifications are vaguer than their implementation! But the Java Memory Model spec is quite specific. I read Peter's response and not sure what to make of it - "don't believe" and "might never" are not two statements that go well together IMO.

    btw - don't forget draw() might get optimized - it's the read on that side that's more problematic.

    But I highly doubt there is any Java implementation out there whose boolean would occupy more than 1 byte of memory. That'd be stupid for many reasons!

    Perhaps, but you use one all the time! I think you'll find boolean, byte, char and short all occupy 4 bytes. ;-)

    https://docs.oracle.com/javase/specs/jvms/se7/jvms7.pdf

    EDIT: OK, engage brain, spot the "deliberate" mistake. That's the JVM spec, what I was actually thinking of was around alignment, which is more a case of it depends.

Sign In or Register to comment.