mirror of
https://github.com/immich-app/immich.git
synced 2024-11-28 09:33:27 +02:00
feat(mobile): Adding filters feature to mobile image editor (#13174)
* Adding filters button * Filter selection page * routing * Localization * Add Filters to this page * More Filters yay! * Final filters * Logic for saving the image * Fixes * Formmating * Finalizing, formating, and fixes * Layout fix * chores * Chore: Static code analysis * fix translation file --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
c5c492eb4f
commit
52c700e9b0
@ -589,6 +589,7 @@
|
|||||||
"viewer_remove_from_stack": "Remove from Stack",
|
"viewer_remove_from_stack": "Remove from Stack",
|
||||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||||
"viewer_unstack": "Un-Stack",
|
"viewer_unstack": "Un-Stack",
|
||||||
|
"filter": "Filter",
|
||||||
"downloading_media": "Downloading media",
|
"downloading_media": "Downloading media",
|
||||||
"download_finished": "Download finished",
|
"download_finished": "Download finished",
|
||||||
"download_filename": "file: {}",
|
"download_filename": "file: {}",
|
||||||
|
799
mobile/lib/constants/filters.dart
Normal file
799
mobile/lib/constants/filters.dart
Normal file
@ -0,0 +1,799 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
List<ColorFilter> filters = [
|
||||||
|
//Original
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Vintage
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.8,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0.1,
|
||||||
|
0.8,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.8,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Mood
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.2,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0.1,
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Crisp
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Cool
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.9,
|
||||||
|
0,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Blush
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0.1,
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Sunkissed
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.3,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
15,
|
||||||
|
0,
|
||||||
|
1.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.9,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Fresh
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Classic
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
-0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
-0.1,
|
||||||
|
1.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
-0.1,
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Lomo-ish
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.5,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.45,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
1.3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Nashville
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.2,
|
||||||
|
0.15,
|
||||||
|
-0.15,
|
||||||
|
0,
|
||||||
|
15,
|
||||||
|
0.1,
|
||||||
|
1.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
-0.05,
|
||||||
|
0.2,
|
||||||
|
1.25,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Valencia
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.15,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0.1,
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Clarendon
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
1.25,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.3,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Moon
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.33,
|
||||||
|
0.33,
|
||||||
|
0.33,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.33,
|
||||||
|
0.33,
|
||||||
|
0.33,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.33,
|
||||||
|
0.33,
|
||||||
|
0.33,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Willow
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Kodak
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.3,
|
||||||
|
0.1,
|
||||||
|
-0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
1.25,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
-0.1,
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Frost
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.8,
|
||||||
|
0.2,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2,
|
||||||
|
1.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Night Vision
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.1,
|
||||||
|
0.95,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
1.5,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2,
|
||||||
|
0.7,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Sunset
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.5,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0.9,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
-0.1,
|
||||||
|
-0.2,
|
||||||
|
1.3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Noir
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.3,
|
||||||
|
-0.3,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
-0.1,
|
||||||
|
1.2,
|
||||||
|
-0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
-0.2,
|
||||||
|
1.3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Dreamy
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
1.1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
15,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Sepia
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.393,
|
||||||
|
0.769,
|
||||||
|
0.189,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.349,
|
||||||
|
0.686,
|
||||||
|
0.168,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.272,
|
||||||
|
0.534,
|
||||||
|
0.131,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Radium
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.438,
|
||||||
|
-0.062,
|
||||||
|
-0.062,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
-0.122,
|
||||||
|
1.378,
|
||||||
|
-0.122,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
-0.016,
|
||||||
|
-0.016,
|
||||||
|
1.483,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Aqua
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.2126,
|
||||||
|
0.7152,
|
||||||
|
0.0722,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2126,
|
||||||
|
0.7152,
|
||||||
|
0.0722,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.7873,
|
||||||
|
0.2848,
|
||||||
|
0.9278,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Purple Haze
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.3,
|
||||||
|
0,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
1.3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Lemonade
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.2,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.1,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0.7,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Caramel
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.6,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
1.3,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0.9,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Peachy
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.3,
|
||||||
|
0.5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2,
|
||||||
|
1.1,
|
||||||
|
0.3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Neon
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Cold Morning
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.9,
|
||||||
|
0.1,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Lush
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.9,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Urban Neon
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
1.1,
|
||||||
|
0,
|
||||||
|
0.3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.9,
|
||||||
|
0.3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.3,
|
||||||
|
0.1,
|
||||||
|
1.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
//Monochrome
|
||||||
|
const ColorFilter.matrix([
|
||||||
|
0.6,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2,
|
||||||
|
0.6,
|
||||||
|
0.2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.7,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
const List<String> filterNames = [
|
||||||
|
'Original',
|
||||||
|
'Vintage',
|
||||||
|
'Mood',
|
||||||
|
'Crisp',
|
||||||
|
'Cool',
|
||||||
|
'Blush',
|
||||||
|
'Sunkissed',
|
||||||
|
'Fresh',
|
||||||
|
'Classic',
|
||||||
|
'Lomo-ish',
|
||||||
|
'Nashville',
|
||||||
|
'Valencia',
|
||||||
|
'Clarendon',
|
||||||
|
'Moon',
|
||||||
|
'Willow',
|
||||||
|
'Kodak',
|
||||||
|
'Frost',
|
||||||
|
'Night Vision',
|
||||||
|
'Sunset',
|
||||||
|
'Noir',
|
||||||
|
'Dreamy',
|
||||||
|
'Sepia',
|
||||||
|
'Radium',
|
||||||
|
'Aqua',
|
||||||
|
'Purple Haze',
|
||||||
|
'Lemonade',
|
||||||
|
'Caramel',
|
||||||
|
'Peachy',
|
||||||
|
'Neon',
|
||||||
|
'Cold Morning',
|
||||||
|
'Lush',
|
||||||
|
'Urban Neon',
|
||||||
|
'Monochrome',
|
||||||
|
];
|
@ -1,4 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
@ -9,7 +8,6 @@ import 'package:fluttertoast/fluttertoast.dart';
|
|||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
@ -91,9 +89,6 @@ class EditImagePage extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final Image imageWidget =
|
|
||||||
Image(image: ImmichImage.imageProvider(asset: asset));
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("edit_image_title".tr()),
|
title: Text("edit_image_title".tr()),
|
||||||
@ -157,26 +152,50 @@ class EditImagePage extends ConsumerWidget {
|
|||||||
color: context.scaffoldBackgroundColor,
|
color: context.scaffoldBackgroundColor,
|
||||||
borderRadius: BorderRadius.circular(30),
|
borderRadius: BorderRadius.circular(30),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: <Widget>[
|
||||||
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Platform.isAndroid
|
Icons.crop_rotate_rounded,
|
||||||
? Icons.crop_rotate_rounded
|
|
||||||
: Icons.crop_rotate_rounded,
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
color: Theme.of(context).iconTheme.color,
|
||||||
size: 25,
|
size: 25,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushRoute(
|
context.pushRoute(
|
||||||
CropImageRoute(asset: asset, image: imageWidget),
|
CropImageRoute(asset: asset, image: image),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Text("crop".tr(), style: context.textTheme.displayMedium),
|
Text("crop".tr(), style: context.textTheme.displayMedium),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.filter,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
size: 25,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context.pushRoute(
|
||||||
|
FilterImageRoute(
|
||||||
|
asset: asset,
|
||||||
|
image: image,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text("filter".tr(), style: context.textTheme.displayMedium),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
187
mobile/lib/pages/editing/filter.page.dart
Normal file
187
mobile/lib/pages/editing/filter.page.dart
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/constants/filters.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
|
||||||
|
/// A widget for filtering an image.
|
||||||
|
/// This widget uses [HookWidget] to manage its lifecycle and state. It allows
|
||||||
|
/// users to add filters to an image and then navigate to the [EditImagePage] with the
|
||||||
|
/// final composition.'
|
||||||
|
@RoutePage()
|
||||||
|
class FilterImagePage extends HookWidget {
|
||||||
|
final Image image;
|
||||||
|
final Asset asset;
|
||||||
|
|
||||||
|
const FilterImagePage({
|
||||||
|
super.key,
|
||||||
|
required this.image,
|
||||||
|
required this.asset,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorFilter = useState<ColorFilter>(filters[0]);
|
||||||
|
final selectedFilterIndex = useState<int>(0);
|
||||||
|
|
||||||
|
Future<ui.Image> createFilteredImage(
|
||||||
|
ui.Image inputImage,
|
||||||
|
ColorFilter filter,
|
||||||
|
) {
|
||||||
|
final completer = Completer<ui.Image>();
|
||||||
|
final size =
|
||||||
|
Size(inputImage.width.toDouble(), inputImage.height.toDouble());
|
||||||
|
final recorder = ui.PictureRecorder();
|
||||||
|
final canvas = Canvas(recorder);
|
||||||
|
|
||||||
|
final paint = Paint()..colorFilter = filter;
|
||||||
|
canvas.drawImage(inputImage, Offset.zero, paint);
|
||||||
|
|
||||||
|
recorder
|
||||||
|
.endRecording()
|
||||||
|
.toImage(size.width.round(), size.height.round())
|
||||||
|
.then((image) {
|
||||||
|
completer.complete(image);
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyFilter(ColorFilter filter, int index) {
|
||||||
|
colorFilter.value = filter;
|
||||||
|
selectedFilterIndex.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Image> applyFilterAndConvert(ColorFilter filter) async {
|
||||||
|
final completer = Completer<ui.Image>();
|
||||||
|
image.image.resolve(ImageConfiguration.empty).addListener(
|
||||||
|
ImageStreamListener((ImageInfo info, bool _) {
|
||||||
|
completer.complete(info.image);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
final uiImage = await completer.future;
|
||||||
|
|
||||||
|
final filteredUiImage = await createFilteredImage(uiImage, filter);
|
||||||
|
final byteData =
|
||||||
|
await filteredUiImage.toByteData(format: ui.ImageByteFormat.png);
|
||||||
|
final pngBytes = byteData!.buffer.asUint8List();
|
||||||
|
|
||||||
|
return Image.memory(pngBytes, fit: BoxFit.contain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: context.scaffoldBackgroundColor,
|
||||||
|
title: Text("filter".tr()),
|
||||||
|
leading: CloseButton(color: context.primaryColor),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.done_rounded,
|
||||||
|
color: context.primaryColor,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final filteredImage =
|
||||||
|
await applyFilterAndConvert(colorFilter.value);
|
||||||
|
context.pushRoute(
|
||||||
|
EditImageRoute(
|
||||||
|
asset: asset,
|
||||||
|
image: filteredImage,
|
||||||
|
isEdited: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
backgroundColor: context.scaffoldBackgroundColor,
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.7,
|
||||||
|
child: Center(
|
||||||
|
child: ColorFiltered(
|
||||||
|
colorFilter: colorFilter.value,
|
||||||
|
child: image,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 120,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: filters.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: _FilterButton(
|
||||||
|
image: image,
|
||||||
|
label: filterNames[index],
|
||||||
|
filter: filters[index],
|
||||||
|
isSelected: selectedFilterIndex.value == index,
|
||||||
|
onTap: () => applyFilter(filters[index], index),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FilterButton extends StatelessWidget {
|
||||||
|
final Image image;
|
||||||
|
final String label;
|
||||||
|
final ColorFilter filter;
|
||||||
|
final bool isSelected;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const _FilterButton({
|
||||||
|
required this.image,
|
||||||
|
required this.label,
|
||||||
|
required this.filter,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: isSelected
|
||||||
|
? Border.all(color: context.primaryColor, width: 3)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: ColorFiltered(
|
||||||
|
colorFilter: filter,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
child: image,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(label, style: Theme.of(context).textTheme.bodyMedium),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,7 @@ import 'package:immich_mobile/pages/common/splash_screen.page.dart';
|
|||||||
import 'package:immich_mobile/pages/common/tab_controller.page.dart';
|
import 'package:immich_mobile/pages/common/tab_controller.page.dart';
|
||||||
import 'package:immich_mobile/pages/editing/edit.page.dart';
|
import 'package:immich_mobile/pages/editing/edit.page.dart';
|
||||||
import 'package:immich_mobile/pages/editing/crop.page.dart';
|
import 'package:immich_mobile/pages/editing/crop.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/editing/filter.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/archive.page.dart';
|
import 'package:immich_mobile/pages/library/archive.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/favorite.page.dart';
|
import 'package:immich_mobile/pages/library/favorite.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/library.page.dart';
|
import 'package:immich_mobile/pages/library/library.page.dart';
|
||||||
@ -135,6 +136,7 @@ class AppRouter extends RootStackRouter {
|
|||||||
),
|
),
|
||||||
AutoRoute(page: EditImageRoute.page),
|
AutoRoute(page: EditImageRoute.page),
|
||||||
AutoRoute(page: CropImageRoute.page),
|
AutoRoute(page: CropImageRoute.page),
|
||||||
|
AutoRoute(page: FilterImageRoute.page),
|
||||||
AutoRoute(page: FavoritesRoute.page, guards: [_authGuard, _duplicateGuard]),
|
AutoRoute(page: FavoritesRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||||
AutoRoute(page: AllVideosRoute.page, guards: [_authGuard, _duplicateGuard]),
|
AutoRoute(page: AllVideosRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
|
@ -755,6 +755,58 @@ class FavoritesRoute extends PageRouteInfo<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [FilterImagePage]
|
||||||
|
class FilterImageRoute extends PageRouteInfo<FilterImageRouteArgs> {
|
||||||
|
FilterImageRoute({
|
||||||
|
Key? key,
|
||||||
|
required Image image,
|
||||||
|
required Asset asset,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
FilterImageRoute.name,
|
||||||
|
args: FilterImageRouteArgs(
|
||||||
|
key: key,
|
||||||
|
image: image,
|
||||||
|
asset: asset,
|
||||||
|
),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'FilterImageRoute';
|
||||||
|
|
||||||
|
static PageInfo page = PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
final args = data.argsAs<FilterImageRouteArgs>();
|
||||||
|
return FilterImagePage(
|
||||||
|
key: args.key,
|
||||||
|
image: args.image,
|
||||||
|
asset: args.asset,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterImageRouteArgs {
|
||||||
|
const FilterImageRouteArgs({
|
||||||
|
this.key,
|
||||||
|
required this.image,
|
||||||
|
required this.asset,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
final Image image;
|
||||||
|
|
||||||
|
final Asset asset;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'FilterImageRouteArgs{key: $key, image: $image, asset: $asset}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [GalleryViewerPage]
|
/// [GalleryViewerPage]
|
||||||
class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
|
class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
|
||||||
|
Loading…
Reference in New Issue
Block a user