Allow setting the radius of Portrait blur

This commit is contained in:
Ming Ming 2022-05-16 03:07:26 +08:00
parent cef00a60da
commit c0c49361b8
8 changed files with 175 additions and 50 deletions

View file

@ -1182,6 +1182,7 @@
"@enhanceTooltip": {
"description": "Enhance a photo"
},
"enhanceButtonLabel": "ENHANCE",
"enhanceIntroDialogTitle": "Enhance your photos",
"enhanceIntroDialogDescription": "Your photos are processed locally on your device. By default, they are downscaled to 2048x1536. You can adjust the output resolution in Settings",
"enhanceLowLightTitle": "Low-light enhancement",
@ -1200,6 +1201,10 @@
"@enhancePortraitBlurTitle": {
"description": "Blur the background of a photo"
},
"enhancePortraitBlurParamBlurLabel": "Blurriness",
"@enhancePortraitBlurParamBlurLabel": {
"description": "This parameter sets the radius of the blur filter"
},
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
"@errorUnauthenticated": {

View file

@ -89,12 +89,14 @@
"backgroundServiceStopping",
"metadataTaskPauseLowBatteryNotification",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel",
"errorAlbumDowngrade"
],
@ -202,12 +204,14 @@
"backgroundServiceStopping",
"metadataTaskPauseLowBatteryNotification",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel",
"errorAlbumDowngrade"
],
@ -370,12 +374,14 @@
"backgroundServiceStopping",
"metadataTaskPauseLowBatteryNotification",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel",
"errorAlbumDowngrade"
],
@ -389,12 +395,14 @@
"backgroundServiceStopping",
"metadataTaskPauseLowBatteryNotification",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle"
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel"
],
"fi": [
@ -403,12 +411,14 @@
"settingsEnhanceMaxResolutionTitle",
"settingsEnhanceMaxResolutionDescription",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle"
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel"
],
"fr": [
@ -421,12 +431,14 @@
"helpButtonLabel",
"removeFromAlbumTooltip",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle"
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel"
],
"pl": [
@ -456,12 +468,14 @@
"backgroundServiceStopping",
"metadataTaskPauseLowBatteryNotification",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle"
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel"
],
"pt": [
@ -470,12 +484,14 @@
"settingsEnhanceMaxResolutionTitle",
"settingsEnhanceMaxResolutionDescription",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle"
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel"
],
"ru": [
@ -484,12 +500,14 @@
"settingsEnhanceMaxResolutionTitle",
"settingsEnhanceMaxResolutionDescription",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle"
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel"
],
"zh": [
@ -498,12 +516,14 @@
"settingsEnhanceMaxResolutionTitle",
"settingsEnhanceMaxResolutionDescription",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle"
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel"
],
"zh_Hant": [
@ -512,11 +532,13 @@
"settingsEnhanceMaxResolutionTitle",
"settingsEnhanceMaxResolutionDescription",
"enhanceTooltip",
"enhanceButtonLabel",
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"collectionEnhancedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle"
"enhancePortraitBlurTitle",
"enhancePortraitBlurParamBlurLabel"
]
}

View file

@ -13,7 +13,9 @@ import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/platform/k.dart' as platform_k;
import 'package:nc_photos/pref.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/widget/settings.dart';
import 'package:nc_photos/widget/stateful_slider.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:url_launcher/url_launcher.dart';
@ -35,39 +37,17 @@ class EnhanceHandler {
return;
}
final selected = await showDialog<_Algorithm>(
context: context,
builder: (context) => SimpleDialog(
children: _getOptions()
.map((o) => SimpleDialogOption(
padding: const EdgeInsets.all(0),
child: ListTile(
title: Text(o.title),
subtitle: o.subtitle?.run((t) => Text(t)),
trailing: o.link != null
? SizedBox(
height: double.maxFinite,
child: TextButton(
child: Text(L10n.global().detailsTooltip),
onPressed: () {
launch(o.link!);
},
),
)
: null,
onTap: () {
Navigator.of(context).pop(o.algorithm);
},
),
))
.toList(),
),
);
final selected = await _pickAlgorithm(context);
if (selected == null) {
// user canceled
return;
}
_log.info("[call] Selected: ${selected.name}");
final args = await _getArgs(context, selected);
if (args == null) {
// user canceled
return;
}
switch (selected) {
case _Algorithm.zeroDce:
await ImageProcessor.zeroDce(
@ -87,6 +67,7 @@ class EnhanceHandler {
file.filename,
Pref().getEnhanceMaxWidthOr(),
Pref().getEnhanceMaxHeightOr(),
args["radius"] ?? 16,
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
@ -148,6 +129,36 @@ class EnhanceHandler {
return true;
}
Future<_Algorithm?> _pickAlgorithm(BuildContext context) =>
showDialog<_Algorithm>(
context: context,
builder: (context) => SimpleDialog(
children: _getOptions()
.map((o) => SimpleDialogOption(
padding: const EdgeInsets.all(0),
child: ListTile(
title: Text(o.title),
subtitle: o.subtitle?.run((t) => Text(t)),
trailing: o.link != null
? SizedBox(
height: double.maxFinite,
child: TextButton(
child: Text(L10n.global().detailsTooltip),
onPressed: () {
launch(o.link!);
},
),
)
: null,
onTap: () {
Navigator.of(context).pop(o.algorithm);
},
),
))
.toList(),
),
);
List<_Option> _getOptions() => [
if (platform_k.isAndroid)
_Option(
@ -165,6 +176,70 @@ class EnhanceHandler {
),
];
Future<Map<String, dynamic>?> _getArgs(
BuildContext context, _Algorithm selected) async {
switch (selected) {
case _Algorithm.zeroDce:
return {};
case _Algorithm.deepLab3Portrait:
return _getDeepLab3PortraitArgs(context);
}
}
Future<Map<String, dynamic>?> _getDeepLab3PortraitArgs(
BuildContext context) async {
var current = .5;
final radius = await showDialog<int>(
context: context,
builder: (context) => AppTheme(
child: AlertDialog(
title: Text(L10n.global().enhancePortraitBlurParamBlurLabel),
contentPadding: const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 0),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisSize: MainAxisSize.max,
children: [
Icon(
Icons.circle,
size: 20,
color: AppTheme.getSecondaryTextColor(context),
),
Expanded(
child: StatefulSlider(
initialValue: current,
onChangeEnd: (value) {
current = value;
},
),
),
Icon(
Icons.blur_on,
color: AppTheme.getSecondaryTextColor(context),
),
],
),
],
),
actions: [
TextButton(
onPressed: () {
final radius = (current * 25).round().clamp(1, 25);
Navigator.of(context).pop(radius);
},
child: Text(L10n.global().enhanceButtonLabel),
),
],
),
),
);
_log.info("[_getDeepLab3PortraitArgs] radius: $radius");
return radius?.run((it) => {"radius": it});
}
final Account account;
final File file;

View file

@ -40,6 +40,7 @@ class ImageProcessorChannelHandler(context: Context) :
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument("radius")!!,
result
)
} catch (e: Throwable) {
@ -69,16 +70,18 @@ class ImageProcessorChannelHandler(context: Context) :
private fun deepLab3Portrait(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, result: MethodChannel.Result
maxWidth: Int, maxHeight: Int, radius: Int, result: MethodChannel.Result
) = method(
fileUrl, headers, filename, maxWidth, maxHeight,
ImageProcessorService.METHOD_DEEL_LAP_PORTRAIT, result
ImageProcessorService.METHOD_DEEL_LAP_PORTRAIT, result, onIntent = {
it.putExtra(ImageProcessorService.EXTRA_RADIUS, radius)
}
)
private fun method(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, method: String,
result: MethodChannel.Result
result: MethodChannel.Result, onIntent: ((Intent) -> Unit)? = null
) {
val intent = Intent(context, ImageProcessorService::class.java).apply {
putExtra(ImageProcessorService.EXTRA_METHOD, method)
@ -89,6 +92,7 @@ class ImageProcessorChannelHandler(context: Context) :
putExtra(ImageProcessorService.EXTRA_FILENAME, filename)
putExtra(ImageProcessorService.EXTRA_MAX_WIDTH, maxWidth)
putExtra(ImageProcessorService.EXTRA_MAX_HEIGHT, maxHeight)
onIntent?.invoke(this)
}
ContextCompat.startForegroundService(context, intent)
result.success(null)

View file

@ -33,6 +33,7 @@ class ImageProcessorService : Service() {
const val EXTRA_FILENAME = "filename"
const val EXTRA_MAX_WIDTH = "maxWidth"
const val EXTRA_MAX_HEIGHT = "maxHeight"
const val EXTRA_RADIUS = "radius"
private const val ACTION_CANCEL = "cancel"
@ -111,8 +112,13 @@ class ImageProcessorService : Service() {
private fun onZeroDce(startId: Int, extras: Bundle) =
onMethod(startId, extras, METHOD_ZERO_DCE)
private fun onDeepLapPortrait(startId: Int, extras: Bundle) =
onMethod(startId, extras, METHOD_DEEL_LAP_PORTRAIT)
private fun onDeepLapPortrait(startId: Int, extras: Bundle) {
return onMethod(
startId, extras, METHOD_DEEL_LAP_PORTRAIT, args = mapOf(
"radius" to extras.getIntOrNull(EXTRA_RADIUS)
)
)
}
/**
* Handle methods without arguments
@ -121,7 +127,10 @@ class ImageProcessorService : Service() {
* @param extras
* @param method
*/
private fun onMethod(startId: Int, extras: Bundle, method: String) {
private fun onMethod(
startId: Int, extras: Bundle, method: String,
args: Map<String, Any?> = mapOf()
) {
val fileUrl = extras.getString(EXTRA_FILE_URL)!!
@Suppress("Unchecked_cast")
@ -132,7 +141,8 @@ class ImageProcessorService : Service() {
val maxHeight = extras.getInt(EXTRA_MAX_HEIGHT)
addCommand(
ImageProcessorCommand(
startId, method, fileUrl, headers, filename, maxWidth, maxHeight
startId, method, fileUrl, headers, filename, maxWidth,
maxHeight, args = args
)
)
}
@ -280,7 +290,7 @@ private data class ImageProcessorCommand(
val filename: String,
val maxWidth: Int,
val maxHeight: Int,
val args: Map<String, Any> = mapOf(),
val args: Map<String, Any?> = mapOf(),
)
@Suppress("Deprecation")
@ -426,9 +436,12 @@ private open class ImageProcessorCommandTask(context: Context) :
).infer(
fileUri
)
ImageProcessorService.METHOD_DEEL_LAP_PORTRAIT -> DeepLab3Portrait(
context, cmd.maxWidth, cmd.maxHeight
context, cmd.maxWidth, cmd.maxHeight,
cmd.args["radius"] as? Int ?: 16
).infer(fileUri)
else -> throw IllegalArgumentException(
"Unknown method: ${cmd.method}"
)

View file

@ -2,6 +2,7 @@ package com.nkming.nc_photos.plugin
import android.app.PendingIntent
import android.os.Build
import android.os.Bundle
import java.net.HttpURLConnection
fun getPendingIntentFlagImmutable(): Int {
@ -37,3 +38,5 @@ inline fun <T> measureTime(tag: String, message: String, block: () -> T): T {
logI(tag, "$message: ${elapsed}ms")
}
}
fun Bundle.getIntOrNull(key: String) = get(key) as? Int

View file

@ -71,10 +71,10 @@ private class DeepLab3(context: Context) {
private val context = context
}
class DeepLab3Portrait(context: Context, maxWidth: Int, maxHeight: Int) {
class DeepLab3Portrait(
context: Context, maxWidth: Int, maxHeight: Int, radius: Int
) {
companion object {
private const val RADIUS = 16
private const val TAG = "DeepLab3Portrait"
}
@ -82,7 +82,7 @@ class DeepLab3Portrait(context: Context, maxWidth: Int, maxHeight: Int) {
val segmentMap = deepLab.infer(imageUri).also {
postProcessSegmentMap(it)
}
return enhance(imageUri, segmentMap, RADIUS)
return enhance(imageUri, segmentMap, radius)
}
/**
@ -146,5 +146,6 @@ class DeepLab3Portrait(context: Context, maxWidth: Int, maxHeight: Int) {
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val radius = radius
private val deepLab = DeepLab3(context)
}

View file

@ -23,7 +23,8 @@ class ImageProcessor {
String fileUrl,
String filename,
int maxWidth,
int maxHeight, {
int maxHeight,
int radius, {
Map<String, String>? headers,
}) =>
_methodChannel.invokeMethod("deepLab3Portrait", <String, dynamic>{
@ -32,6 +33,7 @@ class ImageProcessor {
"filename": filename,
"maxWidth": maxWidth,
"maxHeight": maxHeight,
"radius": radius,
});
static const _methodChannel =