Skip to content

Commit

Permalink
Enable WebP decoding in Signal using libwebp v1.3.2
Browse files Browse the repository at this point in the history
Co-authored-by: Greyson Parrelli <[email protected]>
Co-authored-by: Greyson Parrelli <[email protected]>
  • Loading branch information
3 people authored and alex-signal committed Sep 22, 2023
1 parent 091f7c4 commit a7d9fd1
Show file tree
Hide file tree
Showing 59 changed files with 874 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:

steps:
- uses: actions/checkout@v3
with:
submodules: true

- name: set up JDK 17
uses: actions/setup-java@v3
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/diffuse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,6 +46,7 @@ jobs:

- uses: actions/checkout@v3
with:
submodules: true
clean: 'false'

- name: Build with Gradle
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "libwebp"]
path = libwebp
url = [email protected]:webmproject/libwebp.git
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,4 @@ private Bitmap.CompressFormat getFormat(Bitmap bitmap, Options options) {
return Bitmap.CompressFormat.JPEG;
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ android {
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
}

buildFeatures {
compose = true
}

composeOptions {
kotlinCompilerExtensionVersion = "1.4.4"
}
}

dependencies {
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 12 additions & 3 deletions core-util/src/main/java/org/signal/core/util/StreamUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,21 @@ public static void readFully(InputStream in, byte[] buffer, int len) throws IOEx
}

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();
Expand Down
2 changes: 2 additions & 0 deletions glide-config/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ android {
dependencies {
implementation libs.glide.glide
kapt libs.glide.compiler

implementation project(':glide-webp')
}
1 change: 1 addition & 0 deletions glide-webp/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
25 changes: 25 additions & 0 deletions glide-webp/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
29 changes: 29 additions & 0 deletions glide-webp/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -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 added glide-webp/app/src/main/assets/test_01.webp
Binary file not shown.
Binary file added glide-webp/app/src/main/assets/test_02.webp
Binary file not shown.
Binary file added glide-webp/app/src/main/assets/test_03.webp
Binary file not shown.
Binary file added glide-webp/app/src/main/assets/test_04.webp
Binary file not shown.
Binary file added glide-webp/app/src/main/assets/test_05.webp
Binary file not shown.
Binary file not shown.
Binary file added glide-webp/app/src/main/assets/test_06_lossy.webp
Binary file not shown.
Binary file not shown.
Binary file added glide-webp/app/src/main/assets/test_07_lossy.webp
Binary file not shown.
Binary file not shown.
Binary file added glide-webp/app/src/main/assets/test_08_lossy.webp
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
)
}
Loading

0 comments on commit a7d9fd1

Please sign in to comment.