mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-13 18:58:53 +01:00
Allow setting the radius of Portrait blur
This commit is contained in:
parent
cef00a60da
commit
c0c49361b8
8 changed files with 175 additions and 50 deletions
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
Loading…
Reference in a new issue