2019-01-24 08:43:03 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
using System ;
using System.IO ;
2018-04-13 12:13:09 +00:00
using System.Threading ;
using System.Threading.Tasks ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Allocation ;
using osu.Framework.Audio ;
using osu.Framework.Audio.Sample ;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables ;
2020-02-06 20:02:03 +00:00
using osu.Framework.Graphics ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Input ;
using osu.Framework.Input.Bindings ;
using osu.Framework.Platform ;
2018-04-13 12:13:09 +00:00
using osu.Framework.Threading ;
2018-04-13 09:19:50 +00:00
using osu.Game.Configuration ;
using osu.Game.Input.Bindings ;
using osu.Game.Overlays ;
using osu.Game.Overlays.Notifications ;
2018-08-17 05:30:44 +00:00
using SixLabors.ImageSharp ;
2020-07-24 06:00:18 +00:00
using SixLabors.ImageSharp.Formats.Jpeg ;
2018-04-13 09:19:50 +00:00
namespace osu.Game.Graphics
{
2020-02-06 20:22:30 +00:00
public class ScreenshotManager : Component , IKeyBindingHandler < GlobalAction > , IHandleGlobalKeyboardInput
2018-04-13 09:19:50 +00:00
{
2018-04-13 12:13:09 +00:00
private readonly BindableBool cursorVisibility = new BindableBool ( true ) ;
/// <summary>
2018-04-13 12:15:08 +00:00
/// Changed when screenshots are being or have finished being taken, to control whether cursors should be visible.
2018-04-13 12:13:09 +00:00
/// If cursors should not be visible, cursors have 3 frames to hide themselves.
/// </summary>
public IBindable < bool > CursorVisibility = > cursorVisibility ;
2018-04-13 09:19:50 +00:00
private Bindable < ScreenshotFormat > screenshotFormat ;
2018-04-13 12:13:09 +00:00
private Bindable < bool > captureMenuCursor ;
2020-02-14 13:14:00 +00:00
[Resolved]
private GameHost host { get ; set ; }
2018-04-13 09:19:50 +00:00
private Storage storage ;
2020-02-14 13:14:00 +00:00
[Resolved]
private NotificationOverlay notificationOverlay { get ; set ; }
2018-04-13 09:19:50 +00:00
2021-01-19 08:11:40 +00:00
private Sample shutter ;
2018-04-13 09:19:50 +00:00
[BackgroundDependencyLoader]
2020-02-14 13:14:00 +00:00
private void load ( OsuConfigManager config , Storage storage , AudioManager audio )
2018-04-13 09:19:50 +00:00
{
this . storage = storage . GetStorageForDirectory ( @"screenshots" ) ;
screenshotFormat = config . GetBindable < ScreenshotFormat > ( OsuSetting . ScreenshotFormat ) ;
2018-04-13 12:13:09 +00:00
captureMenuCursor = config . GetBindable < bool > ( OsuSetting . ScreenshotCaptureMenuCursor ) ;
2018-04-13 09:19:50 +00:00
2019-05-28 08:06:01 +00:00
shutter = audio . Samples . Get ( "UI/shutter" ) ;
2018-04-13 09:19:50 +00:00
}
public bool OnPressed ( GlobalAction action )
{
switch ( action )
{
case GlobalAction . TakeScreenshot :
shutter . Play ( ) ;
TakeScreenshotAsync ( ) ;
return true ;
}
return false ;
}
2020-01-22 04:22:34 +00:00
public void OnReleased ( GlobalAction action )
{
}
2018-04-13 09:19:50 +00:00
2018-04-13 12:13:09 +00:00
private volatile int screenShotTasks ;
2018-08-29 11:57:48 +00:00
public Task TakeScreenshotAsync ( ) = > Task . Run ( async ( ) = >
2018-04-13 09:19:50 +00:00
{
2018-04-13 12:13:09 +00:00
Interlocked . Increment ( ref screenShotTasks ) ;
if ( ! captureMenuCursor . Value )
{
cursorVisibility . Value = false ;
// We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value
const int frames_to_wait = 3 ;
int framesWaited = 0 ;
2019-09-14 14:08:56 +00:00
using ( var framesWaitedEvent = new ManualResetEventSlim ( false ) )
{
ScheduledDelegate waitDelegate = host . DrawThread . Scheduler . AddDelayed ( ( ) = >
{
2020-01-27 03:16:00 +00:00
if ( framesWaited + + > = frames_to_wait )
2019-09-14 14:08:56 +00:00
// ReSharper disable once AccessToDisposedClosure
framesWaitedEvent . Set ( ) ;
} , 10 , true ) ;
framesWaitedEvent . Wait ( ) ;
waitDelegate . Cancel ( ) ;
}
2018-04-13 12:13:09 +00:00
}
2021-03-08 03:57:16 +00:00
using ( var image = await host . TakeScreenshotAsync ( ) . ConfigureAwait ( false ) )
2018-04-13 09:19:50 +00:00
{
2019-07-31 22:35:42 +00:00
if ( Interlocked . Decrement ( ref screenShotTasks ) = = 0 & & cursorVisibility . Value = = false )
cursorVisibility . Value = true ;
2018-04-13 12:13:09 +00:00
2018-04-13 09:19:50 +00:00
var fileName = getFileName ( ) ;
if ( fileName = = null ) return ;
var stream = storage . GetStream ( fileName , FileAccess . Write ) ;
switch ( screenshotFormat . Value )
{
case ScreenshotFormat . Png :
2021-03-08 03:57:16 +00:00
await image . SaveAsPngAsync ( stream ) . ConfigureAwait ( false ) ;
2018-04-13 09:19:50 +00:00
break ;
2019-04-01 03:44:46 +00:00
2018-04-13 09:19:50 +00:00
case ScreenshotFormat . Jpg :
2020-07-24 06:26:45 +00:00
const int jpeg_quality = 92 ;
2021-03-08 03:57:16 +00:00
await image . SaveAsJpegAsync ( stream , new JpegEncoder { Quality = jpeg_quality } ) . ConfigureAwait ( false ) ;
2018-04-13 09:19:50 +00:00
break ;
2019-04-01 03:44:46 +00:00
2018-04-13 09:19:50 +00:00
default :
2019-11-28 14:21:21 +00:00
throw new InvalidOperationException ( $"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}." ) ;
2018-04-13 09:19:50 +00:00
}
notificationOverlay . Post ( new SimpleNotification
{
Text = $"{fileName} saved!" ,
Activated = ( ) = >
{
storage . OpenInNativeExplorer ( ) ;
return true ;
}
} ) ;
}
2018-04-13 12:13:09 +00:00
} ) ;
2018-04-13 09:19:50 +00:00
private string getFileName ( )
{
var dt = DateTime . Now ;
2018-07-25 05:37:05 +00:00
var fileExt = screenshotFormat . ToString ( ) . ToLowerInvariant ( ) ;
2018-04-13 09:19:50 +00:00
var withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}" ;
if ( ! storage . Exists ( withoutIndex ) )
return withoutIndex ;
for ( ulong i = 1 ; i < ulong . MaxValue ; i + + )
{
var indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}" ;
if ( ! storage . Exists ( indexedName ) )
return indexedName ;
}
return null ;
}
}
}