Bug 1530402 - Provide GeckoImageDecoder. r=snorp
authorAgi Sferro <agi@sferro.dev>
Mon, 18 Nov 2019 16:48:55 +0000
changeset 502433 0fdc505ba2f610794fce1503e21f9be99f58f108
parent 502432 9afd2ad768ec8b7fde452d4c28707ef1714cf313
child 502434 13283dd2b5fd03bc940d1fd42d3a85dc60322943
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1530402
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1530402 - Provide GeckoImageDecoder. r=snorp This class allows GeckoView embedders to decode images using Gecko's image library. Differential Revision: https://phabricator.services.mozilla.com/D49038
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ImageDecoder.java
widget/android/ImageDecoderSupport.cpp
widget/android/ImageDecoderSupport.h
widget/android/bindings/JavaExceptions-classes.txt
widget/android/moz.build
widget/android/nsAppShell.cpp
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ImageDecoder.java
@@ -0,0 +1,82 @@
+package org.mozilla.geckoview;
+
+import android.graphics.Bitmap;
+import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+
+import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.annotation.WrapForJNI;
+
+/**
+ * Provides access to Gecko's Image processing library.
+ */
+@AnyThread
+/* protected */ class ImageDecoder {
+    private static ImageDecoder instance;
+
+    private ImageDecoder() {}
+
+    public static ImageDecoder instance() {
+        if (instance == null) {
+            instance = new ImageDecoder();
+        }
+
+        return instance;
+    }
+
+    @WrapForJNI(dispatchTo = "gecko", stubName = "Decode")
+    private static native void nativeDecode(final String uri, final int desiredLength,
+                                            GeckoResult<Bitmap> result);
+
+    /**
+     * Fetches and decodes an image at the specified location.
+     * This method supports SVG, PNG, Bitmap and other formats supported by Gecko.
+     *
+     * @param uri location of the image. Can be either a remote https:// location, file:/// if the
+     *            file is local or a resource://android/ if the file is located inside the APK.
+     *
+     *            e.g. if the image file is locate at /assets/test.png inside the apk, set the uri
+     *            to resource://android/assets/test.png.
+     * @return A {@link GeckoResult} to the decoded image.
+     */
+    @NonNull
+    public GeckoResult<Bitmap> decode(final @NonNull String uri) {
+        return decode(uri, 0);
+    }
+
+    /**
+     * Fetches and decodes an image at the specified location and resizes it to the desired length.
+     * This method supports SVG, PNG, Bitmap and other formats supported by Gecko.
+     *
+     * Note: The final size might differ slightly from the requested output.
+     *
+     * @param uri location of the image. Can be either a remote https:// location, file:/// if the
+     *            file is local or a resource://android/ if the file is located inside the APK.
+     *
+     *            e.g. if the image file is locate at /assets/test.png inside the apk, set the uri
+     *            to resource://android/assets/test.png.
+     * @param desiredLength Longest size for the image in device pixel units. The resulting image
+     *                      might be slightly different if the image cannot be resized efficiently.
+     *                      If desiredLength is 0 then the image will be decoded to its natural
+     *                      size.
+     * @return A {@link GeckoResult} to the decoded image.
+     */
+    @NonNull
+    public GeckoResult<Bitmap> decode(final @NonNull String uri, final int desiredLength) {
+        if (uri == null) {
+            throw new IllegalArgumentException("Uri cannot be null");
+        }
+
+        final GeckoResult<Bitmap> result = new GeckoResult<>();
+
+        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+            nativeDecode(uri, desiredLength, result);
+        } else {
+            GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, this,
+                    "nativeDecode", String.class, uri, int.class, desiredLength,
+                    GeckoResult.class, result);
+        }
+
+        return result;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/widget/android/ImageDecoderSupport.cpp
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImageDecoderSupport.h"
+
+#include "imgITools.h"
+#include "gfxUtils.h"
+#include "AndroidGraphics.h"
+#include "JavaExceptions.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Swizzle.h"
+
+namespace mozilla {
+namespace widget {
+
+namespace {
+
+class ImageCallbackHelper;
+
+HashSet<RefPtr<ImageCallbackHelper>,
+      PointerHasher<ImageCallbackHelper*>>
+  gDecodeRequests;
+
+class ImageCallbackHelper : public imgIContainerCallback,
+                            public imgINotificationObserver {
+ public:
+  NS_DECL_ISUPPORTS
+
+  void CompleteExceptionally(const char* aMessage) {
+    mResult->CompleteExceptionally(java::sdk::IllegalArgumentException::New(aMessage)
+        .Cast<jni::Throwable>());
+    gDecodeRequests.remove(this);
+  }
+
+  void Complete(DataSourceSurface::ScopedMap& aSourceSurface, int32_t width, int32_t height) {
+    auto pixels = mozilla::jni::ByteBuffer::New(
+        reinterpret_cast<int8_t*>(aSourceSurface.GetData()),
+        aSourceSurface.GetStride() * height);
+    auto bitmap = java::sdk::Bitmap::CreateBitmap(
+        width, height, java::sdk::Config::ARGB_8888());
+    bitmap->CopyPixelsFromBuffer(pixels);
+    mResult->Complete(bitmap);
+    gDecodeRequests.remove(this);
+  }
+
+  ImageCallbackHelper(java::GeckoResult::Param aResult, int32_t aDesiredLength)
+      : mResult(aResult), mDesiredLength(aDesiredLength), mImage(nullptr) {
+    MOZ_ASSERT(mResult);
+  }
+
+  NS_IMETHOD
+  OnImageReady(imgIContainer* aImage, nsresult aStatus) override {
+    // Let's make sure we are alive until the request completes
+    MOZ_ALWAYS_TRUE(gDecodeRequests.putNew(this));
+
+    if (NS_FAILED(aStatus)) {
+      CompleteExceptionally("Could not process image.");
+      return aStatus;
+    }
+
+    mImage = aImage;
+    return mImage->StartDecoding(
+        imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY,
+        imgIContainer::FRAME_FIRST);
+  }
+
+  // This method assumes that the image is ready to be processed
+  nsresult SendBitmap() {
+    RefPtr<gfx::SourceSurface> surface;
+
+    if (mDesiredLength > 0) {
+      surface = mImage->GetFrameAtSize(
+          gfx::IntSize(mDesiredLength, mDesiredLength),
+          imgIContainer::FRAME_FIRST,
+          imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+    } else {
+      surface = mImage->GetFrame(
+          imgIContainer::FRAME_FIRST,
+          imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+    }
+
+    RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+
+    NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+    int32_t width = dataSurface->GetSize().width;
+    int32_t height = dataSurface->GetSize().height;
+
+    DataSourceSurface::ScopedMap sourceMap(dataSurface,
+                                           DataSourceSurface::READ);
+
+    // Android's Bitmap only supports R8G8B8A8, so we need to convert the
+    // data to the right format
+    RefPtr<DataSourceSurface> destDataSurface =
+        Factory::CreateDataSourceSurfaceWithStride(dataSurface->GetSize(),
+                                                   SurfaceFormat::R8G8B8A8,
+                                                   sourceMap.GetStride());
+    NS_ENSURE_TRUE(destDataSurface, NS_ERROR_FAILURE);
+
+    DataSourceSurface::ScopedMap destMap(destDataSurface,
+                                         DataSourceSurface::READ_WRITE);
+
+    SwizzleData(sourceMap.GetData(), sourceMap.GetStride(),
+                surface->GetFormat(), destMap.GetData(), destMap.GetStride(),
+                SurfaceFormat::R8G8B8A8, destDataSurface->GetSize());
+
+    Complete(destMap, width, height);
+
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  Notify(imgIRequest* aRequest, int32_t aType,
+         const nsIntRect* aData) override {
+    if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+      SendBitmap();
+    }
+
+    return NS_OK;
+  }
+
+ private:
+  const java::GeckoResult::GlobalRef mResult;
+  int32_t mDesiredLength;
+  nsCOMPtr<imgIContainer> mImage;
+  virtual ~ImageCallbackHelper() {}
+};
+
+NS_IMPL_ISUPPORTS(ImageCallbackHelper, imgIContainerCallback,
+                  imgINotificationObserver)
+
+}  // namespace
+
+/* static */ void ImageDecoderSupport::Decode(jni::String::Param aUri,
+                                              int32_t aDesiredLength,
+                                              jni::Object::Param aResult) {
+  auto result = java::GeckoResult::LocalRef(aResult);
+  RefPtr<ImageCallbackHelper> helper =
+      new ImageCallbackHelper(result, aDesiredLength);
+
+  nsresult rv = DecodeInternal(aUri->ToString(), helper, helper);
+  if (NS_FAILED(rv)) {
+    helper->OnImageReady(nullptr, rv);
+  }
+}
+
+/* static */ nsresult ImageDecoderSupport::DecodeInternal(
+    const nsAString& aUri, imgIContainerCallback* aCallback,
+    imgINotificationObserver* aObserver) {
+  nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1");
+  if (NS_WARN_IF(!imgTools)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+
+  nsCOMPtr<nsIChannel> channel;
+  rv = NS_NewChannel(getter_AddRefs(channel), uri,
+                     nsContentUtils::GetSystemPrincipal(),
+                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                     nsIContentPolicy::TYPE_IMAGE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return imgTools->DecodeImageFromChannelAsync(uri, channel, aCallback,
+                                               aObserver);
+}
+
+}  // namespace widget
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/android/ImageDecoderSupport.h
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ImageDecoderSupport_h__
+#define ImageDecoderSupport_h__
+
+#include "GeneratedJNINatives.h"
+
+class imgIContainerCallback;
+
+namespace mozilla {
+namespace widget {
+
+class ImageDecoderSupport final
+    : public java::ImageDecoder::Natives<ImageDecoderSupport> {
+ public:
+  static void Decode(jni::String::Param aUri, int32_t aDesiredLength,
+                     jni::Object::Param aResult);
+
+ private:
+  static nsresult DecodeInternal(const nsAString& aUri,
+                                 imgIContainerCallback* aCallback,
+                                 imgINotificationObserver* aObserver);
+};
+
+}  // namespace widget
+}  // namespace mozilla
+
+#endif  // ImageDecoderSupport_h__
--- a/widget/android/bindings/JavaExceptions-classes.txt
+++ b/widget/android/bindings/JavaExceptions-classes.txt
@@ -1,2 +1,5 @@
 [java.lang.IllegalStateException = skip:true]
-<init>(Ljava/lang/String;)V =
\ No newline at end of file
+<init>(Ljava/lang/String;)V =
+
+[java.lang.IllegalArgumentException = skip:true]
+<init>(Ljava/lang/String;)V =
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -46,16 +46,17 @@ UNIFIED_SOURCES += [
     'AndroidAlerts.cpp',
     'AndroidBridge.cpp',
     'AndroidCompositorWidget.cpp',
     'AndroidContentController.cpp',
     'AndroidUiThread.cpp',
     'EventDispatcher.cpp',
     'GeckoEditableSupport.cpp',
     'GfxInfo.cpp',
+    'ImageDecoderSupport.cpp',
     'nsAndroidProtocolHandler.cpp',
     'nsAppShell.cpp',
     'nsClipboard.cpp',
     'nsDeviceContextAndroid.cpp',
     'nsIdleServiceAndroid.cpp',
     'nsLookAndFeel.cpp',
     'nsNativeThemeAndroid.cpp',
     'nsPrintSettingsServiceAndroid.cpp',
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -61,16 +61,17 @@
 #include "AndroidUiThread.h"
 #include "GeckoBatteryManager.h"
 #include "GeckoNetworkManager.h"
 #include "GeckoProcessManager.h"
 #include "GeckoScreenOrientation.h"
 #include "GeckoSystemStateListener.h"
 #include "GeckoTelemetryDelegate.h"
 #include "GeckoVRManager.h"
+#include "ImageDecoderSupport.h"
 #include "PrefsHelper.h"
 #include "ScreenHelperAndroid.h"
 #include "Telemetry.h"
 #include "WebExecutorSupport.h"
 #include "Base64UtilsSupport.h"
 
 #ifdef DEBUG_ANDROID_EVENTS
 #  define EVLOG(args...) ALOG(args)
@@ -429,16 +430,17 @@ nsAppShell::nsAppShell()
     XPCOMEventTargetWrapper::Init();
     mozilla::GeckoBatteryManager::Init();
     mozilla::GeckoNetworkManager::Init();
     mozilla::GeckoProcessManager::Init();
     mozilla::GeckoScreenOrientation::Init();
     mozilla::GeckoSystemStateListener::Init();
     mozilla::PrefsHelper::Init();
     mozilla::widget::Telemetry::Init();
+    mozilla::widget::ImageDecoderSupport::Init();
     mozilla::widget::WebExecutorSupport::Init();
     mozilla::widget::Base64UtilsSupport::Init();
     nsWindow::InitNatives();
     mozilla::gl::AndroidSurfaceTexture::Init();
     mozilla::WebAuthnTokenManager::Init();
     mozilla::widget::GeckoTelemetryDelegate::Init();
 
     java::GeckoThread::SetState(java::GeckoThread::State::JNI_READY());