Bug 1530402 - Provide GeckoImageDecoder. r=snorp
☠☠ backed out by 17db3abeba1a ☠ ☠
authorAgi Sferro <agi@sferro.dev>
Thu, 14 Nov 2019 19:08:40 +0000
changeset 502026 ab7b21969769b3296eb6de6e67d988d6792cf71a
parent 502025 c7b8cc91f2454e9e5fec2355274031afd87a5c80
child 502027 701de7bcb4840b7bdd70dbf3ecb44ffc46e0d2d8
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());