[androidkit] Introduce Image support

Two sources from now:

  - encoded data factory
  - Surface.makeImageSnapshot()

Change-Id: Icbf2f855e489839dc82db425916c8644418a2fb7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/410324
Reviewed-by: Jorge Betancourt <jmbetancourt@google.com>
Commit-Queue: Florin Malita <fmalita@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 390e616..45bc668 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -2495,6 +2495,7 @@
       sources = [
         "modules/androidkit/src/AndroidKit.cpp",
         "modules/androidkit/src/Canvas.cpp",
+        "modules/androidkit/src/Image.cpp",
         "modules/androidkit/src/Matrix.cpp",
         "modules/androidkit/src/Paint.cpp",
         "modules/androidkit/src/RuntimeShaderBuilder.cpp",
diff --git a/modules/androidkit/src/AndroidKit.cpp b/modules/androidkit/src/AndroidKit.cpp
index c967650..cf02523 100644
--- a/modules/androidkit/src/AndroidKit.cpp
+++ b/modules/androidkit/src/AndroidKit.cpp
@@ -24,6 +24,7 @@
     }
 
     REGISTER_NATIVES(Canvas)
+    REGISTER_NATIVES(Image)
     REGISTER_NATIVES(Matrix)
     REGISTER_NATIVES(Paint)
     REGISTER_NATIVES(RuntimeShaderBuilder)
diff --git a/modules/androidkit/src/Canvas.cpp b/modules/androidkit/src/Canvas.cpp
index 038437e..3a37f0a 100644
--- a/modules/androidkit/src/Canvas.cpp
+++ b/modules/androidkit/src/Canvas.cpp
@@ -79,6 +79,16 @@
     }
 }
 
+void Canvas_DrawImage(JNIEnv* env, jobject, jlong native_instance, jlong native_image,
+                      jfloat x, jfloat y) {
+    auto* canvas = reinterpret_cast<SkCanvas*>(native_instance);
+    auto*  image = reinterpret_cast<SkImage *>(native_image);
+
+    if (canvas && image) {
+        canvas->drawImage(image, x, y);
+    }
+}
+
 }  // namespace
 
 int register_androidkit_Canvas(JNIEnv* env) {
@@ -92,6 +102,7 @@
         {"nConcat16f"       , "(J[F)V"   , reinterpret_cast<void*>(Canvas_Concat16f)     },
         {"nDrawColor"       , "(JFFFF)V" , reinterpret_cast<void*>(Canvas_DrawColor)     },
         {"nDrawRect"        , "(JFFFFJ)V", reinterpret_cast<void*>(Canvas_DrawRect)      },
+        {"nDrawImage"       , "(JJFF)V"  , reinterpret_cast<void*>(Canvas_DrawImage)     },
     };
 
     const auto clazz = env->FindClass("org/skia/androidkit/Canvas");
diff --git a/modules/androidkit/src/Image.cpp b/modules/androidkit/src/Image.cpp
new file mode 100644
index 0000000..435d085
--- /dev/null
+++ b/modules/androidkit/src/Image.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <jni.h>
+
+#include "include/core/SkImage.h"
+
+namespace {
+
+jlong Image_Create(JNIEnv* env, jobject, jbyteArray jdata) {
+    auto  size = env->GetArrayLength(jdata);
+    auto* data = env->GetByteArrayElements(jdata, nullptr);
+    auto image = SkImage::MakeFromEncoded(SkData::MakeWithCopy(data, SkToSizeT(size)));
+
+    env->ReleaseByteArrayElements(jdata, data, 0);
+
+    return reinterpret_cast<jlong>(image.release());
+}
+
+void Image_Release(JNIEnv*, jobject, jlong native_instance) {
+    SkSafeUnref(reinterpret_cast<const SkImage*>(native_instance));
+}
+
+jint Image_GetWidth(JNIEnv*, jobject, jlong native_instance) {
+    const auto* image = reinterpret_cast<const SkImage*>(native_instance);
+    return image ? image->width() : 0;
+}
+
+jint Image_GetHeight(JNIEnv*, jobject, jlong native_instance) {
+    const auto* image = reinterpret_cast<const SkImage*>(native_instance);
+    return image ? image->height() : 0;
+}
+
+} // namespace
+
+int register_androidkit_Image(JNIEnv* env) {
+    static const JNINativeMethod methods[] = {
+        {"nCreate"   , "([B)J", reinterpret_cast<void*>(Image_Create)   },
+        {"nRelease"  , "(J)V" , reinterpret_cast<void*>(Image_Release)  },
+
+        {"nGetWidth" , "(J)I" , reinterpret_cast<void*>(Image_GetWidth) },
+        {"nGetHeight", "(J)I" , reinterpret_cast<void*>(Image_GetHeight)},
+    };
+
+    const auto clazz = env->FindClass("org/skia/androidkit/Image");
+    return clazz
+        ? env->RegisterNatives(clazz, methods, SK_ARRAY_COUNT(methods))
+        : JNI_ERR;
+}
diff --git a/modules/androidkit/src/Surface.cpp b/modules/androidkit/src/Surface.cpp
index b24891a..8732f9f 100644
--- a/modules/androidkit/src/Surface.cpp
+++ b/modules/androidkit/src/Surface.cpp
@@ -225,35 +225,45 @@
     }
 }
 
-static int Surface_GetWidth(JNIEnv* env, jobject, jlong native_surface) {
+static jint Surface_GetWidth(JNIEnv* env, jobject, jlong native_surface) {
     const auto* surface = reinterpret_cast<Surface*>(native_surface);
     return surface ? surface->width() : 0;
 }
 
-static int Surface_GetHeight(JNIEnv* env, jobject, jlong native_surface) {
+static jint Surface_GetHeight(JNIEnv* env, jobject, jlong native_surface) {
     const auto* surface = reinterpret_cast<Surface*>(native_surface);
     return surface ? surface->height() : 0;
 }
 
+static jlong Surface_MakeSnapshot(JNIEnv* env, jobject, jlong native_surface) {
+    if (const auto* surface = reinterpret_cast<Surface*>(native_surface)) {
+        auto snapshot = surface->makeImageSnapshot();
+        return reinterpret_cast<jlong>(snapshot.release());
+    }
+
+    return 0;
+}
+
 // *** End of JNI methods ***
 
 }  // namespace
 
 int register_androidkit_Surface(JNIEnv* env) {
     static const JNINativeMethod methods[] = {
-        {"nCreateBitmap"   , "(Landroid/graphics/Bitmap;)J",
-            reinterpret_cast<void*>(Surface_CreateBitmap)                            },
+        {"nCreateBitmap"     , "(Landroid/graphics/Bitmap;)J",
+            reinterpret_cast<void*>(Surface_CreateBitmap)                              },
         {"nCreateThreadedSurface"  , "(Landroid/view/Surface;)J",
-            reinterpret_cast<void*>(Surface_CreateThreadedSurface)                   },
-        {"nCreateVKSurface", "(Landroid/view/Surface;)J",
-            reinterpret_cast<void*>(Surface_CreateVK)                                },
-        {"nCreateGLSurface", "(Landroid/view/Surface;)J",
-            reinterpret_cast<void*>(Surface_CreateGL)                                },
-        {"nRelease"        , "(J)V", reinterpret_cast<void*>(Surface_Release)        },
-        {"nGetNativeCanvas", "(J)J", reinterpret_cast<void*>(Surface_GetNativeCanvas)},
-        {"nFlushAndSubmit" , "(J)V", reinterpret_cast<void*>(Surface_FlushAndSubmit) },
-        {"nGetWidth"       , "(J)I", reinterpret_cast<void*>(Surface_GetWidth)       },
-        {"nGetHeight"      , "(J)I", reinterpret_cast<void*>(Surface_GetHeight)      },
+            reinterpret_cast<void*>(Surface_CreateThreadedSurface)                     },
+        {"nCreateVKSurface"  , "(Landroid/view/Surface;)J",
+            reinterpret_cast<void*>(Surface_CreateVK)                                  },
+        {"nCreateGLSurface"  , "(Landroid/view/Surface;)J",
+            reinterpret_cast<void*>(Surface_CreateGL)                                  },
+        {"nRelease"          , "(J)V", reinterpret_cast<void*>(Surface_Release)        },
+        {"nGetNativeCanvas"  , "(J)J", reinterpret_cast<void*>(Surface_GetNativeCanvas)},
+        {"nFlushAndSubmit"   , "(J)V", reinterpret_cast<void*>(Surface_FlushAndSubmit) },
+        {"nGetWidth"         , "(J)I", reinterpret_cast<void*>(Surface_GetWidth)       },
+        {"nGetHeight"        , "(J)I", reinterpret_cast<void*>(Surface_GetHeight)      },
+        {"nMakeImageSnapshot", "(J)J", reinterpret_cast<void*>(Surface_MakeSnapshot)   },
     };
 
     const auto clazz = env->FindClass("org/skia/androidkit/Surface");
diff --git a/modules/androidkit/src/Surface.h b/modules/androidkit/src/Surface.h
index 7162087..6da122a 100644
--- a/modules/androidkit/src/Surface.h
+++ b/modules/androidkit/src/Surface.h
@@ -17,6 +17,7 @@
 
 #include "tools/sk_app/WindowContext.h"
 
+#include "include/core/SkImage.h"
 #include "include/core/SkPictureRecorder.h"
 #include "include/core/SkRefCnt.h"
 #include "include/core/SkSurface.h"
@@ -34,6 +35,10 @@
     int width()  const { return fSurface ? fSurface->width()  : 0; }
     int height() const { return fSurface ? fSurface->height() : 0; }
 
+    sk_sp<SkImage> makeImageSnapshot() const {
+        return fSurface ? fSurface->makeImageSnapshot() : nullptr;
+    }
+
 protected:
     sk_sp<SkSurface> fSurface;
 };
diff --git a/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Canvas.java b/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Canvas.java
index f804126..e427e43 100644
--- a/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Canvas.java
+++ b/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Canvas.java
@@ -8,6 +8,7 @@
 package org.skia.androidkit;
 
 import org.skia.androidkit.Color;
+import org.skia.androidkit.Image;
 import org.skia.androidkit.Matrix;
 import org.skia.androidkit.Paint;
 import org.skia.androidkit.Surface;
@@ -70,6 +71,11 @@
         );
     }
 
+    // TODO: sampling options
+    public void drawImage(Image image, float x, float y) {
+        nDrawImage(mNativeInstance, image.getNativeInstance(), x, y);
+    }
+
     // package private
     Canvas(Surface surface, long native_instance) {
         mNativeInstance = native_instance;
@@ -92,4 +98,5 @@
     private static native void nDrawRect(long nativeInstance,
                                          float left, float right, float top, float bottom,
                                          long nativePaint);
+    private static native void nDrawImage(long nativeInstance, long nativeImage, float x, float y);
 }
diff --git a/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Image.java b/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Image.java
new file mode 100644
index 0000000..dea8f21
--- /dev/null
+++ b/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Image.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+package org.skia.androidkit;
+
+public class Image {
+    private long mNativeInstance;
+
+    /**
+     * Construct an Image from encoded (PNG, GIF, etc) data.
+     *
+     * Returns null for unsupported formats or invalid data.
+     */
+    public static Image fromEncoded(byte[] encodedData) {
+        long nativeImage = nCreate(encodedData);
+        return nativeImage != 0
+            ? new Image(nativeImage)
+            : null;
+    }
+
+    public int getWidth() {
+        return nGetWidth(mNativeInstance);
+    }
+
+    public int getHeight() {
+        return nGetHeight(mNativeInstance);
+    }
+
+    /**
+     * Releases any resources associated with this Paint.
+     */
+    public void release() {
+        nRelease(mNativeInstance);
+        mNativeInstance = 0;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        release();
+    }
+
+    // package private
+    Image(long nativeInstance) {
+        mNativeInstance = nativeInstance;
+    }
+
+    // package private
+    long getNativeInstance() { return mNativeInstance; }
+
+    private static native long nCreate(byte[] encodedData);
+    private static native void nRelease(long nativeInstance);
+
+    private static native int nGetWidth(long nativeInstance);
+    private static native int nGetHeight(long nativeInstance);
+}
diff --git a/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Surface.java b/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Surface.java
index dded1b0..541dd17 100644
--- a/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Surface.java
+++ b/platform_tools/android/apps/AndroidKit/src/main/java/org/skia/androidkit/Surface.java
@@ -12,6 +12,7 @@
 import android.support.annotation.RequiresApi;
 
 import org.skia.androidkit.Canvas;
+import org.skia.androidkit.Image;
 
 public class Surface {
     private long mNativeInstance;
@@ -51,6 +52,15 @@
         return new Canvas(this, nGetNativeCanvas(mNativeInstance));
     }
 
+
+    /**
+     * Returns an Image capturing the Surface contents.
+     * Subsequent drawing to Surface contents are not captured.
+     */
+    public Image makeImageSnapshot() {
+        return new Image(nMakeImageSnapshot(mNativeInstance));
+    }
+
     /***
      * Triggers the immediate execution of all pending draw operations.
      *
@@ -104,4 +114,5 @@
     private static native void nFlushAndSubmit(long nativeInstance);
     private static native int  nGetWidth(long nativeInstance);
     private static native int  nGetHeight(long nativeInstance);
+    private static native long nMakeImageSnapshot(long nativeInstance);
 }
diff --git a/platform_tools/android/apps/androidkitdemo/src/main/java/org/skia/androidkitdemo1/MainActivity.java b/platform_tools/android/apps/androidkitdemo/src/main/java/org/skia/androidkitdemo1/MainActivity.java
index 94ab98e..79fb2b3 100644
--- a/platform_tools/android/apps/androidkitdemo/src/main/java/org/skia/androidkitdemo1/MainActivity.java
+++ b/platform_tools/android/apps/androidkitdemo/src/main/java/org/skia/androidkitdemo1/MainActivity.java
@@ -11,9 +11,11 @@
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
+import android.util.Log;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.widget.ImageView;
+import java.io.InputStream;
 import org.skia.androidkit.*;
 
 public class MainActivity extends Activity implements SurfaceHolder.Callback {
@@ -33,7 +35,7 @@
 
         // Bitmap
         Bitmap.Config conf = Bitmap.Config.ARGB_8888;
-        Bitmap bmp = Bitmap.createBitmap(200, 200, conf);
+        Bitmap bmp = Bitmap.createBitmap(400, 400, conf);
         Surface bitmapSurface = new Surface(bmp);
         Canvas canvas = bitmapSurface.getCanvas();
 
@@ -49,6 +51,20 @@
         canvas.drawRect(0, 0, 100, 100, p);
         canvas.restore();
 
+        Image snapshot = bitmapSurface.makeImageSnapshot();
+        canvas.drawImage(snapshot, 0, 200);
+
+        try {
+            InputStream is = getResources().openRawResource(R.raw.brickwork_texture);
+            byte[] data = new byte[is.available()];
+            is.read(data);
+
+            Image image = Image.fromEncoded(data);
+            canvas.drawImage(image, 200, 0);
+        } catch (Exception e) {
+            Log.e("AndroidKit Demo", "Could not load Image resource: " + R.raw.brickwork_texture);
+        }
+
         ImageView image = findViewById(R.id.image);
         image.setImageBitmap(bmp);
 
diff --git a/platform_tools/android/apps/androidkitdemo/src/main/res/layout/activity_main.xml b/platform_tools/android/apps/androidkitdemo/src/main/res/layout/activity_main.xml
index 00e9d0f..b42da69 100644
--- a/platform_tools/android/apps/androidkitdemo/src/main/res/layout/activity_main.xml
+++ b/platform_tools/android/apps/androidkitdemo/src/main/res/layout/activity_main.xml
@@ -16,8 +16,8 @@
         app:layout_constraintTop_toTopOf="parent" />
     <ImageView
         android:id="@+id/image"
-        android:layout_width="200px"
-        android:layout_height="200px"
+        android:layout_width="400px"
+        android:layout_height="400px"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintTop_toTopOf="parent">
     </ImageView>
diff --git a/platform_tools/android/apps/androidkitdemo/src/main/res/raw/brickwork_texture.jpg b/platform_tools/android/apps/androidkitdemo/src/main/res/raw/brickwork_texture.jpg
new file mode 100644
index 0000000..9a7dd11
--- /dev/null
+++ b/platform_tools/android/apps/androidkitdemo/src/main/res/raw/brickwork_texture.jpg
Binary files differ