mirror of
https://github.com/immich-app/immich.git
synced 2026-01-26 11:24:44 -08:00
feat: server support for filters
This commit is contained in:
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@@ -358,6 +358,7 @@ Class | Method | HTTP request | Description
|
||||
- [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md)
|
||||
- [AssetEditAction](doc//AssetEditAction.md)
|
||||
- [AssetEditActionCrop](doc//AssetEditActionCrop.md)
|
||||
- [AssetEditActionFilter](doc//AssetEditActionFilter.md)
|
||||
- [AssetEditActionListDto](doc//AssetEditActionListDto.md)
|
||||
- [AssetEditActionListDtoEditsInner](doc//AssetEditActionListDtoEditsInner.md)
|
||||
- [AssetEditActionMirror](doc//AssetEditActionMirror.md)
|
||||
@@ -427,6 +428,7 @@ Class | Method | HTTP request | Description
|
||||
- [ExifResponseDto](doc//ExifResponseDto.md)
|
||||
- [FaceDto](doc//FaceDto.md)
|
||||
- [FacialRecognitionConfig](doc//FacialRecognitionConfig.md)
|
||||
- [FilterParameters](doc//FilterParameters.md)
|
||||
- [FoldersResponse](doc//FoldersResponse.md)
|
||||
- [FoldersUpdate](doc//FoldersUpdate.md)
|
||||
- [ImageFormat](doc//ImageFormat.md)
|
||||
|
||||
2
mobile/openapi/lib/api.dart
generated
2
mobile/openapi/lib/api.dart
generated
@@ -98,6 +98,7 @@ part 'model/asset_delta_sync_dto.dart';
|
||||
part 'model/asset_delta_sync_response_dto.dart';
|
||||
part 'model/asset_edit_action.dart';
|
||||
part 'model/asset_edit_action_crop.dart';
|
||||
part 'model/asset_edit_action_filter.dart';
|
||||
part 'model/asset_edit_action_list_dto.dart';
|
||||
part 'model/asset_edit_action_list_dto_edits_inner.dart';
|
||||
part 'model/asset_edit_action_mirror.dart';
|
||||
@@ -167,6 +168,7 @@ part 'model/email_notifications_update.dart';
|
||||
part 'model/exif_response_dto.dart';
|
||||
part 'model/face_dto.dart';
|
||||
part 'model/facial_recognition_config.dart';
|
||||
part 'model/filter_parameters.dart';
|
||||
part 'model/folders_response.dart';
|
||||
part 'model/folders_update.dart';
|
||||
part 'model/image_format.dart';
|
||||
|
||||
4
mobile/openapi/lib/api_client.dart
generated
4
mobile/openapi/lib/api_client.dart
generated
@@ -242,6 +242,8 @@ class ApiClient {
|
||||
return AssetEditActionTypeTransformer().decode(value);
|
||||
case 'AssetEditActionCrop':
|
||||
return AssetEditActionCrop.fromJson(value);
|
||||
case 'AssetEditActionFilter':
|
||||
return AssetEditActionFilter.fromJson(value);
|
||||
case 'AssetEditActionListDto':
|
||||
return AssetEditActionListDto.fromJson(value);
|
||||
case 'AssetEditActionListDtoEditsInner':
|
||||
@@ -380,6 +382,8 @@ class ApiClient {
|
||||
return FaceDto.fromJson(value);
|
||||
case 'FacialRecognitionConfig':
|
||||
return FacialRecognitionConfig.fromJson(value);
|
||||
case 'FilterParameters':
|
||||
return FilterParameters.fromJson(value);
|
||||
case 'FoldersResponse':
|
||||
return FoldersResponse.fromJson(value);
|
||||
case 'FoldersUpdate':
|
||||
|
||||
3
mobile/openapi/lib/model/asset_edit_action.dart
generated
3
mobile/openapi/lib/model/asset_edit_action.dart
generated
@@ -26,12 +26,14 @@ class AssetEditAction {
|
||||
static const crop = AssetEditAction._(r'crop');
|
||||
static const rotate = AssetEditAction._(r'rotate');
|
||||
static const mirror = AssetEditAction._(r'mirror');
|
||||
static const filter = AssetEditAction._(r'filter');
|
||||
|
||||
/// List of all possible values in this [enum][AssetEditAction].
|
||||
static const values = <AssetEditAction>[
|
||||
crop,
|
||||
rotate,
|
||||
mirror,
|
||||
filter,
|
||||
];
|
||||
|
||||
static AssetEditAction? fromJson(dynamic value) => AssetEditActionTypeTransformer().decode(value);
|
||||
@@ -73,6 +75,7 @@ class AssetEditActionTypeTransformer {
|
||||
case r'crop': return AssetEditAction.crop;
|
||||
case r'rotate': return AssetEditAction.rotate;
|
||||
case r'mirror': return AssetEditAction.mirror;
|
||||
case r'filter': return AssetEditAction.filter;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
|
||||
107
mobile/openapi/lib/model/asset_edit_action_filter.dart
generated
Normal file
107
mobile/openapi/lib/model/asset_edit_action_filter.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetEditActionFilter {
|
||||
/// Returns a new [AssetEditActionFilter] instance.
|
||||
AssetEditActionFilter({
|
||||
required this.action,
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
AssetEditAction action;
|
||||
|
||||
FilterParameters parameters;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionFilter &&
|
||||
other.action == action &&
|
||||
other.parameters == parameters;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(parameters.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetEditActionFilter[action=$action, parameters=$parameters]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'parameters'] = this.parameters;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetEditActionFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetEditActionFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetEditActionFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetEditActionFilter(
|
||||
action: AssetEditAction.fromJson(json[r'action'])!,
|
||||
parameters: FilterParameters.fromJson(json[r'parameters'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetEditActionFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetEditActionFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetEditActionFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetEditActionFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetEditActionFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetEditActionFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetEditActionFilter-objects as value to a dart map
|
||||
static Map<String, List<AssetEditActionFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetEditActionFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetEditActionFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'parameters',
|
||||
};
|
||||
}
|
||||
|
||||
208
mobile/openapi/lib/model/filter_parameters.dart
generated
Normal file
208
mobile/openapi/lib/model/filter_parameters.dart
generated
Normal file
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class FilterParameters {
|
||||
/// Returns a new [FilterParameters] instance.
|
||||
FilterParameters({
|
||||
required this.bOffset,
|
||||
required this.bbBias,
|
||||
required this.bgBias,
|
||||
required this.brBias,
|
||||
required this.gOffset,
|
||||
required this.gbBias,
|
||||
required this.ggBias,
|
||||
required this.grBias,
|
||||
required this.rOffset,
|
||||
required this.rbBias,
|
||||
required this.rgBias,
|
||||
required this.rrBias,
|
||||
});
|
||||
|
||||
/// B Offset (-255 -> 255)
|
||||
///
|
||||
/// Minimum value: -255
|
||||
/// Maximum value: 255
|
||||
num bOffset;
|
||||
|
||||
/// BB Bias
|
||||
num bbBias;
|
||||
|
||||
/// BG Bias
|
||||
num bgBias;
|
||||
|
||||
/// BR Bias
|
||||
num brBias;
|
||||
|
||||
/// G Offset (-255 -> 255)
|
||||
///
|
||||
/// Minimum value: -255
|
||||
/// Maximum value: 255
|
||||
num gOffset;
|
||||
|
||||
/// GB Bias
|
||||
num gbBias;
|
||||
|
||||
/// GG Bias
|
||||
num ggBias;
|
||||
|
||||
/// GR Bias
|
||||
num grBias;
|
||||
|
||||
/// R Offset (-255 -> 255)
|
||||
///
|
||||
/// Minimum value: -255
|
||||
/// Maximum value: 255
|
||||
num rOffset;
|
||||
|
||||
/// RB Bias
|
||||
num rbBias;
|
||||
|
||||
/// RG Bias
|
||||
num rgBias;
|
||||
|
||||
/// RR Bias
|
||||
num rrBias;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is FilterParameters &&
|
||||
other.bOffset == bOffset &&
|
||||
other.bbBias == bbBias &&
|
||||
other.bgBias == bgBias &&
|
||||
other.brBias == brBias &&
|
||||
other.gOffset == gOffset &&
|
||||
other.gbBias == gbBias &&
|
||||
other.ggBias == ggBias &&
|
||||
other.grBias == grBias &&
|
||||
other.rOffset == rOffset &&
|
||||
other.rbBias == rbBias &&
|
||||
other.rgBias == rgBias &&
|
||||
other.rrBias == rrBias;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(bOffset.hashCode) +
|
||||
(bbBias.hashCode) +
|
||||
(bgBias.hashCode) +
|
||||
(brBias.hashCode) +
|
||||
(gOffset.hashCode) +
|
||||
(gbBias.hashCode) +
|
||||
(ggBias.hashCode) +
|
||||
(grBias.hashCode) +
|
||||
(rOffset.hashCode) +
|
||||
(rbBias.hashCode) +
|
||||
(rgBias.hashCode) +
|
||||
(rrBias.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'FilterParameters[bOffset=$bOffset, bbBias=$bbBias, bgBias=$bgBias, brBias=$brBias, gOffset=$gOffset, gbBias=$gbBias, ggBias=$ggBias, grBias=$grBias, rOffset=$rOffset, rbBias=$rbBias, rgBias=$rgBias, rrBias=$rrBias]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'bOffset'] = this.bOffset;
|
||||
json[r'bbBias'] = this.bbBias;
|
||||
json[r'bgBias'] = this.bgBias;
|
||||
json[r'brBias'] = this.brBias;
|
||||
json[r'gOffset'] = this.gOffset;
|
||||
json[r'gbBias'] = this.gbBias;
|
||||
json[r'ggBias'] = this.ggBias;
|
||||
json[r'grBias'] = this.grBias;
|
||||
json[r'rOffset'] = this.rOffset;
|
||||
json[r'rbBias'] = this.rbBias;
|
||||
json[r'rgBias'] = this.rgBias;
|
||||
json[r'rrBias'] = this.rrBias;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [FilterParameters] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static FilterParameters? fromJson(dynamic value) {
|
||||
upgradeDto(value, "FilterParameters");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return FilterParameters(
|
||||
bOffset: num.parse('${json[r'bOffset']}'),
|
||||
bbBias: num.parse('${json[r'bbBias']}'),
|
||||
bgBias: num.parse('${json[r'bgBias']}'),
|
||||
brBias: num.parse('${json[r'brBias']}'),
|
||||
gOffset: num.parse('${json[r'gOffset']}'),
|
||||
gbBias: num.parse('${json[r'gbBias']}'),
|
||||
ggBias: num.parse('${json[r'ggBias']}'),
|
||||
grBias: num.parse('${json[r'grBias']}'),
|
||||
rOffset: num.parse('${json[r'rOffset']}'),
|
||||
rbBias: num.parse('${json[r'rbBias']}'),
|
||||
rgBias: num.parse('${json[r'rgBias']}'),
|
||||
rrBias: num.parse('${json[r'rrBias']}'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<FilterParameters> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <FilterParameters>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = FilterParameters.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, FilterParameters> mapFromJson(dynamic json) {
|
||||
final map = <String, FilterParameters>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = FilterParameters.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of FilterParameters-objects as value to a dart map
|
||||
static Map<String, List<FilterParameters>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<FilterParameters>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = FilterParameters.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'bOffset',
|
||||
'bbBias',
|
||||
'bgBias',
|
||||
'brBias',
|
||||
'gOffset',
|
||||
'gbBias',
|
||||
'ggBias',
|
||||
'grBias',
|
||||
'rOffset',
|
||||
'rbBias',
|
||||
'rgBias',
|
||||
'rrBias',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15797,7 +15797,8 @@
|
||||
"enum": [
|
||||
"crop",
|
||||
"rotate",
|
||||
"mirror"
|
||||
"mirror",
|
||||
"filter"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15820,6 +15821,25 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetEditActionFilter": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditAction"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/components/schemas/FilterParameters"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"parameters"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetEditActionListDto": {
|
||||
"properties": {
|
||||
"edits": {
|
||||
@@ -15834,6 +15854,9 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionMirror"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionFilter"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -15902,6 +15925,9 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionMirror"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionFilter"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -17545,6 +17571,79 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FilterParameters": {
|
||||
"properties": {
|
||||
"bOffset": {
|
||||
"description": "B Offset (-255 -> 255)",
|
||||
"maximum": 255,
|
||||
"minimum": -255,
|
||||
"type": "number"
|
||||
},
|
||||
"bbBias": {
|
||||
"description": "BB Bias",
|
||||
"type": "number"
|
||||
},
|
||||
"bgBias": {
|
||||
"description": "BG Bias",
|
||||
"type": "number"
|
||||
},
|
||||
"brBias": {
|
||||
"description": "BR Bias",
|
||||
"type": "number"
|
||||
},
|
||||
"gOffset": {
|
||||
"description": "G Offset (-255 -> 255)",
|
||||
"maximum": 255,
|
||||
"minimum": -255,
|
||||
"type": "number"
|
||||
},
|
||||
"gbBias": {
|
||||
"description": "GB Bias",
|
||||
"type": "number"
|
||||
},
|
||||
"ggBias": {
|
||||
"description": "GG Bias",
|
||||
"type": "number"
|
||||
},
|
||||
"grBias": {
|
||||
"description": "GR Bias",
|
||||
"type": "number"
|
||||
},
|
||||
"rOffset": {
|
||||
"description": "R Offset (-255 -> 255)",
|
||||
"maximum": 255,
|
||||
"minimum": -255,
|
||||
"type": "number"
|
||||
},
|
||||
"rbBias": {
|
||||
"description": "RB Bias",
|
||||
"type": "number"
|
||||
},
|
||||
"rgBias": {
|
||||
"description": "RG Bias",
|
||||
"type": "number"
|
||||
},
|
||||
"rrBias": {
|
||||
"description": "RR Bias",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bOffset",
|
||||
"bbBias",
|
||||
"bgBias",
|
||||
"brBias",
|
||||
"gOffset",
|
||||
"gbBias",
|
||||
"ggBias",
|
||||
"grBias",
|
||||
"rOffset",
|
||||
"rbBias",
|
||||
"rgBias",
|
||||
"rrBias"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FoldersResponse": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
AssetEditAction action;
|
||||
|
||||
- MirrorParameters parameters;
|
||||
- FilterParameters parameters;
|
||||
+ Map<String, dynamic> parameters;
|
||||
|
||||
@override
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
return AssetEditActionListDtoEditsInner(
|
||||
action: AssetEditAction.fromJson(json[r'action'])!,
|
||||
- parameters: MirrorParameters.fromJson(json[r'parameters'])!,
|
||||
- parameters: FilterParameters.fromJson(json[r'parameters'])!,
|
||||
+ parameters: json[r'parameters'],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -637,14 +637,44 @@ export type AssetEditActionMirror = {
|
||||
action: AssetEditAction;
|
||||
parameters: MirrorParameters;
|
||||
};
|
||||
export type FilterParameters = {
|
||||
/** B Offset (-255 -> 255) */
|
||||
bOffset: number;
|
||||
/** BB Bias */
|
||||
bbBias: number;
|
||||
/** BG Bias */
|
||||
bgBias: number;
|
||||
/** BR Bias */
|
||||
brBias: number;
|
||||
/** G Offset (-255 -> 255) */
|
||||
gOffset: number;
|
||||
/** GB Bias */
|
||||
gbBias: number;
|
||||
/** GG Bias */
|
||||
ggBias: number;
|
||||
/** GR Bias */
|
||||
grBias: number;
|
||||
/** R Offset (-255 -> 255) */
|
||||
rOffset: number;
|
||||
/** RB Bias */
|
||||
rbBias: number;
|
||||
/** RG Bias */
|
||||
rgBias: number;
|
||||
/** RR Bias */
|
||||
rrBias: number;
|
||||
};
|
||||
export type AssetEditActionFilter = {
|
||||
action: AssetEditAction;
|
||||
parameters: FilterParameters;
|
||||
};
|
||||
export type AssetEditsDto = {
|
||||
assetId: string;
|
||||
/** list of edits */
|
||||
edits: (AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror)[];
|
||||
edits: (AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror | AssetEditActionFilter)[];
|
||||
};
|
||||
export type AssetEditActionListDto = {
|
||||
/** list of edits */
|
||||
edits: (AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror)[];
|
||||
edits: (AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror | AssetEditActionFilter)[];
|
||||
};
|
||||
export type AssetMetadataResponseDto = {
|
||||
key: string;
|
||||
@@ -5870,7 +5900,8 @@ export enum AssetJobName {
|
||||
export enum AssetEditAction {
|
||||
Crop = "crop",
|
||||
Rotate = "rotate",
|
||||
Mirror = "mirror"
|
||||
Mirror = "mirror",
|
||||
Filter = "filter"
|
||||
}
|
||||
export enum MirrorAxis {
|
||||
Horizontal = "horizontal",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { ClassConstructor, plainToInstance, Transform, Type } from 'class-transformer';
|
||||
import { ArrayMinSize, IsEnum, IsInt, Min, ValidateNested } from 'class-validator';
|
||||
import { ArrayMinSize, IsEnum, IsInt, IsNumber, Max, Min, ValidateNested } from 'class-validator';
|
||||
import { IsAxisAlignedRotation, IsUniqueEditActions, ValidateUUID } from 'src/validation';
|
||||
|
||||
export enum AssetEditAction {
|
||||
Crop = 'crop',
|
||||
Rotate = 'rotate',
|
||||
Mirror = 'mirror',
|
||||
Filter = 'filter',
|
||||
}
|
||||
|
||||
export enum MirrorAxis {
|
||||
@@ -48,6 +49,68 @@ export class MirrorParameters {
|
||||
axis!: MirrorAxis;
|
||||
}
|
||||
|
||||
// Sharp supports a 3x3 matrix for color manipulation and rgb offsets
|
||||
// The matrix representation of a filter is as follows:
|
||||
// | rrBias rgBias rbBias | | r_offset |
|
||||
// Image x | grBias ggBias gbBias | + | g_offset |
|
||||
// | brBias bgBias bbBias | | b_offset |
|
||||
|
||||
export class FilterParameters {
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'RR Bias' })
|
||||
rrBias!: number;
|
||||
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'RG Bias' })
|
||||
rgBias!: number;
|
||||
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'RB Bias' })
|
||||
rbBias!: number;
|
||||
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'GR Bias' })
|
||||
grBias!: number;
|
||||
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'GG Bias' })
|
||||
ggBias!: number;
|
||||
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'GB Bias' })
|
||||
gbBias!: number;
|
||||
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'BR Bias' })
|
||||
brBias!: number;
|
||||
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'BG Bias' })
|
||||
bgBias!: number;
|
||||
|
||||
@IsNumber()
|
||||
@ApiProperty({ description: 'BB Bias' })
|
||||
bbBias!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(-255)
|
||||
@Max(255)
|
||||
@ApiProperty({ description: 'R Offset (-255 -> 255)' })
|
||||
rOffset!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(-255)
|
||||
@Max(255)
|
||||
@ApiProperty({ description: 'G Offset (-255 -> 255)' })
|
||||
gOffset!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(-255)
|
||||
@Max(255)
|
||||
@ApiProperty({ description: 'B Offset (-255 -> 255)' })
|
||||
bOffset!: number;
|
||||
}
|
||||
|
||||
class AssetEditActionBase {
|
||||
@IsEnum(AssetEditAction)
|
||||
@ApiProperty({ enum: AssetEditAction, enumName: 'AssetEditAction' })
|
||||
@@ -74,6 +137,12 @@ export class AssetEditActionMirror extends AssetEditActionBase {
|
||||
@ApiProperty({ type: MirrorParameters })
|
||||
parameters!: MirrorParameters;
|
||||
}
|
||||
export class AssetEditActionFilter extends AssetEditActionBase {
|
||||
@ValidateNested()
|
||||
@Type(() => FilterParameters)
|
||||
@ApiProperty({ type: FilterParameters })
|
||||
parameters!: FilterParameters;
|
||||
}
|
||||
|
||||
export type AssetEditActionItem =
|
||||
| {
|
||||
@@ -87,25 +156,31 @@ export type AssetEditActionItem =
|
||||
| {
|
||||
action: AssetEditAction.Mirror;
|
||||
parameters: MirrorParameters;
|
||||
}
|
||||
| {
|
||||
action: AssetEditAction.Filter;
|
||||
parameters: FilterParameters;
|
||||
};
|
||||
|
||||
export type AssetEditActionParameter = {
|
||||
[AssetEditAction.Crop]: CropParameters;
|
||||
[AssetEditAction.Rotate]: RotateParameters;
|
||||
[AssetEditAction.Mirror]: MirrorParameters;
|
||||
[AssetEditAction.Filter]: FilterParameters;
|
||||
};
|
||||
|
||||
type AssetEditActions = AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror;
|
||||
type AssetEditActions = AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror | AssetEditActionFilter;
|
||||
const actionToClass: Record<AssetEditAction, ClassConstructor<AssetEditActions>> = {
|
||||
[AssetEditAction.Crop]: AssetEditActionCrop,
|
||||
[AssetEditAction.Rotate]: AssetEditActionRotate,
|
||||
[AssetEditAction.Mirror]: AssetEditActionMirror,
|
||||
[AssetEditAction.Filter]: AssetEditActionFilter,
|
||||
} as const;
|
||||
|
||||
const getActionClass = (item: { action: AssetEditAction }): ClassConstructor<AssetEditActions> =>
|
||||
actionToClass[item.action];
|
||||
|
||||
@ApiExtraModels(AssetEditActionRotate, AssetEditActionMirror, AssetEditActionCrop)
|
||||
@ApiExtraModels(AssetEditActionRotate, AssetEditActionMirror, AssetEditActionCrop, AssetEditActionFilter)
|
||||
export class AssetEditActionListDto {
|
||||
/** list of edits */
|
||||
@ArrayMinSize(1)
|
||||
|
||||
@@ -165,6 +165,38 @@ describe(MediaRepository.name, () => {
|
||||
// bottom-right should now be top-right (blue)
|
||||
expect(await getPixelColor(bufferVertical, 990, 990)).toEqual({ r: 0, g: 255, b: 0 });
|
||||
});
|
||||
|
||||
it('should apply filter edit correctly', async () => {
|
||||
const resultHorizontal = await sut['applyEdits'](sharp(await buildTestQuadImage()), [
|
||||
{
|
||||
action: AssetEditAction.Filter,
|
||||
parameters: {
|
||||
rrBias: 1,
|
||||
rgBias: 0.5,
|
||||
rbBias: 0.5,
|
||||
grBias: 0.5,
|
||||
ggBias: 1,
|
||||
gbBias: 0.5,
|
||||
brBias: 0.5,
|
||||
bgBias: 0.5,
|
||||
bbBias: 1,
|
||||
rOffset: 5,
|
||||
gOffset: 10,
|
||||
bOffset: 15,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const bufferHorizontal = await resultHorizontal.toBuffer();
|
||||
const metadataHorizontal = await resultHorizontal.metadata();
|
||||
expect(metadataHorizontal.width).toBe(1000);
|
||||
expect(metadataHorizontal.height).toBe(1000);
|
||||
|
||||
expect(await getPixelColor(bufferHorizontal, 10, 10)).toEqual({ r: 255, g: 137, b: 142 });
|
||||
expect(await getPixelColor(bufferHorizontal, 990, 10)).toEqual({ r: 132, g: 255, b: 142 });
|
||||
expect(await getPixelColor(bufferHorizontal, 10, 990)).toEqual({ r: 132, g: 137, b: 255 });
|
||||
expect(await getPixelColor(bufferHorizontal, 990, 990)).toEqual({ r: 255, g: 255, b: 255 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyEdits (multiple sequential edits)', () => {
|
||||
@@ -307,12 +339,29 @@ describe(MediaRepository.name, () => {
|
||||
expect(await getPixelColor(buffer, 490, 490)).toEqual({ r: 255, g: 0, b: 0 });
|
||||
});
|
||||
|
||||
it('should apply all operations: crop, rotate, mirror', async () => {
|
||||
it('should apply all operations: crop, rotate, mirror, filter', async () => {
|
||||
const imageBuffer = await buildTestQuadImage();
|
||||
const result = await sut['applyEdits'](sharp(imageBuffer), [
|
||||
{ action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 1000 } },
|
||||
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } },
|
||||
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
|
||||
{
|
||||
action: AssetEditAction.Filter,
|
||||
parameters: {
|
||||
rrBias: 1,
|
||||
rgBias: 0,
|
||||
rbBias: 0,
|
||||
grBias: 0,
|
||||
ggBias: 1,
|
||||
gbBias: 0,
|
||||
brBias: 0,
|
||||
bgBias: 0,
|
||||
bbBias: 1,
|
||||
rOffset: -10,
|
||||
gOffset: 20,
|
||||
bOffset: -30,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const buffer = await result.png().toBuffer();
|
||||
@@ -320,8 +369,8 @@ describe(MediaRepository.name, () => {
|
||||
expect(metadata.width).toBe(1000);
|
||||
expect(metadata.height).toBe(500);
|
||||
|
||||
expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 255, g: 0, b: 0 });
|
||||
expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 0, g: 0, b: 255 });
|
||||
expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 245, g: 20, b: 0 });
|
||||
expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 0, g: 20, b: 225 });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
TranscodeCommand,
|
||||
VideoInfo,
|
||||
} from 'src/types';
|
||||
import { convertColorFilterToMatricies } from 'src/utils/color_filter';
|
||||
import { handlePromiseError } from 'src/utils/misc';
|
||||
import { createAffineMatrix } from 'src/utils/transform';
|
||||
|
||||
@@ -167,6 +168,12 @@ export class MediaRepository {
|
||||
[c, d],
|
||||
]);
|
||||
|
||||
const filter = edits.find((edit) => edit.action === 'filter');
|
||||
if (filter) {
|
||||
const { biasMatrix, offsetMatrix } = convertColorFilterToMatricies(filter.parameters);
|
||||
pipeline = pipeline.recomb(biasMatrix).linear([1, 1, 1], offsetMatrix);
|
||||
}
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
|
||||
14
server/src/utils/color_filter.ts
Normal file
14
server/src/utils/color_filter.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Matrix3x3 } from 'sharp';
|
||||
import { FilterParameters } from 'src/dtos/editing.dto';
|
||||
|
||||
export function convertColorFilterToMatricies(filter: FilterParameters) {
|
||||
const biasMatrix: Matrix3x3 = [
|
||||
[filter.rrBias, filter.rgBias, filter.rbBias],
|
||||
[filter.grBias, filter.ggBias, filter.gbBias],
|
||||
[filter.brBias, filter.bgBias, filter.bbBias],
|
||||
];
|
||||
|
||||
const offsetMatrix = [filter.rOffset, filter.gOffset, filter.bOffset];
|
||||
|
||||
return { biasMatrix, offsetMatrix };
|
||||
}
|
||||
Reference in New Issue
Block a user