Use WebpSanitizer.
|
@ -487,7 +487,6 @@ dependencies {
|
|||
implementation(project(":sms-exporter"))
|
||||
implementation(project(":sticky-header-grid"))
|
||||
implementation(project(":photoview"))
|
||||
implementation(project(":glide-webp"))
|
||||
implementation(project(":core-ui"))
|
||||
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.ResourceDecoder
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapResource
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.media.WebpSanitizer
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
class WebpInputStreamResourceDecoder(private val bitmapPool: BitmapPool) : ResourceDecoder<InputStream, Bitmap> {
|
||||
/**
|
||||
* Uses WebpSanitizer to check for invalid webp.
|
||||
*/
|
||||
class WebpSanDecoder : ResourceDecoder<InputStream, Bitmap> {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WebpResourceDecoder" // Name > 23 characters
|
||||
private val TAG = Log.tag(WebpSanDecoder::class.java)
|
||||
|
||||
private val MAGIC_NUMBER_P1 = byteArrayOf(0x52, 0x49, 0x46, 0x46) // "RIFF"
|
||||
private val MAGIC_NUMBER_P2 = byteArrayOf(0x57, 0x45, 0x42, 0x50) // "WEBP"
|
||||
|
@ -34,10 +36,12 @@ class WebpInputStreamResourceDecoder(private val bitmapPool: BitmapPool) : Resou
|
|||
* [4-7]: File length
|
||||
* [8-11]: "WEBP"
|
||||
*
|
||||
* We're not verifying the file length here, so we just need to check the first and last
|
||||
* We're not verifying the file length here, so we just need to check the first and last.
|
||||
*
|
||||
* We then sanitize the webp and block the load if the check fails.
|
||||
*/
|
||||
override fun handles(source: InputStream, options: Options): Boolean {
|
||||
return try {
|
||||
try {
|
||||
val magicNumberP1 = ByteArray(4)
|
||||
StreamUtil.readFully(source, magicNumberP1)
|
||||
|
||||
|
@ -47,24 +51,27 @@ class WebpInputStreamResourceDecoder(private val bitmapPool: BitmapPool) : Resou
|
|||
val magicNumberP2 = ByteArray(4)
|
||||
StreamUtil.readFully(source, magicNumberP2)
|
||||
|
||||
magicNumberP1.contentEquals(MAGIC_NUMBER_P1) && magicNumberP2.contentEquals(MAGIC_NUMBER_P2)
|
||||
if (magicNumberP1.contentEquals(MAGIC_NUMBER_P1) && magicNumberP2.contentEquals(MAGIC_NUMBER_P2)) {
|
||||
try {
|
||||
source.reset()
|
||||
source.mark(MAX_WEBP_COMPRESSED_SIZE)
|
||||
WebpSanitizer.sanitize(source, Long.MAX_VALUE)
|
||||
source.reset()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Sanitize check failed or mark position invalidated by reset", e)
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Failed to read magic number from stream!", e)
|
||||
false
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<Bitmap>? {
|
||||
Log.d(TAG, "decode()")
|
||||
|
||||
val webp: ByteArray = try {
|
||||
StreamUtil.readFully(source, MAX_WEBP_COMPRESSED_SIZE)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Unexpected IOException hit while reading image data", e)
|
||||
throw e
|
||||
}
|
||||
|
||||
val bitmap: Bitmap? = WebpDecoder().nativeDecodeBitmapScaled(webp, width, height)
|
||||
return BitmapResource.obtain(bitmap, bitmapPool)
|
||||
Log.w(TAG, "Image did not pass sanitizer")
|
||||
throw IOException("Unable to load image")
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.glide.cache.EncryptedBitmapResourceEncoder;
|
|||
import org.thoughtcrime.securesms.glide.cache.EncryptedCacheDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedCacheEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedGifDrawableResourceEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.WebpSanDecoder;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
|
||||
|
@ -64,6 +65,8 @@ public class SignalGlideComponents implements RegisterGlideComponents {
|
|||
|
||||
registry.prepend(File.class, File.class, UnitModelLoader.Factory.getInstance());
|
||||
|
||||
registry.prepend(InputStream.class, Bitmap.class, new WebpSanDecoder());
|
||||
|
||||
registry.prepend(InputStream.class, new EncryptedCacheEncoder(secret, glide.getArrayPool()));
|
||||
|
||||
registry.prepend(File.class, Bitmap.class, new EncryptedCacheDecoder<>(secret, new StreamBitmapDecoder(new Downsampler(registry.getImageHeaderParsers(), context.getResources().getDisplayMetrics(), glide.getBitmapPool(), glide.getArrayPool()), glide.getArrayPool())));
|
||||
|
|
|
@ -100,8 +100,6 @@ task("qa") {
|
|||
|
||||
tasks.register("clean", Delete::class) {
|
||||
delete(rootProject.buildDir)
|
||||
// Because gradle is weird, we delete here for glide-webp/lib project so the clean tasks there doesn"t barf
|
||||
delete(fileTree("glide-webp/lib/.cxx"))
|
||||
}
|
||||
|
||||
task("format") {
|
||||
|
|
|
@ -70,7 +70,7 @@ fun InputStream.readNBytesOrThrow(length: Int): ByteArray {
|
|||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun InputStream.readLength(): Long? {
|
||||
fun InputStream.readLength(): Long {
|
||||
val buffer = ByteArray(4096)
|
||||
var count = 0L
|
||||
|
||||
|
|
|
@ -10,6 +10,4 @@ android {
|
|||
dependencies {
|
||||
implementation(libs.glide.glide)
|
||||
kapt(libs.glide.compiler)
|
||||
|
||||
implementation(project(":glide-webp"))
|
||||
}
|
||||
|
|
1
glide-webp/app/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("signal-sample-app")
|
||||
kotlin("kapt")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.signal.glide.webp.app"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":glide-webp"))
|
||||
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
|
||||
implementation(libs.glide.glide)
|
||||
kapt(libs.glide.compiler)
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Signal">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Signal">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 173 KiB |
Before Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 1.4 MiB |
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp.app
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.GlideBuilder
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import org.signal.core.util.dp
|
||||
|
||||
/**
|
||||
* Main activity for this sample app.
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.main_activitiy)
|
||||
|
||||
Glide.init(
|
||||
this,
|
||||
GlideBuilder()
|
||||
.setLogLevel(Log.VERBOSE)
|
||||
)
|
||||
|
||||
val context = this
|
||||
|
||||
findViewById<RecyclerView>(R.id.list).apply {
|
||||
adapter = ImageAdapter()
|
||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
}
|
||||
}
|
||||
|
||||
class ImageAdapter : RecyclerView.Adapter<ImageViewHolder>() {
|
||||
|
||||
private val data: List<String> = listOf(
|
||||
"test_01.webp",
|
||||
"test_02.webp",
|
||||
"test_03.webp",
|
||||
"test_04.webp",
|
||||
"test_05.webp",
|
||||
"test_06_lossless.webp",
|
||||
"test_06_lossy.webp",
|
||||
"test_07_lossless.webp",
|
||||
"test_09_large.webp"
|
||||
)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
|
||||
return ImageViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.image_item, parent, false))
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return data.size
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
||||
holder.bind(data[position])
|
||||
}
|
||||
}
|
||||
|
||||
class ImageViewHolder(itemView: View) : ViewHolder(itemView) {
|
||||
val image: ImageView by lazy { itemView.findViewById(R.id.image) }
|
||||
|
||||
fun bind(filename: String) {
|
||||
Glide.with(itemView)
|
||||
.load(Uri.parse("file:///android_asset/$filename"))
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.override(250.dp)
|
||||
.into(image)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp.app
|
||||
|
||||
import android.content.Context
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
@GlideModule
|
||||
class SampleAppGlideModule : AppGlideModule() {
|
||||
companion object {
|
||||
private val TAG = Log.tag(SampleAppGlideModule::class.java)
|
||||
}
|
||||
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
Log.e(TAG, "AppModule - registerComponents")
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp.app.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp.app.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SignalTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = colorScheme.primary.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp.app.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
|
@ -1,35 +0,0 @@
|
|||
<!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
|
@ -1,175 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp">
|
||||
</ImageView>
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 7.6 KiB |
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
|
@ -1,8 +0,0 @@
|
|||
<!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="app_name">Glide WebP</string>
|
||||
</resources>
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="Theme.Signal" parent="Theme.AppCompat.NoActionBar" />
|
||||
</resources>
|
2
glide-webp/lib/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
/build
|
||||
.cxx
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("signal-library")
|
||||
kotlin("kapt")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.signal.glide.webp"
|
||||
|
||||
defaultConfig {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags("-std=c++17", "-fvisibility=hidden")
|
||||
arguments("-DLIBWEBP_PATH=$rootDir/libwebp")
|
||||
}
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("$projectDir/src/main/cpp/CMakeLists.txt")
|
||||
version = "3.22.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core-util"))
|
||||
|
||||
implementation(libs.glide.glide)
|
||||
kapt(libs.glide.compiler)
|
||||
|
||||
androidTestImplementation(testLibs.androidx.test.core)
|
||||
androidTestImplementation(testLibs.androidx.test.core.ktx)
|
||||
androidTestImplementation(testLibs.androidx.test.ext.junit)
|
||||
androidTestImplementation(testLibs.androidx.test.ext.junit.ktx)
|
||||
androidTestImplementation(testLibs.androidx.test.monitor)
|
||||
androidTestImplementation(testLibs.androidx.test.runner)
|
||||
}
|
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 174 KiB |
|
@ -1,102 +0,0 @@
|
|||
package org.signal.glide.webp
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Debug
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.util.StreamUtil
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class WebpDecoderTest {
|
||||
private fun readAsset(name: String): ByteArray {
|
||||
val source = InstrumentationRegistry.getInstrumentation().context.assets.open(name)
|
||||
return StreamUtil.readFully(source)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun landscapeUnscaled() {
|
||||
val input = readAsset("landscape_550x368.webp")
|
||||
|
||||
val outputUnconstrained = WebpDecoder().nativeDecodeBitmapScaled(input, 1000, 1000)!!
|
||||
assertEquals(550, outputUnconstrained.width)
|
||||
assertEquals(368, outputUnconstrained.height)
|
||||
|
||||
val outputSquareConstraints = WebpDecoder().nativeDecodeBitmapScaled(input, 550, 550)!!
|
||||
assertEquals(550, outputSquareConstraints.width)
|
||||
assertEquals(368, outputSquareConstraints.height)
|
||||
|
||||
val outputExactConstraints = WebpDecoder().nativeDecodeBitmapScaled(input, 550, 368)!!
|
||||
assertEquals(550, outputExactConstraints.width)
|
||||
assertEquals(368, outputExactConstraints.height)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun landscapeScaledBasedOnWidth() {
|
||||
val input = readAsset("landscape_550x368.webp")
|
||||
|
||||
val outputUnconstrainedHeight = WebpDecoder().nativeDecodeBitmapScaled(input, 275, 1000)!!
|
||||
assertEquals(275, outputUnconstrainedHeight.width)
|
||||
assertEquals(184, outputUnconstrainedHeight.height)
|
||||
|
||||
val outputSquareConstraints = WebpDecoder().nativeDecodeBitmapScaled(input, 275, 275)!!
|
||||
assertEquals(275, outputSquareConstraints.width)
|
||||
assertEquals(184, outputSquareConstraints.height)
|
||||
|
||||
val outputSmallHeight = WebpDecoder().nativeDecodeBitmapScaled(input, 275, 200)!!
|
||||
assertEquals(275, outputSmallHeight.width)
|
||||
assertEquals(184, outputSmallHeight.height)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun landscapeScaledBasedOnHeight() {
|
||||
val input = readAsset("landscape_550x368.webp")
|
||||
|
||||
val outputUnconstrainedWidth = WebpDecoder().nativeDecodeBitmapScaled(input, 1000, 184)!!
|
||||
assertEquals(275, outputUnconstrainedWidth.width)
|
||||
assertEquals(184, outputUnconstrainedWidth.height)
|
||||
|
||||
val outputSmallWidth = WebpDecoder().nativeDecodeBitmapScaled(input, 300, 184)!!
|
||||
assertEquals(275, outputSmallWidth.width)
|
||||
assertEquals(184, outputSmallWidth.height)
|
||||
}
|
||||
|
||||
private fun getNativeHeapSize(): Long {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Debug.getNativeHeapSize()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canScaleMassiveImagesWithoutAllocatingLargeBuffers() {
|
||||
val nativeHeapSizeAtStart = getNativeHeapSize()
|
||||
|
||||
val input = readAsset("solid_white_10Kx10K.webp")
|
||||
|
||||
val output = WebpDecoder().nativeDecodeBitmapScaled(input, 1000, 1000)!!
|
||||
assertEquals(1000, output.width)
|
||||
assertEquals(1000, output.height)
|
||||
|
||||
var nativeHeapSizeAtEnd = getNativeHeapSize()
|
||||
assertTrue("heap size: " + nativeHeapSizeAtStart + " -> " + nativeHeapSizeAtEnd, nativeHeapSizeAtStart + 10_000 * 10_000 > nativeHeapSizeAtEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canHandleMassiveBrokenImagesWithoutAllocatingLargeBuffers() {
|
||||
val nativeHeapSizeAtStart = getNativeHeapSize()
|
||||
|
||||
val input = readAsset("broken_10Kx10K.webp")
|
||||
|
||||
val output = WebpDecoder().nativeDecodeBitmapScaled(input, 1000, 1000)
|
||||
assertNull(output)
|
||||
|
||||
var nativeHeapSizeAtEnd = getNativeHeapSize()
|
||||
assertTrue("heap size: " + nativeHeapSizeAtStart + " -> " + nativeHeapSizeAtEnd, nativeHeapSizeAtStart + 10_000 * 10_000 > nativeHeapSizeAtEnd)
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.22.1)
|
||||
|
||||
project(signal-webp)
|
||||
|
||||
add_compile_options(-Oz)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
set(WEBP_BUILD_ANIM_UTILS OFF)
|
||||
set(WEBP_BUILD_CWEBP OFF)
|
||||
set(WEBP_BUILD_DWEBP OFF)
|
||||
set(WEBP_BUILD_VWEBP OFF)
|
||||
set(WEBP_BUILD_WEBPINFO OFF)
|
||||
set(WEBP_BUILD_LIBWEBPMUX OFF)
|
||||
set(WEBP_BUILD_EXTRAS OFF)
|
||||
set(WEBP_NEAR_LOSSLESS, OFF)
|
||||
|
||||
file(GLOB SOURCES ${CMAKE_SOURCE_DIR}/*.cpp)
|
||||
|
||||
add_subdirectory(${LIBWEBP_PATH} ${CMAKE_CURRENT_BINARY_DIR}/libwebp)
|
||||
|
||||
add_library(signalwebp SHARED ${SOURCES})
|
||||
target_include_directories(signalwebp PRIVATE ${LIBWEBP_PATH}/src)
|
||||
find_library(androidlog log)
|
||||
target_link_libraries(signalwebp webpdemux ${androidlog})
|
|
@ -1,103 +0,0 @@
|
|||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
#include <webp/demux.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#define TAG "WebpResourceDecoder"
|
||||
|
||||
jobject createBitmap(JNIEnv *env, int width, int height, const uint8_t *pixels) {
|
||||
static auto jbitmapConfigClass = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("android/graphics/Bitmap$Config")));
|
||||
static jfieldID jbitmapConfigARGB8888Field = env->GetStaticFieldID(jbitmapConfigClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
||||
static auto jbitmapClass = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("android/graphics/Bitmap")));
|
||||
static jmethodID jbitmapCreateBitmapMethod = env->GetStaticMethodID(jbitmapClass, "createBitmap", "([IIIIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
|
||||
jintArray intArray = env->NewIntArray(width * height);
|
||||
env->SetIntArrayRegion(intArray, 0, width * height, reinterpret_cast<const jint *>(pixels));
|
||||
|
||||
jobject argb8888Config = env->GetStaticObjectField(jbitmapConfigClass, jbitmapConfigARGB8888Field);
|
||||
jobject jbitmap = env->CallStaticObjectMethod(jbitmapClass, jbitmapCreateBitmapMethod, intArray, 0, width, width, height, argb8888Config);
|
||||
|
||||
return jbitmap;
|
||||
}
|
||||
|
||||
jobject nativeDecodeBitmapScaled(JNIEnv *env, jobject, jbyteArray data, jint requestedWidth, jint requestedHeight) {
|
||||
jbyte *javaBytes = env->GetByteArrayElements(data, nullptr);
|
||||
auto *buffer = reinterpret_cast<uint8_t *>(javaBytes);
|
||||
jsize bufferLength = env->GetArrayLength(data);
|
||||
|
||||
WebPBitstreamFeatures features;
|
||||
if (WebPGetFeatures(buffer, bufferLength, &features) != VP8_STATUS_OK) {
|
||||
__android_log_write(ANDROID_LOG_WARN, TAG, "GetFeatures");
|
||||
env->ReleaseByteArrayElements(data, javaBytes, JNI_ABORT);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WebPDecoderConfig config;
|
||||
if (!WebPInitDecoderConfig(&config)) {
|
||||
__android_log_write(ANDROID_LOG_WARN, TAG, "Init decoder config");
|
||||
env->ReleaseByteArrayElements(data, javaBytes, JNI_ABORT);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
config.options.no_fancy_upsampling = 1;
|
||||
config.output.colorspace = MODE_BGRA;
|
||||
|
||||
if (requestedWidth > 0 && requestedHeight > 0 && features.width > 0 && features.height > 0 && (requestedWidth < features.width || requestedHeight < features.height)) {
|
||||
double widthScale = static_cast<double>(requestedWidth) / features.width;
|
||||
double heightScale = static_cast<double>(requestedHeight) / features.height;
|
||||
double requiredScale = std::min(widthScale, heightScale);
|
||||
|
||||
config.options.use_scaling = 1;
|
||||
config.options.scaled_width = static_cast<int>(requiredScale * features.width);
|
||||
config.options.scaled_height = static_cast<int>(requiredScale * features.height);
|
||||
}
|
||||
|
||||
uint8_t *pixels = nullptr;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
VP8StatusCode result = WebPDecode(buffer, bufferLength, &config);
|
||||
if (result != VP8_STATUS_OK) {
|
||||
__android_log_write(ANDROID_LOG_WARN, TAG, ("Scaled WebPDecode failed (" + std::to_string(result) + ")").c_str());
|
||||
} else {
|
||||
pixels = config.output.u.RGBA.rgba;
|
||||
width = config.output.width;
|
||||
height = config.output.height;
|
||||
}
|
||||
|
||||
jobject jbitmap = nullptr;
|
||||
if (pixels != nullptr) {
|
||||
jbitmap = createBitmap(env, width, height, pixels);
|
||||
}
|
||||
|
||||
WebPFree(pixels);
|
||||
env->ReleaseByteArrayElements(data, javaBytes, JNI_ABORT);
|
||||
|
||||
return jbitmap;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
|
||||
JNIEnv *env;
|
||||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
jclass c = env->FindClass("org/signal/glide/webp/WebpDecoder");
|
||||
if (c == nullptr) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
static const JNINativeMethod methods[] = {
|
||||
{"nativeDecodeBitmapScaled", "([BII)Landroid/graphics/Bitmap;", reinterpret_cast<void *>(nativeDecodeBitmapScaled)}
|
||||
};
|
||||
|
||||
int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
|
||||
|
||||
if (rc != JNI_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
class WebpDecoder {
|
||||
|
||||
init {
|
||||
System.loadLibrary("signalwebp")
|
||||
}
|
||||
|
||||
external fun nativeDecodeBitmapScaled(data: ByteArray, requestedWidth: Int, requestedHeight: Int): Bitmap?
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.module.LibraryGlideModule
|
||||
import org.signal.core.util.logging.Log
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Registers a custom handler for webp images.
|
||||
*/
|
||||
@GlideModule
|
||||
class WebpLibraryGlideModule : LibraryGlideModule() {
|
||||
companion object {
|
||||
private val TAG = Log.tag(WebpLibraryGlideModule::class.java)
|
||||
}
|
||||
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
Log.d(TAG, "registerComponents()")
|
||||
registry.prepend(InputStream::class.java, Bitmap::class.java, WebpInputStreamResourceDecoder(glide.bitmapPool))
|
||||
}
|
||||
}
|
|
@ -58,8 +58,6 @@ include(":benchmark")
|
|||
include(":microbenchmark")
|
||||
include(":video")
|
||||
include(":video-app")
|
||||
include(":glide-webp")
|
||||
include(":glide-webp-app")
|
||||
|
||||
project(":app").name = "Signal-Android"
|
||||
project(":paging").projectDir = file("paging/lib")
|
||||
|
@ -89,9 +87,6 @@ project(":qr-app").projectDir = file("qr/app")
|
|||
project(":video").projectDir = file("video/lib")
|
||||
project(":video-app").projectDir = file("video/app")
|
||||
|
||||
project(":glide-webp").projectDir = file("glide-webp/lib")
|
||||
project(":glide-webp-app").projectDir = file("glide-webp/app")
|
||||
|
||||
rootProject.name = "Signal"
|
||||
|
||||
apply(from = "dependencies.gradle.kts")
|
||||
|
|