Skip to content

Add Effects to System.Drawing #8835

@reflectronic

Description

@reflectronic

Background

A major addition in GDI+1.1 was the addition of effects. GDI+ comes with eleven effect types which perform various adjustments and transformations on images, like blurs, color balancing, brightness/contrast adjustments, etc. This proposal is one of many to add missing GDI+ 1.1 functionality to System.Drawing.

API Proposal

Updated proposal: #8835 (comment)

See the documentation for the gdipluseffects.h header and the corresponding GDI+ flat APIs.

+namespace System.Drawing.Effects
+{
+   public abstract class Effect : IDisposable
+   {
+       public void Dispose();
+
+       public bool UseAuxiliaryData { get; set; }
+
!       I know this is a bit of a mouthful - see below for details.
+       public ColorLookupTable GetColorLookupTableFromAuxiliaryData();
+   }

!   These APIs deviate slightly from the native definitions. See below.
+   public sealed class BlurEffect : Effect
+   {
+       public BlurEffect();
+
+       public float Radius { get; set; }
+       public bool ExpandEdge { get; set; }
+   }

+   public sealed class BrightnessContrastEffect : Effect
+   {
+       public BrightnessContrastEffect();
+
+       public int BrightnessLevel { get; set; }
+       public bool ContrastLevel { get; set; }
+   }

+   public sealed class ColorBalanceEffect : Effect
+   {
+       public ColorBalanceEffect();

+       public int CyanRed { get; set; }
+       public int MagentaGreen { get; set; }
+       public int YellowBlue { get; set; }
+   }

+   public sealed class ColorCurveEffect : Effect
+   {
+       public ColorCurveEffect();

+       public CurveAdjustments Adjustment { get; set; }
+       public CurveChannel Channel { get; set; }
+       public int AdjustmentValue { get; set; }
+   }

+   public enum CurveAdjustments
+   {
+       Exposure,
+       Density,
+       Contrast,
+       Highlight,
+       Shadow,
+       Midtone,
+       WhiteSaturation,
+       BlackSaturation
+   }

+   public enum CurveChannel
+   {
+       All,
+       Red,
+       Green,
+       Blue
+   }

+   public sealed class ColorLookupTableEffect : Effect
+   {
+       public ColorLookupTableEffect();
+
+       public ColorLookupTable LookupTable { get; set; }
+   }

+   public sealed class ColorLookupTable
+   {
+       public ColorLookupTable(byte[] blue, byte[] green, byte[] red, byte[] alpha);
+
+       public byte[] Blue { get; }
+       public byte[] Green { get; }
+       public byte[] Red { get; }
+       public byte[] Alpha { get; }
+   }

+   public sealed class HueSaturationLightnessEffect : Effect
+   {
+       public HueSaturationLightnessEffect();
+
+       public int HueLevel { get; set; }
+       public int SaturationLevel { get; set; }
+       public int LightnessLevel { get; set; }
+   }

+   public sealed class LevelsEffect : Effect
+   {
+       public LevelsEffect();
+
+       public int Highlight { get; set; }
+       public int Midtone { get; set; }
+       public int Shadow { get; set; }
+   }

+   public sealed class RedEyeCorrectionEffect : Effect
+   {
+       public RedEyeCorrectionEffect();
+
+       public Rectangle[] Areas { get; set; }
+   }

+   public sealed class SharpenEffect : Effect
+   {
+       public SharpenEffect();
+
+       public float Radius { get; set; }
+       public float Amount { get; set; }
+   }

+   public sealed class TintEffect : Effect
+   {
+       public TintEffect();
+
+       public int Hue { get; set; }
+       public int Amount { get; set; }
+   }
+} 

namespace System.Drawing
{
    public sealed class Bitmap : Image
    {
!       These overloads are duplicated to match the older API design found in System.Drawing (before Nullable and default parameters).
!       If this is undesirable, each overload pair can be combined into one which takes a Nullable<Rectangle>.
+       public void ApplyEffect(Effect effect);	
+       public void ApplyEffect(Effect effect, Rectangle srcRect);	
+       public static Bitmap[] ApplyEffect(Bitmap[] inputs, Effect effect, out Rectangle affectedRect);
+       public static Bitmap[] ApplyEffect(Bitmap[] inputs, Effect effect, Rectangle srcRect, out Rectangle affectedRect);
    }

    public sealed class Graphics : MarshalByRefObject, IDisposable, IDeviceContext
    {
!       Same applies as above.
+       public void DrawImage(Image image, Matrix? transform, Effect? effect, ImageAttributes? imageAttr, GraphicsUnit srcUnit)
+       public void DrawImage(Image image, Rectangle srcRect, Matrix? transform	, Effect? effect, ImageAttributes? imageAttr, GraphicsUnit srcUnit)
    }
}

GetColorLookupTableFromAuxiliaryData is a bit of a mouthful, but I can justify the choice. In GDI+, an Effect has a property UseAuxData and a method VOID* GetAuxData(). After calling ApplyEffect, if UseAuxData is set to true, the aux data is populated based on the actual type of the effect used. At this moment, that can either be no data (where this method would throw an exception) or a ColorLookupTable. Technically, another iteration of GDI+ could add a third type of aux data, but that's likely to never happen.

This API differs from the native definitions slightly:

  • The Effect suffix is not present in the C++ definitions
  • As proposed, these types integrate the effect parameters as properties on the Effect-derived type itself. In C++, each Effect-derived type (e.g. Blur) has a corresponding Parameters type (e.g. BlurParams) with the desired parameters. To reduce API bloat and to make consumption easier, I just ditched this concept. If this is not okay with the area owners, I can revert this.

This requires changes to libgdiplus in order to support it on non-Windows platforms.

Metadata

Metadata

Assignees

Labels

🚧 work in progressWork that is current in progressapi-approved(4) API was approved in API review, it can be implementedarea-System.DrawingSystem.Drawing issues

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions