1ã€ã®ã¹ã©ã€ããŒã3ã€ã®ã¬ã³ãã©ãŒïŒãã¬ãã¥ãŒã«ã¯GPUã·ã§ãŒããŒããã®ä»ã®å Žæã«ã¯Skiaã«ã©ãŒãããªãã¯ã¹ããšã¯ã¹ããŒãã«ã¯FFmpegã䜿çšããŠInstagram颚ãããªãã£ã«ã¿ãŒãæ§ç¯ããããããã¹ãŠãåãèŠèŠçã¹ããŒãªãŒãäŒããããã«ç¶æããæ¹æ³ã
ãã£ã«ã¿ãŒã¯ç°¡åã«èŠããããããªãã£ã«ã¿ãŒã¯éãã
Flutterã§ç»åã«ã»ãã¢ããŒã³ãé©çšããã®ã¯ãColorFilteredãŠã£ãžã§ããã§ã©ããããã ãã®1è¡ã®ã³ãŒãã§å®äºããŸããããããããããããªã«é©çšããã®ã¯å šãå¥ã®è©±ã§ãïŒ
- ãã¬ãã¥ãŒã¯ããŠãŒã¶ãŒãææããããããããã€ã¹ã§ãåçäžã®ãããªã®äžã«30ã60 fpsã§åäœããå¿ èŠããããŸãã
- ãŠãŒã¶ãŒã¯ã¹ã©ã€ããŒïŒæãããã³ã³ãã©ã¹ããè²çžãè²æž©åºŠãªã©ïŒããã©ãã°ããçµæããªã¢ã«ã¿ã€ã ã§ç¢ºèªã§ããå¿ èŠããããŸãã
- ãããŠå³ããç¹ãããŠãŒã¶ãŒããšã¯ã¹ããŒããã¿ããããéãçŒãä»ããããMP4ã¯ãã¬ãã¥ãŒã§èŠããã®ãšå šãåãã§ãªããã°ãªããŸããããããããã¬ãã¥ãŒã¬ã³ãã©ãŒïŒFlutter/GPUïŒãšãšã¯ã¹ããŒãã¬ã³ãã©ãŒïŒFFmpegïŒã¯ãäºãã«é£æºããããšã®ãªãå®å šã«ç°ãªããšã³ãžã³ã§ãã
ãã®æçš¿ã§ã¯ãFlutterãããªãšãã£ã¿ãŒã§ãã®ãã€ãã©ã€ã³ãæ§ç¯ããæ¹æ³ã«ã€ããŠèª¬æããŸããããªã»ãããšãã©ã¡ãŒã¿ãŒããšã®èª¿æŽãåãããã£ã«ã¿ãŒã«ã¿ãã°ãæãå®äŸ¡ãªã¬ã³ãã©ãŒãéžæããã©ã€ããã¬ãã¥ãŒããããŠãã®å€èгãåçŸããFFmpegãšã¯ã¹ããŒããã¹ã§ãããŸããæãéèŠãªæèšã¯æ¬¡ã®ãšããã§ãã
ãã¹ãŠã®ãã£ã«ã¿ãŒã¯æçµçã«ãGPUã·ã§ãŒããŒæ§æãSkia 4Ã5ã«ã©ãŒãããªãã¯ã¹ãããã³FFmpegãã£ã«ã¿ãŒã¹ããŒãã¡ã³ããšããŠ3åå®è£ ããããã¹ãŠ1ã€ã®å ±æãã©ã¡ãŒã¿ãŒãããã«ãã£ãŠé§åãããŸããããã3ã€ã®åå®è£ ã®èŠèŠçãªäžè²«æ§ãç¶æããããšãçã®ãšã³ãžãã¢ãªã³ã°ã§ãã
ã¢ãŒããã¯ãã£ïŒ1ã€ã®ãã©ã¡ãŒã¿ãŒãããã3ã€ã®ã¬ã³ãã©ãŒ
ââââââââââââââââââââââââââââââ
â å
±æãã©ã¡ãŒã¿ãŒããã â
â {filterId, params, 0â1 â
â 匷床} â
âââââââ¬ââââââââ¬ââââââââ¬âââââââ
â â â
ââââââââââââââââ â âââââââââââââââââ
⌠⌠âŒ
GPUã·ã§ãŒããŒãã¬ãã¥ãŒ Skia ColorFilter.matrix FFmpeg -vfæåå
(flutter_gpu_video_ (äž»èŠãªã©ã€ããã¬ãã¥ãŒ, (ãšã¯ã¹ããŒã â ãŠãŒã¶ãŒã
filters, ããŒã«ã«ã¯ãªãã) æ®éçãªãã©ãŒã«ããã¯) ä¿æããå¯äžã®ãã®)
3ã€ã®ãã¹ãååšããã®ã¯ãããããã«å©ç¹ãããããã§ãïŒ
- GPUã·ã§ãŒã㌠(flutter_gpu_video_filters) ã¯ãæªã¿ããŒãããããŒã³ãããã³ã°ãšãã£ãçã®ã·ã§ãŒããŒå質ã®ãšãã§ã¯ããæäŸããŸããããã®ããã±ãŒãžã¯ç¬èªã®ãµãŒãã§ã¹ã«ã¬ã³ããªã³ã°ããããŒã«ã«ãããªãã¡ã€ã«ã§æãããæ©èœããŸãã
- Skia ColorFilter.matrix â ColorFilteredã§ãããªãŠã£ãžã§ãããã©ãããã4x5ã«ã©ãŒãããªãã¯ã¹ â ã¯ã»ãŒç¡æã§ããããããããªãŠã£ãžã§ããïŒãããã¯ãŒã¯ã¹ããªãŒã ãå«ãïŒã§åäœããæãããã³ã³ãã©ã¹ããã»ãã¢ãã°ã¬ãŒã¹ã±ãŒã«ãè²çžããã¥ãªããŒã³ãªã©ãæããã䜿ããã20çš®é¡ä»¥äžã®ãã£ã«ã¿ãŒãã«ããŒããŸãã
- FFmpegã¯ããŠãŒã¶ãŒãå®éã«ä¿æããå¯äžã®ã¬ã³ãã©ãŒã§ãããšã¯ã¹ããŒãã¯ããã¹ãŠã®å€èгããã£ã«ã¿ãŒã°ã©ãæååïŒeq=ãhue=ãcolorchannelmixer=âŠïŒãšããŠåå°åºããŸãã
å°ããªã¹ãã©ããžãŒã«ãŒã¿ãŒãããã£ã«ã¿ãŒããšã«ã©ã®ãã¬ãã¥ãŒãšã³ãžã³ã䜿çšããããæ±ºå®ããŸãïŒ
// ãã®ãã£ã«ã¿ãŒãã¬ã³ããªã³ã°ã§ãããã¬ãã¥ãŒæŠç¥ã¯ã©ããïŒ
static FilterStrategy getFilterStrategy(String filterId) {
if (_colorFilterIds.contains(filterId)) return FilterStrategy.colorFilter;
if (_customPainterFilterIds.contains(filterId)) return FilterStrategy.customPainter;
return FilterStrategy.exportOnly;
}
- colorFilter â ãã¬ã€ã€ãŒãColorFiltered(colorFilter: getColorFilter(id, params))ã§ã©ãããã
- customPainter â ãããªã«CustomPainterãªãŒããŒã¬ã€ãéããïŒããããããã¯ã»ã«åãããŒãããŒã³ãªã©ãã«ã©ãŒãããªãã¯ã¹ã§ã¯è¡šçŸã§ããªããã®ïŒ
- exportOnly â å ã®ãããªãšããšãã§ã¯ãããšã¯ã¹ããŒãã«è¡šç€ºãããããšããŠãŒã¶ãŒã«ç¥ããããããžã衚瀺ãã
ãã®ã«ãŒã¿ãŒã¯ã·ã¹ãã å ã§æãè²»çšå¯Ÿå¹æã®é«ã決å®ã§ããçŽ24çš®é¡ã®ãã£ã«ã¿ãŒã«ãããããããã€ã¹ã§æ¬åœã«ç¡æã®ãªã¢ã«ã¿ã€ã ãã¬ãã¥ãŒãæäŸããå¿ èŠãªãã£ã«ã¿ãŒã®ããã«éãåŠçæ©æ§ã確ä¿ããŸãã
1. ãã£ã«ã¿ãŒã«ã¿ãã°ãããªã»ãããããã³èª¿æŽ
ã«ã¿ãã°å ã®åãã£ã«ã¿ãŒã¯ãIDã衚瀺åãã«ããŽãªããããŠïŒGPUãã¹ããµããŒãããŠããå ŽåïŒã·ã§ãŒããŒæ§æã®ããã®ãã¡ã¯ããªãšãããå°ããªå®£èšçã¢ã€ãã ã§ãïŒ
const GpuVideoFilterItem({
required this.id,
required this.name,
required this.icon,
required this.category,
this.createConfiguration, // () => GPUFilterConfiguration, GPU察å¿ã®å Žå
});
GPUFilterConfiguration? getConfiguration() => createConfiguration?.call();
ããªã»ããã¯ããã£ã«ã¿ãŒãæãååä»ããã©ã¡ãŒã¿ãŒãã³ãã«ãã€ãŸãUIã®ãP1ïœP5ãã¯ã€ãã¯ã«ãã¯ã§ãïŒ
static List<FilterPreset> get allPresets => [
const FilterPreset(id: 'P1', name: 'Warm Vintage',
filterId: 'sepia', parameters: {'intensity': 0.8}),
const FilterPreset(id: 'P2', name: 'Classic B&W',
filterId: 'grayscale', parameters: {'intensity': 1.0}),
const FilterPreset(id: 'P3', name: 'Vivid Colors',
filterId: 'vibrance', parameters: {'vibrance': 0.5}),
const FilterPreset(id: 'P4', name: 'High Contrast',
filterId: 'contrast', parameters: {'contrast': 1.4}),
const FilterPreset(id: 'P5', name: 'Cinematic',
filterId: 'vignette', parameters: {'vignetteStart': 0.3, 'vignetteEnd': 0.75}),
];
調æŽã¯ãç¯å²ãããã©ã«ãå€ãããã«ã¯ã°ã©ããŒã·ã§ã³ã®ãã³ããæã€åä»ãããããã©ã¡ãŒã¿ãŒèšè¿°åã§ããããã«ãããåã¹ã©ã€ããŒã¯æå³ã®ãããã©ãã¯ïŒæããã«ã¯é»âçœãè²çžã«ã¯è¹è²ïŒãã¬ã³ããªã³ã°ã§ããŸãïŒ
case 'brightness':
return [const FilterParameter(id: 'brightness', displayName: 'Brightness',
minValue: -1.0, maxValue: 1.0, defaultValue: 0.0,
gradientType: FilterParameterGradientType.blackToWhite)];
case 'contrast'
:
return [const FilterParameter(id: 'contrast', displayName: 'Contrast',
minValue: 0.5, maxValue: 2.0, defaultValue: 1.0,
gradientType: FilterParameterGradientType.grayToWhite)];
case 'hue'
:
return [const FilterParameter(id: 'hue', displayName: 'Hue',
minValue: -180.0, maxValue: 180.0, defaultValue: 0.0, unit: '°',
gradientType: FilterParameterGradientType.rainbow)];
çŸåšã®ã¹ã©ã€ããŒå€ã¯ãããã©ã«ãå€ããã·ãŒããããäžå€ã®ç¶æ ãªããžã§ã¯ãã«æ ŒçŽãããŸãïŒ
factory FilterAdjustmentState.fromDefaults(String filterId, List<FilterParameter> parameters) {
return FilterAdjustmentState(
filterId: filterId,
parameterValues: Map.fromEntries(parameters.map((p) => MapEntry(p.id, p.defaultValue))),
);
}
ãããŠãèŠãç®ä»¥äžã«éèŠãªUXã®è©³çްã1ã€ãããŸããã¹ã©ã€ããŒã®æŽæ°ã¯ããã«ç¶æ ã«æžã蟌ãŸããŸãïŒããã«ããããµã ãæã«è¿œåŸããŸãïŒãã**ãã¬ãã¥ãŒã®åã¬ã³ããªã³ã°ã¯300ããªç§ã§ãããŠã³ã¹ãããŸã**ããããŠã³ã¹ããªãå Žåãã¹ã©ã€ããŒããã©ãã°ãããšãã£ã«ã¿ãŒåŠçããããããªãµãããªãŒã1ç§éã«äœååãåæ§ç¯ããããã©ãã°ãéåããŠããŸããŸãããããŠã³ã¹ãããã°ãæãé ããªã£ãç¬éã«ãã¬ãã¥ãŒãæçµå€ã«ã¹ãããããŸãã
ã¯ãªããã«ä¿æããããã®ã¯æå³çã«æå°éã«æããããŠããŸãïŒ
class Filters {
final String? adjust; // æåã§éžæããããã£ã«ã¿ãŒIDãäŸïŒ 'sepia'
final String? presets; // ããªã»ããIDãäŸïŒ 'P1'
final double? intensity; // 0.0ïœ1.0ïŒnullã¯1.0ãæå³ããŸãïŒ1.0æªæºã®å Žåã®ã¿ä¿åïŒ
final Map<String, double>? parameters; // ãã£ã«ã¿ãŒããšã®ã¹ã©ã€ããŒå€
}
2. ã©ã€ããã¬ãã¥ãŒããã¹ããšã«
äž»åïŒ4Ã5ã«ã©ãŒãããªãã¯ã¹
ã«ã¿ãã°ã®ã»ãšãã©ã¯ã«ã©ãŒèšç®ã§ãããSkiaã«ã©ãŒãããªãã¯ã¹ã¯ã«ã©ãŒèšç®ãç¡æã§å®è¡ããŸãããã£ã«ã¿ãŒãããŒãžã£ãŒã¯ããã£ã«ã¿ãŒIDãšãã©ã¡ãŒã¿ãŒãColorFilter.matrixã«å€æããŸãïŒ
static ColorFilter getColorFilter(String filterId, [Map<String, double> parameters = const {}]) {
final matrix = _getColorMatrixWithParams(filterId, parameters);
return ColorFilter.matrix(matrix);
}
ãã©ã¡ãŒã¿ãŒåããããã£ã«ã¿ãŒã®ãããªãã¯ã¹ã¯åçã«æ§ç¯ãããŸãã2ã€ã®äŸã瀺ããŸãããããªãã¯ã¹ãªãã»ããã¯0-255ã«ã©ãŒã¹ããŒã¹ã«ååšããããã**Ã255**ã«æ³šæããŠãã ããïŒ
case 'brightness':
final brightness = (params['brightness'] ?? 0.0) * 255.0;
return [1,0,0,0,brightness, 0,1,0,0,brightness,
0,0,1,0,brightness, 0,0,0,1,0];
case 'contrast':
final contrast = params['contrast'] ?? 1.0;
final offset = -(0.5 * contrast) + 0.5;
return [contrast,0,0,0,offset*255, 0,contrast,0,0,offset*255,
0,0,contrast,0,offset*255, 0,0,0,1,0];
**ãã£ã«ã¿ãŒåŒ·åºŠ** â ã°ããŒãã«ãªããã®å€èгã®åºŠåããã¹ã©ã€ã㌠â ã¯ããšãã§ã¯ããããªãã¯ã¹ãšåäœè¡åãšã®éã®çŽæ¥è£éãšããŠå®è£ ãããŸãïŒ
final interpolatedMatrix = List<double>.generate(20, (i) {
return identityMatrix[i] + (effectMatrix[i] - identityMatrix[i]) * intensity;
});
ãã®1è¡ã®ã³ãŒãã«ããããã¹ãŠã®ã«ã©ãŒãã£ã«ã¿ãŒã«ã远å ã®ã·ã§ãŒããŒããã€ãã©ã€ã³äœæ¥ãªãã§ã0ïœ100%ã®åŒ·åºŠå¶åŸ¡ãç¡æã§æäŸãããŸãã
çã®GPUãã¹
ããŒã«ã«ã¯ãªããã®å Žåããšãã£ã¿ãŒã¯flutter_gpu_video_filtersããçã®GPUãã£ã«ã¿ãŒæžã¿ãµãŒãã§ã¹ã«åãæ¿ããŸãããŠã£ãžã§ããã«ããŒãèšå®ãããŠãããããã¯ãªãããŸãã¯ãã£ã«ã¿ãŒã倿ŽãããšããµãŒãã§ã¹å šäœãç Žæ£ãããåäœæãããŸãïŒããã±ãŒãžã®ã³ã³ãããŒã©ãŒã¯å®è¡äžã®ããããªæ§æå€æŽã奜ã¿ãŸããïŒïŒ
return GPUVideoSurfacePreview(
key: ValueKey(filterKey), // '${clipIndex}_${filterId}' â å®å
šãªåäœæã匷å¶
configuration: _gpuFilterConfiguration!,
onViewCreated: (controller, sizeStream) async {
_gpuPreviewController = controller;
if (_mainController != null && _isPlaying) {
_mainController!.pause(); // éè€åçïŒããã³åã«ãªã£ããªãŒãã£ãªïŒïŒãé¿ãã
}
await controller.setVideoSource(FileInputSource(File(clipInfo.assetPath!)));
if (mounted) setState(() => _isGpuPreviewInitialized = true);
},
);
ã·ã§ãŒããŒæ§æã¯ãããã±ãŒãžã®åä»ããããèšå®ãããã£ã«ã¿ãŒããšã«çµã¿ç«ãŠãããã¹ã©ã€ããŒãæžã蟌ãã®ãšåããã©ã¡ãŒã¿ãŒãããã«ãã£ãŠäŸçµŠãããŸãïŒ
case 'brightness': return GPUBrightnessConfiguration()..brightness = getParam('brightness', 0.0);
case 'contrast': return GPUContrastConfiguration()..contrast = getParam('contrast', 1.0);
case 'saturation':
return GPUSaturationConfiguration()..saturation = getParam('saturation', 1.0);
case 'hue': return GPUHueConfiguration()..hue = getParam('hue', 0.0);
case 'white_balance': return GPUWhiteBalanceConfiguration()..temperature = getParam('temperature', 5000.0);
ãããã°æéã§åŠãã 2ã€ã®ã©ã€ããµã€ã¯ã«ã«é¢ããæèšïŒ
- GPUãµãŒãã§ã¹ãéå§ããåã«ãåºã«ãªããã¬ãŒã€ãŒãäžæåæ¢ããŸãã GPUãã¬ãã¥ãŒã¯ãããªèªäœãåçããŸããäžæåæ¢ãå¿ãããšãåãã¯ãªãããåçãã2ã€ã®ãã³ãŒããŒãèµ·åãã**åã«ãªã£ãããããã«ããããªãŒãã£ãª**ãçºçããŸãã
- ãã£ã«ã¿ãŒå€æŽæã«é©åã«ç Žæ£ããŸãïŒå€ãæ§æãdisconnect()ããæ°ãããã¬ãã¥ãŒãã©ã¡ãŒã¿ãŒãäœæããæ°ãããã®ãconnect()ããç»é¢ãé¢ããéã«ã³ã³ãããŒã©ãŒãdispose()ããŸãïŒããã«ãµã€ãºã¹ããªãŒã ã®è³Œèªããã£ã³ã»ã«ããŸãïŒã
ãã¹ã¯ãããã§ã®äºæãã¬åé¡
ãã¹ã¯ãããã§ã¯ããããªããã¯ãšã³ãã¯Skiaã®ã¬ã€ã€ãŒåæããã€ãã¹ãã**å€éšãã¯ã¹ãã£**ã«ã¬ã³ããªã³ã°ããŸãããã®ãããColorFilteredã§ãã¬ãŒã€ãŒãã©ããããŠããé»ã£ãŠäœãèµ·ãããŸããã倿ããSkiaã¬ã€ã€ãŒããªãããã§ããåé¿çã¯ãåºã«ãªãã¬ã€ã€ãŒã®ã©ã¹ã¿ã©ã€ãºã匷å¶ããããšïŒäŸãã°ãSizedBox.expandäžã«BackdropFilterãæ¿å ¥ãããªã©ïŒã§ãã«ã©ãŒãããªãã¯ã¹ãå®éã«æäœãããã¯ã»ã«ãæãŠãããã«ããããšã§ãããAndroidã§ã¯ãã£ã«ã¿ãŒãåäœãããããã¹ã¯ãããã§ã¯åäœããªããå Žåãããã¯ã»ãŒééããªããã®çç±ã§ãã
3. ãšã¯ã¹ããŒãïŒFFmpegã§ã®å€èгã®åæ§ç¯
ãã¬ãã¥ãŒã¯äžæçãªãã®ã§ããããšã¯ã¹ããŒãã¯æ°žç¶çãªãã®ã§ãããšã¯ã¹ããŒãæãä¿åãããFiltersã¢ãã«ã解決ããïŒæåã§éžæããããã£ã«ã¿ãŒãããªã»ãããããåªå ãããããªã»ããIDã¯å¯Ÿå¿ãããã£ã«ã¿ãŒãšãã©ã¡ãŒã¿ãŒã«ãããã³ã°ãçŽãããŸãïŒãFFmpegãã£ã«ã¿ãŒã¹ããŒãã¡ã³ãã«å€æãããŸãïŒ
switch (normalizedId) {
case 'grayscale': return 'hue=s=0';
case 'sepia': return 'colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131';
case 'invert': return 'negate';
case 'brightness':
final b = params?['brightness'] ?? 0.3; return 'eq=brightness=${b.toStringAsFixed(3)}';
case 'contrast':
final c = params
?['contrast'] ?? 1.5; return 'eq=contrast=${c.toStringAsFixed(3)}';
case 'saturation':
final s = params?['saturation'] ?? 1.5; return 'eq=saturation=${s.toStringAsFixed(3)}';
case 'exposure':
final e = params?['exposure'] ?? 0.4; // FFmpeg eqã«ã¯exposureããªã â gammaã§åœè£
final gamma = e >= 0 ? (1.0 - e * 0.5).clamp(0.1, 10.0)
: (1.0 / (1.0 + (-e) * 0.5)).clamp(0.1, 10.0);
return 'eq=gamma=${gamma.toStringAsFixed(3)}';
case 'hue':
final h = params?['hue'] ?? 90.0; return 'hue=h=${h.toStringAsFixed(1)}';
case 'gaussian_blur':
final sigma = (params?['sigma'] ?? 5.0).clamp(
0.1, 50.0);
return 'gblur=sigma=${sigma.toStringAsFixed(1)}';
case 'vignette': return "vignette='PI/4'";
// ã¹ã¯ãŒã« / ãã«ãž / ãã¥ãŒã³ / ã¯ã¯ãã© / ã¯ãã¹ããã ... â ''ãè¿ãïŒãã¬ãã¥ãŒã®ã¿ïŒ
}
ãšã¯ã¹ããŒãæã®åŒ·åºŠïŒåå²/ãã¬ã³ãã®ããªãã¯
ãã¬ãã¥ãŒã§ã¯ãã«ã©ãŒãããªãã¯ã¹ãåäœè¡åã«åããŠç·åœ¢è£éããããšã§åŒ·åºŠãå®è£ ããããšãæãåºããŠãã ãããFFmpegã«ã¯ããããªãã¯ã¹ç·åœ¢è£éãã¯ãããŸããããã¹ããªãŒã åææ©èœããããŸãããã®ãããéšåçãªåŒ·åºŠã§ã®ãšã¯ã¹ããŒãã¯ã**ãããªãåå²ããäžæ¹ã®ãã©ã³ãããã£ã«ã¿ãŒåŠçããä¿åãããäžéæåºŠã§å ã®ãããªã®äžã«ãã¬ã³ããçŽããŸã**ïŒ
if (intensity >= 0.99) {
command = ['-i','"$videoPath"','-vf', filterCommand,
'-c:v','libx264','-preset',options.preset,'-crf','${options.crf}',
'-c:a','copy','-movflags','+faststart','-y','"$outputPath"'].join(' ');
} else {
final opacity = intensity.toStringAsFixed(2);
command = ['-i','"$videoPath"','-filter_complex',
'[0:v]split[orig][tofilter];'
'[tofilter]$filterCommand[filtered];'
'[orig][filtered]blend=all_mode=normal:all_opacity=$opacity[out]',
'-map','[out]','-c:v','libx264','-preset',options.preset,'-crf','${options.crf}',
'-c:a','copy','-movflags','+faststart','-y','"$outputPath"'].join(' ');
}
ããã¯ãããªãã¯ã¹ç·åœ¢è£éãšæ°åŠçã«åäžã§ã¯ãããŸããããç¥èŠçã«ã¯è¿ãçµæãåŸãããŸãããŸããã«ã©ãŒãããªãã¯ã¹ã ãã§ãªãããããããã£ã«ã¿ãŒã¹ããŒãã¡ã³ãã§æ©èœããŸãã
ãã£ã«ã¿ãŒãã¹ã¯ãããé·ããšã¯ã¹ããŒããã§ãŒã³ïŒãã£ã³ãã¹ãã£ãã â **ãã£ã«ã¿ãŒ** â ãšãã§ã¯ã â ãªãŒããŒã¬ã€ â ãªãŒãã£ãªããã¯ã¹ â é£çµïŒã«ãããç¬ç«ããåãšã³ã³ãŒãã®1ã€ã§ãããªãŒãã£ãªã¯å°çšã®ããã·ã³ã°ã¹ããŒãžãŸã§-c:a copyã«ãã£ãŠå€æŽãããã«ä¿æãããæ©æã®ãããæ€åºããããã«ãããããæ®µéã§ãœãŒã¹ã®ãã¥ã¬ãŒã·ã§ã³ããããŒããããŸãã
4. ããªãã£ããŒãã«ïŒ3ã€ã®ã¬ã³ãã©ãŒãäžèŽããç¹ãšããªãç¹
ããã¯èª°ããè§Šããªãéšåã§ãã1ã€ã®ä¿åãããå€ã3ã€ã®è§£éïŒ
| ãã©ã¡ãŒã¿ãŒ | ã¹ã©ã€ããŒç¯å² | GPUã·ã§ãŒã㌠| Skiaãããªãã¯ã¹ | FFmpegãšã¯ã¹ããŒã |
|---|---|---|---|---|
| æãã | â1 ⊠1 | ãã€ãã£ã â1âŠ1 | ãªãã»ãã = v à 255 | eq=brightness=v |
| ã³ã³ãã©ã¹ã | 0.5 ⊠2 | ãã€ãã£ã | 察è§ç· vããªãã»ããåäžå€®å | eq=contrast=v |
| 圩床 | 0 ⊠2 | ãã€ãã£ã | Rec.709èŒåºŠå éãã¬ã³ã | eq=saturation=v |
| é²åº | â1 ⊠1 | ãªãã¢ã²ã€ã³ | ãªãã¢ã²ã€ã³ | ã¬ã³ãã«ãŒãã§åœè£ |
| è²çž | â180° ⊠180° | ãã€ãã£ã | å®å šãªcos/sinå転ãããªãã¯ã¹ | hue=h=v |
| ã»ã㢠| 匷床 | ã·ã§ãŒã㌠| ãããªãã¯ã¹ .393/.769/.189⊠| colorchannelmixer â åãä¿æ°ãå®å šãªããªã㣠|
| 匷床 | 0 ⊠1 | ããªã»ãã乿° | ãããªãã¯ã¹ â åäœè¡å ç·åœ¢è£é | åå² + blend=all_opacity |
èŠåŽããŠåŸãããªãã£ã®æèšïŒ
- 1ã€ã®ãã£ã«ã¿ãŒãççå€ãšããŠéžæããŠãã ããã ã»ãã¢ã¯ãSkiaãããªãã¯ã¹ãšFFmpegã®colorchannelmixerã®äž¡æ¹ã«åäžã®3x3ä¿æ°ãçŸãããããæ£ç¢ºãªããªãã£ãéæããŸãããŸããããäœæãããã€ãã©ã€ã³ã®ç«¯ãã端ãŸã§ãæ€èšŒããããã«äœ¿çšããŠãã ããã
- åäœã®äžäžèŽã«æ³šæããŠãã ããã Skiaã®æãããªãã»ããã¯0ïœ255ã®ç©ºéã§ãããGPUã·ã§ãŒããŒã¯-1ïœ1ã®ç¯å²ã§åäœããŸããæå³çã«æ£èŠåããªãéããåãã¹ã©ã€ããŒå€ã§ãäžæ¹ã®ãã¹ã§ã¯åçã«åŒ·ã广ãåºãŸãã
- ããã€ãã®ãããã³ã°ã¯è¿äŒŒã§ãããããã¯æ±ºå®äºé ã§ãã FFmpegã®eqã«ã¯é²åºå¶åŸ¡ããªãããããšã¯ã¹ããŒãã¯éã¬ã³ãã«ãŒãã§é²åºãè¿äŒŒããŸãããªãã¢ã²ã€ã³ãã¬ãã¥ãŒãšã¬ã³ãã«ãŒããšã¯ã¹ããŒãã¯ã·ã£ããŠéšåã§ä¹é¢ããŸãã蚱容ã§ãããïŒãããã â ããããæèçã«æ±ºå®ããèšé²ããŠããã¹ãã§ãã
- éãã«å£åãããã©ã¡ãŒã¿ãŒã«æ³šæããŠãã ããã åæããŒãžã§ã³ã§ã¯ããã¯ã€ããã©ã³ã¹ã®Kelvinã¹ã©ã€ããŒã¯GPUãã¬ãã¥ãŒãçŸããé§åããŸãããããšã¯ã¹ããŒãã§ã¯ããŒãã³ãŒããããè²æž©åºŠãåºåãããŸããããŠãŒã¶ãŒã¯æçµçãªãããªã«äœã圱é¿ããªãã¹ã©ã€ããŒããã©ãã°ããŠããã®ã§ãã3ã€ã®ãã¹ãã¹ãŠã§åãã©ã¡ãŒã¿ãŒãç£æ»ããŠãã ããã
- ãã¬ãã¥ãŒã®ã¿ã®ãã£ã«ã¿ãŒã¯ãã®æšãæèšããå¿ èŠããããŸãã æªã¿ãæ§åŒåãšãã§ã¯ãïŒã¹ã¯ãŒã«ããã«ãžããã¥ãŒã³ãã¯ã¯ãã©ãªã©ïŒã¯GPUã·ã§ãŒããŒãšããŠã®ã¿ååšãããããã®ãšã¯ã¹ããŒã倿ã¯ç©ºã®æååãè¿ãããã€ãã©ã€ã³ã¯å ã®ãããªããã®ãŸãŸééãããŸããUIã¯ãããæ£çŽã«é瀺ããªããã°ãªããŸããããã¬ãã¥ãŒãšãšã¯ã¹ããŒãã®éã§é»ç€ºçãªäžäžèŽããããšããŠãŒã¶ãŒã®ä¿¡é Œã倱ãæéã®æ¹æ³ãšãªããŸãã
5. ãããã¯ã·ã§ã³ããã®ããã©ãŒãã³ã¹ã«é¢ããã¡ã¢
- ãããªãã¯ã¹ã§ã¬ã³ããªã³ã°ã§ãããã®ãGPUãã£ã«ã¿ãŒããªãã§ãã ããã ã¹ãã©ããžãŒã«ãŒã¿ãŒã¯ãColorFilteredãä»ããŠçŽ24çš®é¡ã®ã«ã©ãŒãã£ã«ã¿ãŒãåŠçããŸããããã¯äºå®äžç¡æã§ããããã¯ãŒã¯ã¹ããªãŒã ã§åäœããSkiaãåæããããããå Žæã§æ©èœããŸããGPUãµãŒãã§ã¹ã¯ãããŒã«ã«ãã¡ã€ã«ãšã·ã§ãŒããŒã®ã¿ã®ãšãã§ã¯ãã®ããã«äºçŽãããŠããŸãã
- GPUãµãŒãã§ã¹ã«ããŒãèšå®ãããã¥ãŒããŒãããªãã§ãã ããã ValueKey('${clipIndex}_${filterId}')ãšå®å šãªåäœæã¯ãã©ã€ãã³ã³ãããŒã©ãŒäžã§ã·ã§ãŒããŒæ§æããããã¹ã¯ããããããšãããããåªããŠããŸãã
- ã¹ã©ã€ããŒããããŠã³ã¹ããïŒçŽ300ããªç§ïŒ â ã¬ã¹ãã³ã·ããªãµã ã®ããã«ç¶æ ãããã«æŽæ°ãããã£ã«ã¿ãŒããããã¬ãã¥ãŒã¯é å»¶ã¬ã³ããªã³ã°ããŸãã
- å¿ èŠã§ãªãéãããã£ã«ã¿ãŒããšã®ãµã ãã€ã«ã¬ã³ããªã³ã°ã¯ã¹ãããããŠãã ããã ç§ãã¡ã®ãã£ã«ã¿ãŒã«ã«ãŒã»ã«ã¯ã30æã®ãã£ã«ã¿ãŒæžã¿ãããªãµã ãã€ã«ã®ä»£ããã«ã¢ã€ã³ã³ã¿ã€ã«ã䜿çšããŠããŸãããã£ã«ã¿ãŒããšãã¯ãªããããšã«å®éã®ãã£ã«ã¿ãŒæžã¿ãã¬ãã¥ãŒãçæããŠãã£ãã·ã¥ããããšã¯ãäœã¹ããã¯ããã€ã¹ã§ã¯ãããããªUXåäžã«å¯ŸããŠèŠããã«ããã倧ããªã³ã¹ããšãªããŸãã
- çã«ä¿æããŠãã ããã 匷床ã¯1.0æªæºã®å Žåã®ã¿æžã蟌ãŸããnullã¯ãå®å šãªåŒ·åºŠããæå³ããŸãããã®ãããªå°ããªéžæãããããžã§ã¯ãã«å€æ°ã®ã¯ãªãããããå Žåã«ã¯ãªããããšã®JSONã軜éã«ä¿ã¡ãŸãã
åŠãã æèš
- åäžã®ããã£ã«ã¿ãŒå®è£ ãã¯ååšããŸããã ãã¬ãã¥ãŒãšãšã¯ã¹ããŒããç°ãªããšã³ãžã³ã§ããããšãåãå ¥ãã1ã€ã®æšæºçãªãã©ã¡ãŒã¿ãŒããããèšèšãããã¹ãŠã®ã¬ã³ãã©ãŒããã®æåœ±ãšããŠæ±ã£ãŠãã ããã
- æ©èœã«ãã£ãŠã«ãŒãã£ã³ã°ããŠãã ããã 3段éã®æŠç¥ïŒã«ã©ãŒãããªãã¯ã¹ â ã«ã¹ã¿ã ãã€ã³ã¿ãŒ â GPU/ãšã¯ã¹ããŒãã®ã¿ïŒã«ãããã»ãšãã©ã®ãã£ã«ã¿ãŒã«ç¡æã®ãªã¢ã«ã¿ã€ã ãã¬ãã¥ãŒãæäŸãã䟡å€ã®ãããšãã§ã¯ãã®ããã«é«äŸ¡ãªãã¹ã確ä¿ããŸãã
- 匷床ã¯ãããªãããããŸã§ãªãªãŒã¹ããäžã§æãå®äŸ¡ãªãã¬ãã¢ã æ©èœã§ã â ãã¬ãã¥ãŒã§ã¯ãããªãã¯ã¹ç·åœ¢è£éãFFmpegã§ã¯åå²/ãã¬ã³ãã§ãã
- ããªãã£ã¯ãã¹ããµãŒãã§ã¹ã§ãã åããã©ã¡ãŒã¿ãŒã䜿çšããŠåãã¹ã§ãã¬ãŒã ãã¬ã³ããªã³ã°ããæ¯èŒããŠãã ãããããªããèŠã€ããäžäžèŽïŒåäœãã¬ã³ããšã²ã€ã³ãããŒãã³ãŒãããã宿°ãªã©ïŒã¯ããŠãŒã¶ãŒãããªãã®ããã«èŠã€ããŠãããã§ããããã®ãšå šãåãã§ãã
- UIã§æ£çŽã«ãªã£ãŠãã ããã ãã£ã«ã¿ãŒããšã¯ã¹ããŒãæã«ã®ã¿ååšããå ŽåããŸãã¯ãã¬ãã¥ãŒæã«ã®ã¿ååšããå Žåããã®æšãæèšããŠãã ãããä¿¡é Œã¯æ©èœã®æ¬ èœã«ã¯èããããŸãããäºæãã¬äºæ ã«ã¯èããããŸããã
ãã®çµæãå®äŸ¡ãªAndroidã¹ããŒããã©ã³ãããã¹ã¯ããããã«ããŸã§ãããããã®ã§ã©ã€ããã¬ãã¥ãŒãå³åº§ã®ãã£ãŒãããã¯ã䌎ãã¹ã©ã€ããŒã¬ãã«ã®èª¿æŽãååä»ãããªã»ããããããŠãŠãŒã¶ãŒãèŠããã®ãšåãããã«èŠãããšã¯ã¹ããŒããæäŸãããã£ã«ã¿ãŒã·ã¹ãã ã宿ããŸãããããã¯ã1ã€ã®ãã©ã¡ãŒã¿ãŒããããšãæ³šææ·±ã調æŽããã3ã€ã®ã¬ã³ãã©ãŒããæ§ç¯ãããŠããŸãã

