/* 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 "imgINotificationObserver.h" #include "imgITools.h" #include "imgINotificationObserver.h" #include "gfxUtils.h" #include "AndroidGraphics.h" #include "JavaExceptions.h" #include "mozilla/gfx/Point.h" #include "mozilla/gfx/Swizzle.h" #include "mozilla/java/ImageWrappers.h" #include "nsNetUtil.h" using namespace mozilla::gfx; namespace mozilla { namespace widget { namespace { class ImageCallbackHelper; HashSet, PointerHasher> gDecodeRequests; class ImageCallbackHelper : public imgIContainerCallback, public imgINotificationObserver { public: NS_DECL_ISUPPORTS void CompleteExceptionally(nsresult aRv) { nsPrintfCString error("Could not process image: 0x%08X", aRv); mResult->CompleteExceptionally( java::Image::ImageProcessingException::New(error.get()) .Cast()); gDecodeRequests.remove(this); } void Complete(DataSourceSurface::ScopedMap& aSourceSurface, int32_t width, int32_t height) { auto pixels = mozilla::jni::ByteBuffer::New( reinterpret_cast(aSourceSurface.GetData()), aSourceSurface.GetStride() * height); auto bitmap = java::sdk::Bitmap::CreateBitmap( width, height, java::sdk::Bitmap::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(aStatus); 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 surface; NS_ENSURE_TRUE(mImage, NS_ERROR_FAILURE); 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); } NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); RefPtr 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 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; } void Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData) override { if (aType == imgINotificationObserver::DECODE_COMPLETE) { nsresult status = SendBitmap(); if (NS_FAILED(status)) { CompleteExceptionally(status); } // Breack the cyclic reference between `ImageDecoderListener` (which is a // `imgIContainer`) and `ImageCallbackHelper`. mImage = nullptr; } } private: const java::GeckoResult::GlobalRef mResult; int32_t mDesiredLength; nsCOMPtr 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 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 imgTools = do_GetService("@mozilla.org/image/tools;1"); if (NS_WARN_IF(!imgTools)) { return NS_ERROR_FAILURE; } nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri); NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, nsIContentPolicy::TYPE_IMAGE); NS_ENSURE_SUCCESS(rv, rv); return imgTools->DecodeImageFromChannelAsync(uri, channel, aCallback, aObserver); } } // namespace widget } // namespace mozilla