зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1650692 - ImageDecoder improvements, r=agi,geckoview-reviewers,snorp
Differential Revision: https://phabricator.services.mozilla.com/D82308
This commit is contained in:
Родитель
c9da5c6675
Коммит
f543954b4b
Двоичные данные
mobile/android/geckoview/src/androidTest/assets/web_extensions/actions/button/red.png
Normal file
Двоичные данные
mobile/android/geckoview/src/androidTest/assets/web_extensions/actions/button/red.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.7 KiB |
|
@ -0,0 +1,123 @@
|
||||||
|
package org.mozilla.geckoview.test
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Color
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.filters.MediumTest
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.geckoview.GeckoResult
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ImageDecoderTest {
|
||||||
|
|
||||||
|
private fun compareBitmap(expectedLocation: String, actual: Bitmap) {
|
||||||
|
val stream = InstrumentationRegistry.getInstrumentation().targetContext.assets
|
||||||
|
.open(expectedLocation)
|
||||||
|
|
||||||
|
val expected = BitmapFactory.decodeStream(stream)
|
||||||
|
for (x in 0 until actual.height) {
|
||||||
|
for (y in 0 until actual.width) {
|
||||||
|
assertEquals(expected.getPixel(x, y), actual.getPixel(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun decodeNullUri() {
|
||||||
|
ImageDecoder.instance().decode(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decodeIconSvg() {
|
||||||
|
val svgNatural = GeckoResult<Void>()
|
||||||
|
val svg100 = GeckoResult<Void>()
|
||||||
|
|
||||||
|
ImageDecoder.instance().decode("web_extensions/actions/button/icon.svg").accept { actual ->
|
||||||
|
assertEquals(500, actual!!.width)
|
||||||
|
assertEquals(500, actual.height)
|
||||||
|
svgNatural.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDecoder.instance().decode("web_extensions/actions/button/icon.svg", 100).accept { actual ->
|
||||||
|
assertEquals(100, actual!!.width)
|
||||||
|
assertEquals(100, actual.height)
|
||||||
|
compareBitmap("web_extensions/actions/button/expected.png", actual)
|
||||||
|
svg100.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
UiThreadUtils.waitForResult(svgNatural)
|
||||||
|
UiThreadUtils.waitForResult(svg100)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decodeIconPng() {
|
||||||
|
val pngNatural = GeckoResult<Void>()
|
||||||
|
val png100 = GeckoResult<Void>()
|
||||||
|
val png38 = GeckoResult<Void>()
|
||||||
|
val png19 = GeckoResult<Void>()
|
||||||
|
|
||||||
|
ImageDecoder.instance().decode("web_extensions/actions/button/geo-19.png").accept { actual ->
|
||||||
|
compareBitmap("web_extensions/actions/button/geo-19.png", actual!!)
|
||||||
|
pngNatural.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raster images shouldn't be upscaled
|
||||||
|
ImageDecoder.instance().decode("web_extensions/actions/button/geo-38.png", 100).accept { actual ->
|
||||||
|
compareBitmap("web_extensions/actions/button/geo-38.png", actual!!)
|
||||||
|
png100.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDecoder.instance().decode("web_extensions/actions/button/geo-38.png", 38).accept { actual ->
|
||||||
|
compareBitmap("web_extensions/actions/button/geo-38.png", actual!!)
|
||||||
|
png38.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDecoder.instance().decode("web_extensions/actions/button/geo-19.png", 19).accept { actual ->
|
||||||
|
compareBitmap("web_extensions/actions/button/geo-19.png", actual!!)
|
||||||
|
png19.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
UiThreadUtils.waitForResult(pngNatural)
|
||||||
|
UiThreadUtils.waitForResult(png100)
|
||||||
|
UiThreadUtils.waitForResult(png38)
|
||||||
|
UiThreadUtils.waitForResult(png19)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decodeIconPngDownscale() {
|
||||||
|
val pngNatural = GeckoResult<Void>()
|
||||||
|
val png16 = GeckoResult<Void>()
|
||||||
|
|
||||||
|
ImageDecoder.instance().decode("web_extensions/actions/button/red.png").accept { actual ->
|
||||||
|
assertEquals(32, actual!!.width)
|
||||||
|
assertEquals(32, actual.height)
|
||||||
|
|
||||||
|
for (x in 0 until actual.height) {
|
||||||
|
for (y in 0 until actual.width) {
|
||||||
|
assertEquals(Color.RED, actual.getPixel(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pngNatural.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDecoder.instance().decode("web_extensions/actions/button/red.png", 16).accept { actual ->
|
||||||
|
assertEquals(16, actual!!.width)
|
||||||
|
assertEquals(16, actual.height)
|
||||||
|
|
||||||
|
for (x in 0 until actual.height) {
|
||||||
|
for (y in 0 until actual.width) {
|
||||||
|
assertEquals(Color.RED, actual.getPixel(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
png16.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
UiThreadUtils.waitForResult(pngNatural)
|
||||||
|
UiThreadUtils.waitForResult(png16)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,11 @@ import org.mozilla.gecko.annotation.WrapForJNI;
|
||||||
|
|
||||||
private ImageDecoder() {}
|
private ImageDecoder() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the singleton instance of this class.
|
||||||
|
*
|
||||||
|
* @return the singleton {@link ImageDecoder} instance
|
||||||
|
*/
|
||||||
public static ImageDecoder instance() {
|
public static ImageDecoder instance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = new ImageDecoder();
|
instance = new ImageDecoder();
|
||||||
|
@ -25,7 +30,7 @@ import org.mozilla.gecko.annotation.WrapForJNI;
|
||||||
}
|
}
|
||||||
|
|
||||||
@WrapForJNI(dispatchTo = "gecko", stubName = "Decode")
|
@WrapForJNI(dispatchTo = "gecko", stubName = "Decode")
|
||||||
private static native void nativeDecode(final String uri, final int desiredLength,
|
private static native void nativeDecode(final String uri, final int maxSize,
|
||||||
GeckoResult<Bitmap> result);
|
GeckoResult<Bitmap> result);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,14 +60,13 @@ import org.mozilla.gecko.annotation.WrapForJNI;
|
||||||
*
|
*
|
||||||
* e.g. if the image file is locate at /assets/test.png inside the apk, set the uri
|
* e.g. if the image file is locate at /assets/test.png inside the apk, set the uri
|
||||||
* to resource://android/assets/test.png.
|
* to resource://android/assets/test.png.
|
||||||
* @param desiredLength Longest size for the image in device pixel units. The resulting image
|
* @param maxSize Longest size for the image in device pixel units. The resulting image
|
||||||
* might be slightly different if the image cannot be resized efficiently.
|
* might be slightly different if the image cannot be resized efficiently.
|
||||||
* If desiredLength is 0 then the image will be decoded to its natural
|
* If maxSize is 0 then the image will be decoded to its natural size.
|
||||||
* size.
|
|
||||||
* @return A {@link GeckoResult} to the decoded image.
|
* @return A {@link GeckoResult} to the decoded image.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public GeckoResult<Bitmap> decode(final @NonNull String uri, final int desiredLength) {
|
public GeckoResult<Bitmap> decode(final @NonNull String uri, final int maxSize) {
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
throw new IllegalArgumentException("Uri cannot be null");
|
throw new IllegalArgumentException("Uri cannot be null");
|
||||||
}
|
}
|
||||||
|
@ -70,10 +74,10 @@ import org.mozilla.gecko.annotation.WrapForJNI;
|
||||||
final GeckoResult<Bitmap> result = new GeckoResult<>();
|
final GeckoResult<Bitmap> result = new GeckoResult<>();
|
||||||
|
|
||||||
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
|
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
|
||||||
nativeDecode(uri, desiredLength, result);
|
nativeDecode(uri, maxSize, result);
|
||||||
} else {
|
} else {
|
||||||
GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, this,
|
GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, this,
|
||||||
"nativeDecode", String.class, uri, int.class, desiredLength,
|
"nativeDecode", String.class, uri, int.class, maxSize,
|
||||||
GeckoResult.class, result);
|
GeckoResult.class, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,4 +68,4 @@ gradle.ext.mozconfig = json
|
||||||
def orig = slurper.parse(new File(json.topobjdir, '.mozconfig.json'))
|
def orig = slurper.parse(new File(json.topobjdir, '.mozconfig.json'))
|
||||||
gradle.ext.mozconfig.orig_mozconfig = orig.mozconfig
|
gradle.ext.mozconfig.orig_mozconfig = orig.mozconfig
|
||||||
project(':messaging_example').projectDir = new File('mobile/android/examples/messaging_example/app')
|
project(':messaging_example').projectDir = new File('mobile/android/examples/messaging_example/app')
|
||||||
project(':port_messaging_example').projectDir = new File('mobile/android/examples/port_messaging_example/app')
|
project(':port_messaging_example').projectDir = new File('mobile/android/examples/port_messaging_example/app')
|
||||||
|
|
|
@ -47,8 +47,8 @@ class ImageCallbackHelper : public imgIContainerCallback,
|
||||||
gDecodeRequests.remove(this);
|
gDecodeRequests.remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageCallbackHelper(java::GeckoResult::Param aResult, int32_t aDesiredLength)
|
ImageCallbackHelper(java::GeckoResult::Param aResult, int32_t aMaxSize)
|
||||||
: mResult(aResult), mDesiredLength(aDesiredLength), mImage(nullptr) {
|
: mResult(aResult), mMaxSize(aMaxSize), mImage(nullptr) {
|
||||||
MOZ_ASSERT(mResult);
|
MOZ_ASSERT(mResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,20 +63,21 @@ class ImageCallbackHelper : public imgIContainerCallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
mImage = aImage;
|
mImage = aImage;
|
||||||
return mImage->StartDecoding(
|
return mImage->StartDecoding(imgIContainer::FLAG_SYNC_DECODE |
|
||||||
imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY,
|
imgIContainer::FLAG_ASYNC_NOTIFY |
|
||||||
imgIContainer::FRAME_FIRST);
|
imgIContainer::FLAG_HIGH_QUALITY_SCALING,
|
||||||
|
imgIContainer::FRAME_FIRST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method assumes that the image is ready to be processed
|
// This method assumes that the image is ready to be processed
|
||||||
nsresult SendBitmap() {
|
nsresult SendBitmap() {
|
||||||
RefPtr<gfx::SourceSurface> surface;
|
RefPtr<gfx::SourceSurface> surface;
|
||||||
|
|
||||||
if (mDesiredLength > 0) {
|
if (mMaxSize > 0) {
|
||||||
surface = mImage->GetFrameAtSize(
|
surface = mImage->GetFrameAtSize(
|
||||||
gfx::IntSize(mDesiredLength, mDesiredLength),
|
gfx::IntSize(mMaxSize, mMaxSize), imgIContainer::FRAME_FIRST,
|
||||||
imgIContainer::FRAME_FIRST,
|
imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY |
|
||||||
imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
|
imgIContainer::FLAG_HIGH_QUALITY_SCALING);
|
||||||
} else {
|
} else {
|
||||||
surface = mImage->GetFrame(
|
surface = mImage->GetFrame(
|
||||||
imgIContainer::FRAME_FIRST,
|
imgIContainer::FRAME_FIRST,
|
||||||
|
@ -125,7 +126,7 @@ class ImageCallbackHelper : public imgIContainerCallback,
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const java::GeckoResult::GlobalRef mResult;
|
const java::GeckoResult::GlobalRef mResult;
|
||||||
int32_t mDesiredLength;
|
int32_t mMaxSize;
|
||||||
nsCOMPtr<imgIContainer> mImage;
|
nsCOMPtr<imgIContainer> mImage;
|
||||||
virtual ~ImageCallbackHelper() {}
|
virtual ~ImageCallbackHelper() {}
|
||||||
};
|
};
|
||||||
|
@ -136,11 +137,11 @@ NS_IMPL_ISUPPORTS(ImageCallbackHelper, imgIContainerCallback,
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
/* static */ void ImageDecoderSupport::Decode(jni::String::Param aUri,
|
/* static */ void ImageDecoderSupport::Decode(jni::String::Param aUri,
|
||||||
int32_t aDesiredLength,
|
int32_t aMaxSize,
|
||||||
jni::Object::Param aResult) {
|
jni::Object::Param aResult) {
|
||||||
auto result = java::GeckoResult::LocalRef(aResult);
|
auto result = java::GeckoResult::LocalRef(aResult);
|
||||||
RefPtr<ImageCallbackHelper> helper =
|
RefPtr<ImageCallbackHelper> helper =
|
||||||
new ImageCallbackHelper(result, aDesiredLength);
|
new ImageCallbackHelper(result, aMaxSize);
|
||||||
|
|
||||||
nsresult rv = DecodeInternal(aUri->ToString(), helper, helper);
|
nsresult rv = DecodeInternal(aUri->ToString(), helper, helper);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace widget {
|
||||||
class ImageDecoderSupport final
|
class ImageDecoderSupport final
|
||||||
: public java::ImageDecoder::Natives<ImageDecoderSupport> {
|
: public java::ImageDecoder::Natives<ImageDecoderSupport> {
|
||||||
public:
|
public:
|
||||||
static void Decode(jni::String::Param aUri, int32_t aDesiredLength,
|
static void Decode(jni::String::Param aUri, int32_t aMaxSize,
|
||||||
jni::Object::Param aResult);
|
jni::Object::Param aResult);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Загрузка…
Ссылка в новой задаче