///////////////////////////////////////////////////////////////////////////////// // Paint.NET // // Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // // See src/Resources/Files/License.txt for full licensing and attribution // // details. // // . // ///////////////////////////////////////////////////////////////////////////////// using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Runtime.Serialization; using System.Threading; using System.Windows.Forms; namespace PaintDotNet { /// /// A layer's properties are immutable. That is, you can modify the surface /// of a layer all you want, but to change its dimensions requires creating /// a new layer. /// [Serializable] public abstract class Layer : ICloneable, IDisposable, IThumbnailProvider { private int width; private int height; /// /// The background layer is generally opaque although it doesn't *have* to be. For /// example, the Canvas Size action distinguishes between background and non-background /// layers such that it fills the background layer with opaque and the non-background /// layers with transparency. /// The value of this property should not be used to disallow the user from performing /// an action. /// public bool IsBackground { get { return properties.isBackground; } set { bool oldValue = properties.isBackground; if (oldValue != value) { OnPropertyChanging(LayerProperties.IsBackgroundName); properties.isBackground = value; OnPropertyChanged(LayerProperties.IsBackgroundName); } } } /// /// If this value is non-0, then the PropertyChanged event will be /// suppressed. This is in place so that the Layer Properties dialog /// can tweak the properties without them filling up the undo stack. /// [NonSerialized] private int suppressPropertyChanges; /// /// Encapsulates the mutable properties of the Layer class. /// [Serializable] internal sealed class LayerProperties : ICloneable, ISerializable { public string name; public NameValueCollection userMetaData; public bool visible; public bool isBackground; public byte opacity; private const string nameTag = "name"; private const string userMetaDataTag = "userMetaData"; private const string visibleTag = "visible"; private const string isBackgroundTag = "isBackground"; private const string opacityTag = "opacity"; public static string IsBackgroundName { get { return PdnResources.GetString("Layer.Properties.IsBackground.Name"); } } public static string NameName { get { return PdnResources.GetString("Layer.Properties.Name.Name"); } } public static string VisibleName { get { return PdnResources.GetString("Layer.Properties.Visible.Name"); } } public static string OpacityName { get { return PdnResources.GetString("Layer.Properties.Opacity.Name"); } } public LayerProperties(string name, NameValueCollection userMetaData, bool visible, bool isBackground, byte opacity) { this.name = name; this.userMetaData = new NameValueCollection(userMetaData); this.visible = visible; this.isBackground = isBackground; this.opacity = opacity; } public LayerProperties(LayerProperties copyMe) { this.name = copyMe.name; this.userMetaData = new NameValueCollection(copyMe.userMetaData); this.visible = copyMe.visible; this.isBackground = copyMe.isBackground; this.opacity = copyMe.opacity; } public object Clone() { return new LayerProperties(this); } public LayerProperties(SerializationInfo info, StreamingContext context) { this.name = info.GetString(nameTag); this.userMetaData = (NameValueCollection)info.GetValue(userMetaDataTag, typeof(NameValueCollection)); this.visible = info.GetBoolean(visibleTag); this.isBackground = info.GetBoolean(isBackgroundTag); // This property was added with v2.1. So as to allow loading old .PDN files, // this is an optional item. // (Historical note: this property was actually moved from the BitmapLayer // properties to the base class because it was found to be a rather important // property for rendering regardless of layer "type") try { this.opacity = info.GetByte(opacityTag); } catch (SerializationException) { this.opacity = 255; } } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue(nameTag, name); info.AddValue(userMetaDataTag, userMetaData); info.AddValue(visibleTag, visible); info.AddValue(isBackgroundTag, isBackground); info.AddValue(opacityTag, opacity); } } private LayerProperties properties; public byte Opacity { get { if (disposed) { throw new ObjectDisposedException("Layer"); } return properties.opacity; } set { if (disposed) { throw new ObjectDisposedException("Layer"); } if (properties.opacity != value) { OnPropertyChanging(LayerProperties.OpacityName); properties.opacity = value; OnPropertyChanged(LayerProperties.OpacityName); Invalidate(); } } } /// /// Allows you to save the mutable properties of the layer so you can restore them later /// (esp. important for undo!). Mutable properties include the layer's name, whether it's /// visible, and the metadata. This list might expand later. /// /// /// An object that can be used later in a call to LoadProperties. /// /// /// It is important that derived classes call this in the correct fashion so as to 'chain' /// the properties list together. The following is the correct pattern: /// /// public override object SaveProperties() /// { /// object baseProperties = base.SaveProperties(); /// return new List(properties.Clone(), new List(baseProperties, null)); /// } /// public virtual object SaveProperties() { return properties.Clone(); } public void LoadProperties(object oldState) { LoadProperties(oldState, false); } public virtual void LoadProperties(object oldState, bool suppressEvents) { LayerProperties lp = (LayerProperties)oldState; List changed = new List(); if (!suppressEvents) { if (lp.name != properties.name) { changed.Add(LayerProperties.NameName); } if (lp.isBackground != properties.isBackground) { changed.Add(LayerProperties.IsBackgroundName); } if (lp.visible != properties.visible) { changed.Add(LayerProperties.VisibleName); } if (lp.opacity != properties.opacity) { changed.Add(LayerProperties.OpacityName); } } foreach (string propertyName in changed) { OnPropertyChanging(propertyName); } properties = (LayerProperties)((LayerProperties)oldState).Clone(); Invalidate(); foreach (string propertyName in changed) { OnPropertyChanged(propertyName); } } public int Width { get { return width; } } public int Height { get { return height; } } public Size Size { get { return new Size(Width, Height); } } public Rectangle Bounds { get { return new Rectangle(new Point(0, 0), Size); } } public void PushSuppressPropertyChanged() { Interlocked.Increment(ref suppressPropertyChanges); } public void PopSuppressPropertyChanged() { if (0 > Interlocked.Decrement(ref suppressPropertyChanges)) { throw new InvalidProgramException("suppressPreviewChanged is less than zero"); } } [field: NonSerialized] public event EventHandler PreviewChanged; protected virtual void OnPreviewChanged() { if (PreviewChanged != null) { PreviewChanged(this, EventArgs.Empty); } } /// /// This event is raised before a property is changed. Note that the name given /// in the PropertyEventArgs is for descriptive (UI) purposes only and serves no /// programmatic purpose. When this event is raised you should not make any /// assumptions about which property was changed based on this description. /// [field: NonSerialized] public event PropertyEventHandler PropertyChanging; protected virtual void OnPropertyChanging(string propertyName) { if (this.suppressPropertyChanges == 0) { if (PropertyChanging != null) { PropertyChanging(this, new PropertyEventArgs(propertyName)); } } } /// /// This event is raised after a property is changed. Note that the name given /// in the PropertyEventArgs is for descriptive (UI) purposes only and serves no /// programmatic purpose. When this event is raised you should not make any /// assumptions about which property was changed based on this description. /// [field: NonSerialized] public event PropertyEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { if (this.suppressPropertyChanges == 0) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyEventArgs(propertyName)); } } } /// /// You can call this to raise the PropertyChanged event. Note that is will /// raise the event with an empty string for the property name description. /// Thus it is useful only for syncing up UI elements that require notification /// of events but that otherwise don't really track it. /// public void PerformPropertyChanged() { OnPropertyChanged(string.Empty); } /// /// A user-definable name. /// public string Name { get { return properties.name; } set { if (properties.name != value) { OnPropertyChanging(LayerProperties.NameName); properties.name = value; OnPropertyChanged(LayerProperties.NameName); } } } [NonSerialized] private Metadata metadata; public Metadata Metadata { get { if (metadata == null) { metadata = new Metadata(properties.userMetaData); } return metadata; } } /// /// Determines whether the layer is part of a document's composition. If this /// property is false, the composition engine will ignore this layer. /// public bool Visible { get { return properties.visible; } set { bool oldValue = properties.visible; if (oldValue != value) { OnPropertyChanging(LayerProperties.VisibleName); properties.visible = value; OnPropertyChanged(LayerProperties.VisibleName); Invalidate(); } } } /// /// Determines whether a rectangle is fully in bounds or not. This is determined by checking /// to make sure the left, top, right, and bottom edges are within bounds. /// /// /// private bool IsInBounds(Rectangle roi) { if (roi.Left < 0 || roi.Top < 0 || roi.Left >= Width || roi.Top >= Height || roi.Right > Width || roi.Bottom > Height) { return false; } return true; } /// /// Implements IThumbnailProvider.RenderThumbnail(). /// public abstract Surface RenderThumbnail(int maxEdgeLength); /// /// Causes the layer to render a given rectangle of interest (roi) to the given destination surface. /// /// Contains information about which objects to use for rendering /// The rectangular region to be rendered. public void Render(RenderArgs args, Rectangle roi) { // the bitmap we're rendering to must match the size of the layer we're rendering from if (args.Surface.Width != Width || args.Surface.Height != Height) { throw new ArgumentException(); } // the region of interest can not be out of bounds! if (!IsInBounds(roi)) { throw new ArgumentOutOfRangeException("roi"); } RenderImpl(args, roi); } /// /// Causes the layer to render a given region of interest (roi) to the given destination surface. /// /// Contains information about which objects to use for rendering /// The region to be rendered. public void Render(RenderArgs args, PdnRegion roi) { Rectangle roiBounds = roi.GetBoundsInt(); if (!IsInBounds(roiBounds)) { throw new ArgumentOutOfRangeException("roi"); } Rectangle[] rects = roi.GetRegionScansReadOnlyInt(); RenderImpl(args, rects); } public void RenderUnchecked(RenderArgs args, Rectangle[] roi, int startIndex, int length) { RenderImpl(args, roi, startIndex, length); } /// /// Override this method to provide your layer's rendering capabilities. /// /// Contains information about which objects to use for rendering /// The rectangular region to be rendered. protected abstract void RenderImpl(RenderArgs args, Rectangle roi); protected void RenderImpl(RenderArgs args, Rectangle[] roi) { RenderImpl(args, roi, 0, roi.Length); } protected virtual void RenderImpl(RenderArgs args, Rectangle[] roi, int startIndex, int length) { for (int i = startIndex; i < startIndex + length; ++i) { RenderImpl(args, roi[i]); } } [field: NonSerialized] public event InvalidateEventHandler Invalidated; protected virtual void OnInvalidated(InvalidateEventArgs e) { if (Invalidated != null) { Invalidated(this, e); } } /// /// Causes the entire layer surface to be invalidated. /// public void Invalidate() { Rectangle rect = new Rectangle(0, 0, Width, Height); OnInvalidated(new InvalidateEventArgs(rect)); } /// /// Causes a portion of the layer surface to be invalidated. /// /// The region of interest to be invalidated. public void Invalidate(PdnRegion roi) { foreach (Rectangle rect in roi.GetRegionScansReadOnlyInt()) { Invalidate(rect); } } /// /// Causes a portion of the layer surface to be invalidated. /// /// The region of interest to be invalidated. public void Invalidate(RectangleF[] roi) { foreach (RectangleF rectF in roi) { Invalidate(Rectangle.Truncate(rectF)); } } /// /// Causes a portion of the layer surface to be invalidated. /// /// The rectangle of interest to be invalidated. public void Invalidate(Rectangle roi) { Rectangle rect = Rectangle.Intersect(roi, this.Bounds); // TODO: this is horrible for performance w.r.t. complex invalidation regions. Lots of heap pollution. // fix that! OnInvalidated(new InvalidateEventArgs(rect)); } public Layer(int width, int height) { this.width = width; this.height = height; this.properties = new LayerProperties(null, new NameValueCollection(), true, false, 255); } protected Layer(Layer copyMe) { this.width = copyMe.width; this.height = copyMe.height; this.properties = (LayerProperties)copyMe.properties.Clone(); } // TODO: add "name" parameter, keep this for legacy and fill it in with "Background" // goal is to put complete burden of loc on the client public static BitmapLayer CreateBackgroundLayer(int width, int height) { // set colors to 0xffffffff // note: we use alpha of 255 here so that "invert colors" works as expected // that is, for just 1 layer we invert the initial white->black // but on subsequent layers we invert transparent white -> transparent black, which shows up as white for the most part BitmapLayer layer = new BitmapLayer(width, height, ColorBgra.White); layer.Name = PdnResources.GetString("Layer.Background.Name"); // tag it as a background layer layer.properties.isBackground = true; return layer; } /// /// This allows a layer to provide a dialog for configuring /// the layer's properties. /// public abstract PdnBaseForm CreateConfigDialog(); public abstract object Clone(); ~Layer() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!disposed) { disposed = true; if (disposing) { } } } } }