commit 57dc13015d4b74e048659c14d8573a86a140d3bb
Author: Leszek Koltunski <leszek@distorted.org>
Date:   Fri Jun 10 16:39:46 2016 +0100

    Implement SAVE_PNG

diff --git a/src/main/java/org/distorted/library/EffectMessage.java b/src/main/java/org/distorted/library/EffectMessage.java
index 20eb493..1de0ea9 100644
--- a/src/main/java/org/distorted/library/EffectMessage.java
+++ b/src/main/java/org/distorted/library/EffectMessage.java
@@ -28,7 +28,14 @@ public enum EffectMessage
  * If then the end effect is equal to the effect's zero point, then immediately after this message you 
  * will also get a EFFECT_REMOVED message.
  */
-  EFFECT_FINISHED;
+  EFFECT_FINISHED,
+
+  /**
+ * The effect has failed to properly execute.
+ * <p>
+ * Currently only OTHER effects (saving to PNG file and to a MP4 movie) can fail.
+ */
+  EFFECT_FAILED;
   }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectQueueOther.java b/src/main/java/org/distorted/library/EffectQueueOther.java
index d9a1176..bc60f9c 100644
--- a/src/main/java/org/distorted/library/EffectQueueOther.java
+++ b/src/main/java/org/distorted/library/EffectQueueOther.java
@@ -2,6 +2,15 @@ package org.distorted.library;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+import android.graphics.Bitmap;
+import android.opengl.GLES20;
+
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
 class EffectQueueOther extends EffectQueue
   {
   private static final int NUM_UNIFORMS = 0;
@@ -28,6 +37,7 @@ class EffectQueueOther extends EffectQueue
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// runs on the graphics thread
 
   synchronized void send()
     {
@@ -35,7 +45,50 @@ class EffectQueueOther extends EffectQueue
       {
       if (mType[i] == EffectNames.SAVE_PNG.ordinal() )
         {
-        // TODO: Implement SAVE_PNG HERE
+        ByteBuffer buf = ByteBuffer.allocateDirect( (int)(mObjHalfX * mObjHalfY * 16) );
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        GLES20.glReadPixels(0, 0, (int)(2*mObjHalfX), (int)(2*mObjHalfY) , GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
+
+        buf.rewind();
+
+        BufferedOutputStream bos = null;
+
+        try
+          {
+          bos = new BufferedOutputStream(new FileOutputStream(mFilename[i]));
+          Bitmap bmp = Bitmap.createBitmap( (int)(2*mObjHalfX), (int)(2*mObjHalfY), Bitmap.Config.ARGB_8888);
+          bmp.copyPixelsFromBuffer(buf);
+          bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
+          bmp.recycle();
+
+          for(int j=0; j<mNumListeners; j++)
+            EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                            EffectMessage.EFFECT_FINISHED,
+                                           (mID[i]<<EffectTypes.LENGTH)+EffectTypes.OTHER.type,
+                                            mType[i],
+                                            mBitmapID);
+          }
+        catch(Exception e)
+          {
+          for(int j=0; j<mNumListeners; j++)
+            EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                            EffectMessage.EFFECT_FAILED,
+                                           (mID[i]<<EffectTypes.LENGTH)+EffectTypes.OTHER.type,
+                                            mType[i],
+                                            mBitmapID);
+          }
+        finally
+          {
+          if (bos != null)
+            {
+            try {bos.close();}
+            catch(IOException io ) {}
+            }
+          }
+
+        remove(i);
+        i--;
+        continue;
         }
       else if (mType[i] == EffectNames.SAVE_MP4.ordinal() )
         {
