commit b9615af9076727b2e788978374a7b05abd831378
Author: Leszek Koltunski <leszek@distoretedandroid.org>
Date:   Mon Jun 13 13:01:45 2016 +0100

    Save PNG effect almost finished. Supporting App (hopefully!) completely finished.
    
    What remains to be done: put actual saving of the Bitmap in a separate thread, away from the Graphics thread!!

diff --git a/src/main/java/org/distorted/examples/listener/ListenerRenderer.java b/src/main/java/org/distorted/examples/listener/ListenerRenderer.java
index 0708d34..3e08bb1 100644
--- a/src/main/java/org/distorted/examples/listener/ListenerRenderer.java
+++ b/src/main/java/org/distorted/examples/listener/ListenerRenderer.java
@@ -61,8 +61,9 @@ class ListenerRenderer implements GLSurfaceView.Renderer,EffectListener
       }
    
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// the library sending messages to us
 
-   public void effectMessage(EffectMessage em, long effectID, int effectType, long bitmapID)
+   public void effectMessage(final EffectMessage em, final long effectID, final int effectName, final long bitmapID, final String message)
      {
      switch(em)
         {
diff --git a/src/main/java/org/distorted/examples/save/SaveRenderer.java b/src/main/java/org/distorted/examples/save/SaveRenderer.java
index 4cddc1b..da2412d 100644
--- a/src/main/java/org/distorted/examples/save/SaveRenderer.java
+++ b/src/main/java/org/distorted/examples/save/SaveRenderer.java
@@ -4,6 +4,7 @@ package org.distorted.examples.save;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Vector;
 
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
@@ -12,153 +13,234 @@ import org.distorted.examples.R;
 
 import org.distorted.library.Distorted;
 import org.distorted.library.DistortedBitmap;
+import org.distorted.library.EffectListener;
+import org.distorted.library.EffectMessage;
 import org.distorted.library.EffectTypes;
 import org.distorted.library.Float1D;
 import org.distorted.library.Float2D;
 import org.distorted.library.Float4D;
 import org.distorted.library.Interpolator1D;
 
+import android.app.Activity;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.opengl.GLES20;
 import android.opengl.GLSurfaceView;
 import android.os.Environment;
+import android.widget.Toast;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-class SaveRenderer implements GLSurfaceView.Renderer 
-{
-    private GLSurfaceView mView;
-    private static DistortedBitmap mGirl;
-    private Float2D pLeft, pRight;
-    private Float4D sinkRegion;
-    private static Interpolator1D diSink;
-    private static Float1D s0;
-    
-    private int bmpHeight, bmpWidth;
-    
-    private static float boobsSink;
-    
+class SaveRenderer implements GLSurfaceView.Renderer ,EffectListener
+  {
+  private GLSurfaceView mView;
+  private static DistortedBitmap mGirl;
+  private Float2D pLeft, pRight;
+  private Float4D sinkRegion;
+  private static Interpolator1D diSink;
+  private static Float1D s0;
+
+  private int bmpHeight, bmpWidth;
+  private static int scrHeight, scrWidth;
+  private static float boobsSink;
+
+  private static class SaveInfo
+    {
+    private long id;
+    private String path;
+
+    SaveInfo(long id, String path)
+      {
+      this.id   =   id;
+      this.path = path;
+      }
+    }
+
+  private static Vector<SaveInfo> mSaveInfo;  // Vector keeping all the information about the PATH
+                                              // where current 'SAVE' effects are saving to and also
+                                              // the IDs of the effects.
+                                              // We need to have a whole Vector because there can be
+                                              // many concurrent 'SAVE' operations (click 'SAVE',
+                                              // don't wait for the Toast to appear, immediately
+                                              // click again! )
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   public SaveRenderer(GLSurfaceView v) 
-      {
-      mView = v;
+  public SaveRenderer(GLSurfaceView v)
+    {
+    mView = v;
       
-      boobsSink  = 1.0f;
+    boobsSink  = 1.0f;
       
-      pLeft = new Float2D(132, 264);
-      pRight= new Float2D(247, 264);
+    pLeft = new Float2D(132, 264);
+    pRight= new Float2D(247, 264);
       
-      // Make the boobs bigger
-      sinkRegion = new Float4D(0,0,60,60);
+    sinkRegion = new Float4D(0,0,60,60);
       
-      s0 = new Float1D(boobsSink);
+    s0 = new Float1D(boobsSink);
       
-      diSink = new Interpolator1D(); 
-      diSink.setCount(0.5f);
-      diSink.setDuration(0);
-      diSink.add(s0);
-      }
+    diSink = new Interpolator1D();
+    diSink.setCount(0.5f);
+    diSink.setDuration(0);
+    diSink.add(s0);
+
+    mSaveInfo = new Vector<>();
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   public static void setSize(float s)
-     {
-     boobsSink = s;
-     s0.set(boobsSink);
-     }
+  public static void setSize(float s)
+    {
+    boobsSink = s;
+    s0.set(boobsSink);
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   public static void Save()
-     {
-     File file;
-     String filePath;
-     int lowestNotFound = 1;
-
-     while(true)
-       {
-       filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/girl" + lowestNotFound +".png";
-       file = new File(filePath);
-
-       if( !file.exists() )
-         {
-         android.util.Log.e("SAVE", "Saving to "+file.getAbsolutePath());
-         mGirl.savePNG(file.getAbsolutePath());
-         break;
-         }
-       lowestNotFound++;
-       }
-     }
+  public static void Save()
+    {
+    File file;
+    int lowestNotFound = 1;
+    String path;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-   public void onDrawFrame(GL10 glUnused) 
+    while(true)
       {
-      GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-      GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
-      
-      mGirl.draw(System.currentTimeMillis());
+      path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/girl" + lowestNotFound +".png";
+      file = new File(path);
+
+      if( !file.exists() )
+        {
+        long id= mGirl.savePNG(file.getAbsolutePath(),0,0,scrWidth,scrHeight);
+        mSaveInfo.add(new SaveInfo(id,path));
+        break;
+        }
+      lowestNotFound++;
       }
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    public void onSurfaceChanged(GL10 glUnused, int width, int height) 
-      { 
-      mGirl.abortEffects(EffectTypes.MATRIX);
-      
-      if( bmpHeight/bmpWidth > height/width )
-        {
-        int w = (height*bmpWidth)/bmpHeight;
-        mGirl.move((width-w)/2 ,0, 0);
-        mGirl.scale((float)height/bmpHeight);
+// the library sending messages to us. This does not run on this UI thread!
+
+  public void effectMessage(final EffectMessage em, final long effectID, final int effectName, final long bitmapID, final String message)
+    {
+    int numSaves = mSaveInfo.size();
+    int found = -1;
+
+    for(int i=0; i<numSaves; i++)           // doesn't need to be synchronized with Save()
+      {                                     // because over there we only add() and here we only
+      if( effectID == mSaveInfo.get(i).id ) // remove() - and even if we add() in between of
+        {                                   // computing the 'numSaves' above and this loop,
+        found = i;                          // it still doesn't matter :)
+        break;
         }
-      else
+      }
+
+    if( found>=0 )  // the message really is about one of the 'SAVE' effects in execution now
+      {
+      Activity act = (Activity)mView.getContext();
+      final String path = mSaveInfo.get(found).path;
+      mSaveInfo.remove(found);
+
+      switch(em)
         {
-        int h = (width*bmpHeight)/bmpWidth;
-        mGirl.move(0 ,(height-h)/2, 0);
-        mGirl.scale((float)width/bmpWidth);
+        case EFFECT_FINISHED: act.runOnUiThread(new Runnable()
+                                {
+                                public void run()
+                                  {
+                                  Toast.makeText(mView.getContext(),
+                                      "Saving to \n\n"+path+"\n\nsuccessful" ,
+                                      Toast.LENGTH_LONG).show();
+                                  }
+                                });
+                              break;
+
+        case EFFECT_FAILED  : act.runOnUiThread(new Runnable()
+                                {
+                                public void run()
+                                  {
+                                  Toast.makeText(mView.getContext(),
+                                      "Saving to \n\n"+path+"\n\n failed: "+"\n\n"+message ,
+                                      Toast.LENGTH_LONG).show();
+                                  }
+                                });
+                              break;
+
+        default             : break;
         }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public void onDrawFrame(GL10 glUnused)
+    {
+    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+    GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
       
-      Distorted.onSurfaceChanged(width, height); 
+    mGirl.draw(System.currentTimeMillis());
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+  public void onSurfaceChanged(GL10 glUnused, int width, int height)
+    {
+    scrWidth = width;
+    scrHeight= height;
+
+    mGirl.abortEffects(EffectTypes.MATRIX);
+      
+    if( bmpHeight/bmpWidth > height/width )
+      {
+      int w = (height*bmpWidth)/bmpHeight;
+      mGirl.move((width-w)/2 ,0, 0);
+      mGirl.scale((float)height/bmpHeight);
       }
+    else
+      {
+      int h = (width*bmpHeight)/bmpWidth;
+      mGirl.move(0 ,(height-h)/2, 0);
+      mGirl.scale((float)width/bmpWidth);
+      }
+      
+    Distorted.onSurfaceChanged(width, height);
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
     
-    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) 
-      {    
-      InputStream is = mView.getContext().getResources().openRawResource(R.raw.girl);
-      Bitmap bitmap;
+  public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
+    {
+    InputStream is = mView.getContext().getResources().openRawResource(R.raw.girl);
+    Bitmap bitmap;
         
-      try 
-        {
-        bitmap = BitmapFactory.decodeStream(is);
-        } 
-      finally 
+    try
+      {
+      bitmap = BitmapFactory.decodeStream(is);
+      }
+    finally
+      {
+      try
         {
-        try 
-          {
-          is.close();
-          } 
-        catch(IOException e) { }
-        }  
+        is.close();
+        }
+      catch(IOException e) { }
+      }
       
-      bmpHeight = bitmap.getHeight();
-      bmpWidth  = bitmap.getWidth();
+    bmpHeight = bitmap.getHeight();
+    bmpWidth  = bitmap.getWidth();
       
-      mGirl = new DistortedBitmap(bitmap, 30);
+    mGirl = new DistortedBitmap(bitmap, 30);
+    mGirl.addEventListener(this);
 
-      mGirl.sink( diSink, sinkRegion, pLeft );
-      mGirl.sink( diSink, sinkRegion, pRight);
+    mGirl.sink( diSink, sinkRegion, pLeft );
+    mGirl.sink( diSink, sinkRegion, pRight);
 
-      try
-        {
-        Distorted.onSurfaceCreated(mView.getContext());
-        }
-      catch(Exception ex)
-        {
-        android.util.Log.e("Renderer", ex.getMessage() );
-        }
+    try
+      {
+      Distorted.onSurfaceCreated(mView.getContext());
+      }
+    catch(Exception ex)
+      {
+      android.util.Log.e("Renderer", ex.getMessage() );
       }
-}
+    }
+  }
diff --git a/src/main/java/org/distorted/examples/starwars/StarWarsRenderer.java b/src/main/java/org/distorted/examples/starwars/StarWarsRenderer.java
index ea3eeab..b1cb748 100644
--- a/src/main/java/org/distorted/examples/starwars/StarWarsRenderer.java
+++ b/src/main/java/org/distorted/examples/starwars/StarWarsRenderer.java
@@ -312,7 +312,7 @@ class StarWarsRenderer implements GLSurfaceView.Renderer, EffectListener
     
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void effectMessage(EffectMessage em, long effectID, int effectType, long bitmapID)
+    public void effectMessage(final EffectMessage em, final long effectID, final int effectName, final long bitmapID, final String message)
       {
       if( em==EffectMessage.EFFECT_FINISHED )
         {
