Bug 1530402 - Provide GeckoImageDecoder. r=snorp
☠☠ backed out by 61ebc1a22544 ☠ ☠
authorAgi Sferro <agi@sferro.dev>
Fri, 15 Nov 2019 16:33:45 +0000
changeset 502228 282b033c7daaab3b9bca0cd3ec6dc504e220ce5e
parent 502227 72274a5a821b2b8e3d9edaa9a0aaeba3e55de836
child 502229 e0ae4f16f7eda0839925ddd8402b926a70c9f898
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());