Editor image editing utility for the SuperShot feature. It applies transforms, color adjustments, filters, detail (sharpen/blur), vignette, film grain, border and a text watermark to a Bitmap according to EditSettings.
using System;
using Sandbox;
namespace Editor.SuperShot;
public static class SuperShotEdit
{
public static Bitmap Apply( Bitmap source, EditSettings edit )
{
if ( source is null )
return null;
var working = source.Clone();
try
{
working = ApplyTransform( working, edit );
ApplyColor( working, edit );
ApplyFilter( working, edit.Filter );
ApplyDetail( working, edit );
ApplyVignette( working, edit.Vignette );
ApplyGrain( working, edit.Grain );
working = ApplyBorder( working, edit );
ApplyWatermark( working, edit );
}
catch ( Exception e )
{
Log.Warning( $"[Supershot] Edit pass failed: {e.Message}" );
}
return working;
}
static Bitmap ApplyTransform( Bitmap bmp, EditSettings edit )
{
float degrees = edit.Rotate switch
{
ShotRotate.Cw90 => 90f,
ShotRotate.Half => 180f,
ShotRotate.Ccw90 => 270f,
_ => 0f
};
if ( degrees != 0f )
{
var rotated = bmp.Rotate( degrees );
bmp.Dispose();
bmp = rotated;
}
if ( edit.FlipH )
{
var f = bmp.FlipHorizontal();
bmp.Dispose();
bmp = f;
}
if ( edit.FlipV )
{
var f = bmp.FlipVertical();
bmp.Dispose();
bmp = f;
}
return bmp;
}
static void ApplyColor( Bitmap bmp, EditSettings edit )
{
float brightness = edit.Brightness * MathF.Pow( 2f, edit.Exposure );
bool changed = brightness != 1f || edit.Contrast != 1f || edit.Saturation != 1f || edit.Hue != 0f;
if ( changed )
bmp.Adjust( brightness, edit.Contrast, edit.Saturation, edit.Hue );
}
static void ApplyFilter( Bitmap bmp, ShotFilter filter )
{
switch ( filter )
{
case ShotFilter.Warm:
bmp.Tint( new Color( 1f, 0.92f, 0.82f ) );
break;
case ShotFilter.Cool:
bmp.Tint( new Color( 0.84f, 0.92f, 1f ) );
break;
case ShotFilter.Vintage:
bmp.Adjust( 1.02f, 0.92f, 0.7f, 0f );
bmp.Tint( new Color( 1f, 0.9f, 0.75f ) );
break;
case ShotFilter.Cinematic:
bmp.Adjust( 1f, 1.15f, 0.9f, 0f );
bmp.Tint( new Color( 0.92f, 0.97f, 1f ) );
break;
case ShotFilter.BlackAndWhite:
bmp.Adjust( 1f, 1.05f, 0f, 0f );
break;
case ShotFilter.HighContrast:
bmp.Adjust( 1f, 1.35f, 1.05f, 0f );
break;
}
}
static void ApplyDetail( Bitmap bmp, EditSettings edit )
{
if ( edit.Sharpen > 0f )
bmp.Sharpen( edit.Sharpen );
if ( edit.Blur > 0f )
bmp.Blur( edit.Blur );
}
static void ApplyVignette( Bitmap bmp, float intensity )
{
if ( intensity <= 0f )
return;
var center = bmp.Center;
float radius = MathF.Sqrt( center.x * center.x + center.y * center.y );
var g = new Gradient();
g.AddColor( 0f, Color.Black );
g.AddColor( 1f, Color.Black );
g.AddAlpha( 0f, 0f );
g.AddAlpha( 0.55f, 0f );
g.AddAlpha( 1f, MathX.Clamp( intensity, 0f, 1f ) );
bmp.SetRadialGradient( center, radius, g );
bmp.DrawRect( bmp.Rect );
}
static void ApplyGrain( Bitmap bmp, float intensity )
{
if ( intensity <= 0f )
return;
int nw = Math.Clamp( bmp.Width / 2, 8, 1024 );
int nh = Math.Clamp( bmp.Height / 2, 8, 1024 );
using var noise = new Bitmap( nw, nh );
var pixels = new Color[nw * nh];
float a = MathX.Clamp( intensity, 0f, 1f ) * 0.5f;
for ( int i = 0; i < pixels.Length; i++ )
{
float v = System.Random.Shared.Float( 0f, 1f );
pixels[i] = new Color( v, v, v, a );
}
noise.SetPixels( pixels );
bmp.SetAntialias( true );
bmp.DrawBitmap( noise, bmp.Rect );
}
static Bitmap ApplyBorder( Bitmap bmp, EditSettings edit )
{
if ( edit.Border <= 0 )
return bmp;
int b = Math.Min( edit.Border, Math.Min( bmp.Width, bmp.Height ) / 2 - 1 );
if ( b <= 0 )
return bmp;
var bordered = new Bitmap( bmp.Width, bmp.Height );
bordered.Clear( edit.BorderColor );
bordered.DrawBitmap( bmp, new Rect( b, b, bmp.Width - b * 2, bmp.Height - b * 2 ) );
bmp.Dispose();
return bordered;
}
static void ApplyWatermark( Bitmap bmp, EditSettings edit )
{
if ( !edit.Watermark || string.IsNullOrWhiteSpace( edit.WatermarkText ) )
return;
float margin = MathF.Max( 8f, edit.WatermarkSize * 0.4f );
var rect = new Rect( margin, margin, bmp.Width - margin * 2f, bmp.Height - margin * 2f );
var flags = edit.WatermarkAnchor switch
{
ShotAnchor.TopLeft => TextFlag.LeftTop,
ShotAnchor.TopRight => TextFlag.RightTop,
ShotAnchor.BottomLeft => TextFlag.LeftBottom,
ShotAnchor.BottomRight => TextFlag.RightBottom,
_ => TextFlag.Center
};
var scope = new TextRendering.Scope( edit.WatermarkText, edit.WatermarkColor, edit.WatermarkSize );
bmp.DrawText( scope, rect, flags );
}
}