Enable WebP decoding in Signal using libwebp v1.3.2

Co-authored-by: Greyson Parrelli <greyson@signal.org>
Co-authored-by: Greyson Parrelli <greyson@pop-os.localdomain>
This commit is contained in:
Cody Henthorne 2023-09-22 12:55:49 -04:00 committed by Alex Hart
parent 091f7c49ab
commit a7d9fd19d9
59 changed files with 874 additions and 7 deletions

View file

@ -18,6 +18,8 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: set up JDK 17
uses: actions/setup-java@v3

View file

@ -15,6 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: true
ref: ${{ github.event.pull_request.base.sha }}
- name: set up JDK 17
@ -45,6 +46,7 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: true
clean: 'false'
- name: Build with Gradle

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "libwebp"]
path = libwebp
url = git@github.com:webmproject/libwebp.git

View file

@ -15,6 +15,12 @@ Truths which we believe to be self-evident:
1. **There is no such thing as time.** Protocol ideas that require synchronized clocks are doomed to failure.
## Building
1. You'll need to get the `libwebp` submodule after checking out the repository with `git submodule init && git submodule update`
1. Most things are pretty straightforward, and opening the project in Android Studio should get you most of the way there.
1. Depending on your configuration, you'll also likely need to install additional SDK Tool components, namely the versions of NDK and CMake we are currently using in our [Docker](https://github.com/signalapp/Signal-Android/blob/main/reproducible-builds/Dockerfile#L30) configuration.
## Issues
### Useful bug reports

View file

@ -520,6 +520,7 @@ dependencies {
implementation project(':sms-exporter')
implementation project(':sticky-header-grid')
implementation project(':photoview')
implementation project(':glide-webp')
implementation libs.libsignal.android

View file

@ -60,6 +60,4 @@ public class EncryptedBitmapResourceEncoder extends EncryptedCoder implements Re
return Bitmap.CompressFormat.JPEG;
}
}
}

View file

@ -45,6 +45,14 @@ android {
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.4"
}
}
dependencies {

View file

@ -98,6 +98,8 @@ task qa {
task clean(type: Delete) {
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 {

View file

@ -60,12 +60,21 @@ public final class StreamUtil {
}
public static byte[] readFully(InputStream in) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int read;
return readFully(in, Integer.MAX_VALUE);
}
public static byte[] readFully(InputStream in, int maxBytes) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int totalRead = 0;
int read;
while ((read = in.read(buffer)) != -1) {
bout.write(buffer, 0, read);
totalRead += read;
if (totalRead > maxBytes) {
throw new IOException("Stream size limit exceeded");
}
}
in.close();

View file

@ -10,4 +10,6 @@ android {
dependencies {
implementation libs.glide.glide
kapt libs.glide.compiler
implementation project(':glide-webp')
}

1
glide-webp/app/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,25 @@
/*
* 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.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)
implementation(libs.glide.glide)
kapt(libs.glide.compiler)
}

View file

@ -0,0 +1,29 @@
<?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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View file

@ -0,0 +1,85 @@
/*
* 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"
)
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)
}
}
}

View file

@ -0,0 +1,20 @@
/*
* 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() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
Log.e("SPIDERMAN", "AppModule - registerComponents")
}
}

View file

@ -0,0 +1,16 @@
/*
* 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)

View file

@ -0,0 +1,75 @@
/*
* 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
)
}

View file

@ -0,0 +1,39 @@
/*
* 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
)
*/
)

View file

@ -0,0 +1,35 @@
<!--
~ 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>

View file

@ -0,0 +1,175 @@
<?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>

View file

@ -0,0 +1,12 @@
<?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>

View file

@ -0,0 +1,12 @@
<?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>

View file

@ -0,0 +1,11 @@
<?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>

View file

@ -0,0 +1,11 @@
<?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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -0,0 +1,15 @@
<?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>

View file

@ -0,0 +1,8 @@
<!--
~ Copyright 2023 Signal Messenger, LLC
~ SPDX-License-Identifier: AGPL-3.0-only
-->
<resources>
<string name="app_name">Glide WebP</string>
</resources>

View file

@ -0,0 +1,9 @@
<?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 Normal file
View file

@ -0,0 +1,2 @@
/build
.cxx

View file

@ -0,0 +1,36 @@
/*
* 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")
}
}
}
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)
}

View file

@ -0,0 +1,24 @@
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)
target_link_libraries(signalwebp webpdemux)

View file

@ -0,0 +1,62 @@
#include <jni.h>
#include <webp/demux.h>
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);
env->DeleteLocalRef(argb8888Config);
return jbitmap;
}
jobject nativeDecodeBitmap(JNIEnv *env, jobject, jbyteArray data) {
jbyte *javaBytes = env->GetByteArrayElements(data, nullptr);
auto *buffer = reinterpret_cast<uint8_t *>(javaBytes);
jsize bufferLength = env->GetArrayLength(data);
WebPBitstreamFeatures features;
WebPGetFeatures(buffer, bufferLength, &features);
int width;
int height;
uint8_t *pixels = WebPDecodeBGRA(buffer, bufferLength, &width, &height);
jobject jbitmap = createBitmap(env, width, height, pixels);
WebPFree(pixels);
env->ReleaseByteArrayElements(data, javaBytes, 0);
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[] = {
{"nativeDecodeBitmap", "([B)Landroid/graphics/Bitmap;", reinterpret_cast<void *>(nativeDecodeBitmap)}
};
int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
if (rc != JNI_OK) {
return rc;
}
return JNI_VERSION_1_6;
}

View file

@ -0,0 +1,17 @@
/*
* 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 nativeDecodeBitmap(data: ByteArray): Bitmap?
}

View file

@ -0,0 +1,71 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.glide.webp
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 java.io.IOException
import java.io.InputStream
class WebpInputStreamResourceDecoder(private val bitmapPool: BitmapPool) : ResourceDecoder<InputStream, Bitmap> {
companion object {
private const val TAG = "WebpResourceDecoder" // Name > 23 characters
private val MAGIC_NUMBER_P1 = byteArrayOf(0x52, 0x49, 0x46, 0x46) // "RIFF"
private val MAGIC_NUMBER_P2 = byteArrayOf(0x57, 0x45, 0x42, 0x50) // "WEBP"
private const val MAX_WEBP_COMPRESSED_SIZE = 10 * 1024 * 1024; // 10mb
}
/**
* The "magic number" for a WEBP file is in the first 12 bytes. The layout is:
*
* [0-3]: "RIFF"
* [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
*/
override fun handles(source: InputStream, options: Options): Boolean {
return try {
val magicNumberP1 = ByteArray(4)
StreamUtil.readFully(source, magicNumberP1)
val fileLength = ByteArray(4)
StreamUtil.readFully(source, fileLength)
val magicNumberP2 = ByteArray(4)
StreamUtil.readFully(source, magicNumberP2)
magicNumberP1.contentEquals(MAGIC_NUMBER_P1) && magicNumberP2.contentEquals(MAGIC_NUMBER_P2)
} catch (e: IOException) {
Log.w(TAG, "Failed to read magic number from stream!", e)
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().nativeDecodeBitmap(webp)
return BitmapResource.obtain(bitmap, bitmapPool)
}
}

View file

@ -0,0 +1,30 @@
/*
* 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))
}
}

View file

@ -426,6 +426,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="95357e17469bed6ba57588baa55a6579633b6d02143878c4351bad04006c0295" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.compiler" name="compiler" version="1.3.2">
<artifact name="compiler-1.3.2.jar">
<sha256 value="d19beb5fc48395e1730acad2f654daa49a17f6b44a2a45a2e23a0dec806a6931" origin="Generated by Gradle"/>
</artifact>
<artifact name="compiler-1.3.2.module">
<sha256 value="fa7fc38c46d9440fb2673e6a128057d75bb896d0a991fe701cb07825b17f18a7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.compiler" name="compiler" version="1.4.3">
<artifact name="compiler-1.4.3.jar">
<sha256 value="43fcad86835221accdc8c4043fa52d2dfcc8d66442498d298c6404376d18d17c" origin="Generated by Gradle"/>

1
libwebp Submodule

@ -0,0 +1 @@
Subproject commit ca332209cb5567c9b249c86788cb2dbf8847e760

View file

@ -27,7 +27,7 @@ RUN unzip ${ANDROID_COMMAND_LINE_TOOLS_FILENAME} -d /usr/local/android-sdk-linux
RUN rm ${ANDROID_COMMAND_LINE_TOOLS_FILENAME}
RUN yes | sdkmanager --update --sdk_root="${ANDROID_HOME}"
RUN yes | sdkmanager --sdk_root="${ANDROID_HOME}" "platforms;${ANDROID_API_LEVELS}" "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" "extras;google;m2repository" "extras;android;m2repository" "extras;google;google_play_services"
RUN yes | sdkmanager --sdk_root="${ANDROID_HOME}" "platforms;${ANDROID_API_LEVELS}" "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" "extras;google;m2repository" "extras;android;m2repository" "extras;google;google_play_services" "ndk;25.1.8937393" "cmake;3.22.1"
RUN yes | sdkmanager --licenses --sdk_root="${ANDROID_HOME}"

View file

@ -57,6 +57,8 @@ include ':core-ui'
include ':benchmark'
include ':microbenchmark'
include ':video-app'
include ':glide-webp'
include ':glide-webp-app'
project(':app').name = 'Signal-Android'
project(':paging').projectDir = file('paging/lib')
@ -85,6 +87,9 @@ project(':contacts-app').projectDir = file('contacts/app')
project(':qr').projectDir = file('qr/lib')
project(':qr-app').projectDir = file('qr/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'
apply from: 'dependencies.gradle'