/src/Data/Document.cs
C# | 1777 lines | 1241 code | 247 blank | 289 comment | 223 complexity | bda577f229112c8b436096fe432eeb7d MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- /////////////////////////////////////////////////////////////////////////////////
- // Paint.NET //
- // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
- // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
- // See src/Resources/Files/License.txt for full licensing and attribution //
- // details. //
- // . //
- /////////////////////////////////////////////////////////////////////////////////
-
- using System.Collections.Generic;
- using PaintDotNet.Base;
- using PaintDotNet.SystemLayer;
- using System;
- using System.Collections.Specialized;
- using System.ComponentModel;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.Drawing.Imaging;
- using System.IO;
- using System.IO.Compression;
- using System.Reflection;
- using System.Runtime.Serialization;
- using System.Runtime.Serialization.Formatters.Binary;
- using System.Text;
- using System.Windows.Forms;
- using System.Xml;
-
- namespace PaintDotNet
- {
- [Serializable]
- public sealed class Document
- : IDeserializationCallback,
- IDisposable,
- ICloneable
- {
- private readonly LayerList _layers;
- private readonly int _width;
- private readonly int _height;
- private readonly NameValueCollection _userMetaData;
-
- [NonSerialized]
- private Threading.ThreadPool _threadPool = new Threading.ThreadPool();
-
- [NonSerialized]
- private InvalidateEventHandler _layerInvalidatedDelegate;
-
- // TODO: the document class should not manage its own update region, its owner should
- [NonSerialized]
- private Vector<Rectangle> _updateRegion;
-
- [NonSerialized]
- private bool _dirty;
-
- private Version _savedWith;
-
- [NonSerialized]
- private Metadata _metadata;
-
- [NonSerialized]
- private XmlDocument _headerXml;
-
- private const string HeaderXmlSkeleton = "<pdnImage><custom></custom></pdnImage>";
-
- private XmlDocument HeaderXml
- {
- get
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- if (_headerXml == null)
- {
- _headerXml = new XmlDocument();
- _headerXml.LoadXml(HeaderXmlSkeleton);
- }
-
- return _headerXml;
- }
- }
-
- public string Header
- {
- get
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- return HeaderXml.OuterXml;
- }
- }
-
- public string CustomHeaders
- {
- get
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- return HeaderXml != null ? HeaderXml.SelectSingleNode("/pdnImage/custom").InnerXml : null;
- }
-
- set
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- if (HeaderXml != null) HeaderXml.SelectSingleNode("/pdnImage/custom").InnerXml = value;
- Dirty = true;
- }
- }
-
- /// <summary>
- /// Gets or sets the units that are used for measuring the document's physical (printed) size.
- /// </summary>
- /// <remarks>
- /// If this property is set to MeasurementUnit.Pixel, then Dpu will be reset to 1.
- /// If this property has not been set in the image's metadata, its default value
- /// will be MeasurementUnit.Inch.
- /// If the EXIF data for the image is invalid (such as "ResolutionUnit = 0" or something),
- /// then the default DpuUnit will be returned.
- /// </remarks>
- public MeasurementUnit DpuUnit
- {
- get
- {
- PropertyItem[] pis = Metadata.GetExifValues(ExifTagID.ResolutionUnit);
-
- if (pis.Length == 0)
- {
- DpuUnit = DefaultDpuUnit;
- return DefaultDpuUnit;
- }
- try
- {
- ushort unit = Exif.DecodeShortValue(pis[0]);
-
- // Guard against bad data in the EXIF store
- switch ((MeasurementUnit)unit)
- {
- case MeasurementUnit.Centimeter:
- case MeasurementUnit.Inch:
- case MeasurementUnit.Pixel:
- return (MeasurementUnit)unit;
-
- default:
- Metadata.RemoveExifValues(ExifTagID.ResolutionUnit);
- return DpuUnit; // recursive call
- }
- }
-
- catch (Exception)
- {
- Metadata.RemoveExifValues(ExifTagID.ResolutionUnit);
- return DpuUnit; // recursive call
- }
- }
-
- set
- {
- PropertyItem pi = Exif.CreateShort(ExifTagID.ResolutionUnit, (ushort)value);
- Metadata.ReplaceExifValues(ExifTagID.ResolutionUnit, new[] { pi });
-
- if (value == MeasurementUnit.Pixel)
- {
- DpuX = 1.0;
- DpuY = 1.0;
- }
-
- Dirty = true;
- }
- }
-
- public static MeasurementUnit DefaultDpuUnit
- {
- get
- {
- return MeasurementUnit.Inch;
- }
- }
-
- #if false
- [Obsolete("Use DefaultDpuUnit property instead.")]
- public static MeasurementUnit GetDefaultDpuUnit()
- {
- return DefaultDpuUnit;
- }
- #endif
-
- private const double defaultDpi = 96.0;
-
- public static double DefaultDpi
- {
- get
- {
- return defaultDpi;
- }
- }
-
- public const double CmPerInch = 2.54;
- private const double defaultDpcm = defaultDpi / CmPerInch;
-
- public static double DefaultDpcm
- {
- get
- {
- return defaultDpcm;
- }
- }
-
- public const double MinimumDpu = 0.01;
- public const double MaximumDpu = 32767.0;
-
- public static double InchesToCentimeters(double inches)
- {
- return inches * CmPerInch;
- }
-
- public static double CentimetersToInches(double centimeters)
- {
- return centimeters / CmPerInch;
- }
-
- public static double DotsPerInchToDotsPerCm(double dpi)
- {
- return dpi / CmPerInch;
- }
-
- public static double DotsPerCmToDotsPerInch(double dpcm)
- {
- return dpcm * CmPerInch;
- }
-
- public static double GetDefaultDpu(MeasurementUnit units)
- {
- double dpu;
-
- switch (units)
- {
- case MeasurementUnit.Inch:
- dpu = defaultDpi;
- break;
-
- case MeasurementUnit.Centimeter:
- dpu = defaultDpcm;
- break;
-
- case MeasurementUnit.Pixel:
- dpu = 1.0;
- break;
-
- default:
- throw new InvalidEnumArgumentException("DpuUnit", (int)units, typeof(MeasurementUnit));
- }
-
- return dpu;
- }
-
- /// <summary>
- /// Ensures that the document's DpuX, DpuY, and DpuUnits properties are set.
- /// If they are not already set, they are initialized to their default values (96, 96 , inches).
- /// </summary>
- private void InitializeDpu()
- {
- DpuUnit = DpuUnit;
- DpuX = DpuX;
- DpuY = DpuY;
- }
-
- private static byte[] GetDoubleAsRationalExifData(double value)
- {
- uint numerator;
- uint denominator;
-
- if (Math.IEEERemainder(value, 1.0) == 0)
- {
- numerator = (uint)value;
- denominator = 1;
- }
- else
- {
- double s = value * 1000.0;
- numerator = (uint)Math.Floor(s);
- denominator = 1000;
- }
-
- return Exif.EncodeRationalValue(numerator, denominator);
- }
-
- /// <summary>
- /// Gets or sets the Document's dots-per-unit scale in the X direction.
- /// </summary>
- /// <remarks>
- /// If DpuUnit is equal to MeasurementUnit.Pixel, then this property may not be set
- /// to any value other than 1.0. Setting DpuUnit to MeasurementUnit.Pixel will reset
- /// this property to 1.0. This property may only be set to a value greater than 0.
- /// One dot is always equal to one pixel. This property will not return a value less
- /// than MinimumDpu, nor a value larger than MaximumDpu.
- /// </remarks>
- public double DpuX
- {
- get
- {
- PropertyItem[] pis = Metadata.GetExifValues(ExifTagID.XResolution);
-
- if (pis.Length == 0)
- {
- double defaultDpu = GetDefaultDpu(DpuUnit);
- DpuX = defaultDpu;
- return defaultDpu;
- }
- try
- {
- uint numerator;
- uint denominator;
-
- Exif.DecodeRationalValue(pis[0], out numerator, out denominator);
-
- if (denominator == 0)
- {
- throw new DivideByZeroException(); // will be caught by the below catch{}
- }
- return Math.Min(MaximumDpu, Math.Max(MinimumDpu, (double)numerator / (double)denominator));
- }
-
- catch
- {
- Metadata.RemoveExifValues(ExifTagID.XResolution);
- return DpuX; // recursive call;
- }
- }
-
- set
- {
- if (value <= 0.0)
- {
- throw new ArgumentOutOfRangeException("value", value, "must be > 0.0");
- }
-
- if (DpuUnit == MeasurementUnit.Pixel && value != 1.0)
- {
- throw new ArgumentOutOfRangeException("value", value, "if DpuUnit == Pixel, then value must equal 1.0");
- }
-
- byte[] data = GetDoubleAsRationalExifData(value);
-
- PropertyItem pi = Exif.CreatePropertyItem(ExifTagID.XResolution, ExifTagType.Rational, data);
- Metadata.ReplaceExifValues(ExifTagID.XResolution, new[] { pi });
- Dirty = true;
- }
- }
-
- /// <summary>
- /// Gets or sets the Document's dots-per-unit scale in the Y direction.
- /// </summary>
- /// <remarks>
- /// If DpuUnit is equal to MeasurementUnit.Pixel, then this property may not be set
- /// to any value other than 1.0. Setting DpuUnit to MeasurementUnit.Pixel will reset
- /// this property to 1.0. This property may only be set to a value greater than 0.
- /// One dot is always equal to one pixel. This property will not return a value less
- /// than MinimumDpu, nor a value larger than MaximumDpu.
- /// </remarks>
- public double DpuY
- {
- get
- {
- PropertyItem[] pis = Metadata.GetExifValues(ExifTagID.YResolution);
-
- if (pis.Length == 0)
- {
- // If there's no DpuY setting, default to the DpuX setting
- double dpu = DpuX;
- DpuY = dpu;
- return dpu;
- }
- try
- {
- uint numerator;
- uint denominator;
-
- Exif.DecodeRationalValue(pis[0], out numerator, out denominator);
-
- if (denominator == 0)
- {
- throw new DivideByZeroException(); // will be caught by the below catch{}
- }
- return Math.Min(MaximumDpu, Math.Max(MinimumDpu, (double)numerator / (double)denominator));
- }
-
- catch
- {
- Metadata.RemoveExifValues(ExifTagID.YResolution);
- return DpuY; // recursive call;
- }
- }
-
- set
- {
- if (value <= 0.0)
- {
- throw new ArgumentOutOfRangeException("value", value, "must be > 0.0");
- }
-
- if (DpuUnit == MeasurementUnit.Pixel && value != 1.0)
- {
- throw new ArgumentOutOfRangeException("value", value, "if DpuUnit == Pixel, then value must equal 1.0");
- }
-
- byte[] data = GetDoubleAsRationalExifData(value);
-
- PropertyItem pi = Exif.CreatePropertyItem(ExifTagID.YResolution, ExifTagType.Rational, data);
- Metadata.ReplaceExifValues(ExifTagID.YResolution, new[] { pi });
- Dirty = true;
- }
- }
-
- /// <summary>
- /// Gets the Document's measured physical width based on the DpuUnit and DpuX properties.
- /// </summary>
- public double PhysicalWidth
- {
- get
- {
- return Width / DpuX;
- }
- }
-
- /// <summary>
- /// Gets the Document's measured physical height based on the DpuUnit and DpuY properties.
- /// </summary>
- public double PhysicalHeight
- {
- get
- {
- return Height / DpuY;
- }
- }
-
- //
- // Conversion Matrix:
- //
- // GetPhysical[X|Y](x, unit), where dpu = this.dpuX or dpuY
- //
- // dpu | px | in | cm |
- // unit | | | |
- // -------------+------+------+------------+
- // px | x | x | x |
- // -------------+------+------+------------+
- // in | x / | x / | x / |
- // | 96 | dpuX | (dpuX*2.54)|
- // -------------+------+------+------------+
- // cm | x / |x*2.54| x / dpuX |
- // | 37.8| /dpuX| |
- // -------------+------+------+------------+
-
- public static double PixelToPhysical(double pixel, MeasurementUnit resultUnit, MeasurementUnit dpuUnit, double dpu)
- {
- double result;
-
- if (resultUnit == MeasurementUnit.Pixel)
- {
- result = pixel;
- }
- else
- {
- if (resultUnit == dpuUnit)
- {
- result = pixel / dpu;
- }
- else if (dpuUnit == MeasurementUnit.Pixel)
- {
- double defaultDpu = GetDefaultDpu(dpuUnit);
- result = pixel / defaultDpu;
- }
- else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch)
- {
- result = pixel / (CmPerInch * dpu);
- }
- else // if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter)
- {
- result = (pixel * CmPerInch) / dpu;
- }
- }
-
- return result;
- }
-
- public double PixelToPhysicalX(double pixel, MeasurementUnit resultUnit)
- {
- double result;
-
- if (resultUnit == MeasurementUnit.Pixel)
- {
- result = pixel;
- }
- else
- {
- MeasurementUnit dpuUnit = DpuUnit;
-
- if (resultUnit == dpuUnit)
- {
- result = pixel / DpuX;
- }
- else if (dpuUnit == MeasurementUnit.Pixel)
- {
- double defaultDpuX = GetDefaultDpu(dpuUnit);
- result = pixel / defaultDpuX;
- }
- else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch)
- {
- result = pixel / (CmPerInch * DpuX);
- }
- else //if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter)
- {
- result = (pixel * CmPerInch) / DpuX;
- }
- }
-
- return result;
- }
-
- public double PixelToPhysicalY(double pixel, MeasurementUnit resultUnit)
- {
- double result;
-
- if (resultUnit == MeasurementUnit.Pixel)
- {
- result = pixel;
- }
- else
- {
- MeasurementUnit dpuUnit = DpuUnit;
-
- if (resultUnit == dpuUnit)
- {
- result = pixel / DpuY;
- }
- else if (dpuUnit == MeasurementUnit.Pixel)
- {
- double defaultDpuY = GetDefaultDpu(dpuUnit);
- result = pixel / defaultDpuY;
- }
- else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch)
- {
- result = pixel / (CmPerInch * DpuY);
- }
- else //if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter)
- {
- result = (pixel * CmPerInch) / DpuY;
- }
- }
-
- return result;
- }
-
- private static bool IsValidMeasurementUnit(MeasurementUnit unit)
- {
- switch (unit)
- {
- case MeasurementUnit.Pixel:
- case MeasurementUnit.Centimeter:
- case MeasurementUnit.Inch:
- return true;
-
- default:
- return false;
- }
- }
-
- public static double ConvertMeasurement(
- double sourceLength,
- MeasurementUnit sourceUnits,
- MeasurementUnit basisDpuUnits,
- double basisDpu,
- MeasurementUnit resultDpuUnits)
- {
- // Validation
- if (!IsValidMeasurementUnit(sourceUnits))
- {
- throw new InvalidEnumArgumentException("sourceUnits", (int)sourceUnits, typeof(MeasurementUnit));
- }
-
- if (!IsValidMeasurementUnit(basisDpuUnits))
- {
- throw new InvalidEnumArgumentException("basisDpuUnits", (int)basisDpuUnits, typeof(MeasurementUnit));
- }
-
- if (!IsValidMeasurementUnit(resultDpuUnits))
- {
- throw new InvalidEnumArgumentException("resultDpuUnits", (int)resultDpuUnits, typeof(MeasurementUnit));
- }
-
- if (basisDpuUnits == MeasurementUnit.Pixel && basisDpu != 1.0)
- {
- throw new ArgumentOutOfRangeException("basisDpuUnits, basisDpu", "if basisDpuUnits is Pixel, then basisDpu must equal 1.0");
- }
-
- // Case 1. No conversion is necessary if they want the same units out.
- if (sourceUnits == resultDpuUnits)
- {
- return sourceLength;
- }
-
- // Case 2. Simple inches -> centimeters
- if (sourceUnits == MeasurementUnit.Inch && resultDpuUnits == MeasurementUnit.Centimeter)
- {
- return InchesToCentimeters(sourceLength);
- }
-
- // Case 3. Simple centimeters -> inches.
- if (sourceUnits == MeasurementUnit.Centimeter && resultDpuUnits == MeasurementUnit.Inch)
- {
- return CentimetersToInches(sourceLength);
- }
-
- // At this point we know we are converting from non-pixels to pixels, or from pixels
- // to non-pixels.
- // Cases 4 through 8 cover conversion from non-pixels to pixels.
- // Cases 9 through 11 cover conversion from pixels to non-pixels.
-
- // Case 4. Conversion from pixels to inches/centimeters when basis is in pixels too.
- // This means we must use the default DPU for the desired result measurement.
- // No need to compare lengthUnits != resultDpuUnits, since we already know this to
- // be true from case 1.
- if (sourceUnits == MeasurementUnit.Pixel && basisDpuUnits == MeasurementUnit.Pixel)
- {
- double dpu = GetDefaultDpu(resultDpuUnits);
- double lengthInOrCm = sourceLength / dpu;
- return lengthInOrCm;
- }
-
- // Case 5. Conversion from inches/centimeters to pixels when basis is in pixels too.
- // This means we must use the default DPU for the given input measurement.
- if (sourceUnits != MeasurementUnit.Pixel && basisDpuUnits == MeasurementUnit.Pixel)
- {
- double dpu = GetDefaultDpu(sourceUnits);
- double resultPx = sourceLength * dpu;
- return resultPx;
- }
-
- // Case 6. Conversion from inches/centimeters to pixels, when basis is in same units as input.
- if (sourceUnits == basisDpuUnits && resultDpuUnits == MeasurementUnit.Pixel)
- {
- double resultPx = sourceLength * basisDpu;
- return resultPx;
- }
-
- // Case 7. Conversion from inches to pixels, when basis is in centimeters.
- if (sourceUnits == MeasurementUnit.Inch && basisDpuUnits == MeasurementUnit.Centimeter)
- {
- double dpi = DotsPerCmToDotsPerInch(basisDpu);
- double resultPx = sourceLength * dpi;
- return resultPx;
- }
-
- // Case 8. Conversion from centimeters to pixels, when basis is in inches.
- if (sourceUnits == MeasurementUnit.Centimeter && basisDpuUnits == MeasurementUnit.Inch)
- {
- double dpcm = DotsPerInchToDotsPerCm(basisDpu);
- double resultPx = sourceLength * dpcm;
- return resultPx;
- }
-
- // Case 9. Converting from pixels to inches/centimeters, when the basis and result
- // units are the same.
- if (basisDpuUnits == resultDpuUnits)
- {
- double resultInOrCm = sourceLength / basisDpu;
- return resultInOrCm;
- }
-
- // Case 10. Converting from pixels to centimeters, when the basis is in inches.
- if (resultDpuUnits == MeasurementUnit.Centimeter && basisDpuUnits == MeasurementUnit.Inch)
- {
- double dpcm = DotsPerInchToDotsPerCm(basisDpu);
- double resultCm = sourceLength / dpcm;
- return resultCm;
- }
-
- // Case 11. Converting from pixels to inches, when the basis is in centimeters.
- if (resultDpuUnits == MeasurementUnit.Inch && basisDpuUnits == MeasurementUnit.Centimeter)
- {
- double dpi = DotsPerCmToDotsPerInch(basisDpu);
- double resultIn = sourceLength / dpi;
- return resultIn;
- }
-
- // Should not be possible to get here, but must appease the compiler.
- throw new InvalidOperationException();
- }
-
- public double PixelAreaToPhysicalArea(double area, MeasurementUnit resultUnit)
- {
- double xScale = PixelToPhysicalX(1.0, resultUnit);
- double yScale = PixelToPhysicalY(1.0, resultUnit);
-
- return area * xScale * yScale;
- }
-
- private static string GetUnitsAbbreviation(MeasurementUnit units)
- {
- string result;
-
- switch (units)
- {
- case MeasurementUnit.Pixel:
- result = string.Empty;
- break;
-
- case MeasurementUnit.Centimeter:
- result = PdnResources.GetString("MeasurementUnit.Centimeter.Abbreviation");
- break;
-
- case MeasurementUnit.Inch:
- result = PdnResources.GetString("MeasurementUnit.Inch.Abbreviation");
- break;
-
- default:
- throw new InvalidEnumArgumentException("MeasurementUnit was invalid");
- }
-
- return result;
- }
-
- public void CoordinatesToStrings(MeasurementUnit units, int x, int y, out string xString, out string yString, out string unitsString)
- {
- string unitsAbbreviation = GetUnitsAbbreviation(units);
-
- unitsString = GetUnitsAbbreviation(units);
-
- if (units == MeasurementUnit.Pixel)
- {
- xString = x.ToString();
- yString = y.ToString();
- }
- else
- {
- double physicalX = PixelToPhysicalX(x, units);
- xString = physicalX.ToString("F2");
-
- double physicalY = PixelToPhysicalY(y, units);
- yString = physicalY.ToString("F2");
- }
- }
-
- /// <summary>
- /// This is provided for future use.
- /// If you want to add new stuff that must be serialized, create a new class,
- /// then point 'tag' to a new instance of this class that is initialized
- /// during construction. Make sure the new class has a 'tag' variable as well.
- /// We effectively set up a 'linked list' where new versions of the code
- /// can open old versions of the document, as .NET serialization is fickle in
- /// certain areas. You might also add a new property to simplify using
- /// this stuff...
- /// public DocumentVersion2Data DocV2Data { get { return (DocumentVersion2Data)tag; } }
- /// </summary>
- // In practice, this has never been used, and .NET 2.0+ has better facilities for adding
- // new data to a serialization schema. Therefore, marking as obsolete.
- //[Obsolete]
- // private object _tag = null; COMMENTED OUT AS IT IS OBSOLETE
-
- /// <summary>
- /// Reports the version of Paint.NET that this file was saved with.
- /// This is reset when SaveToStream is used. This can be used to
- /// determine file format compatibility if necessary.
- /// </summary>
- public Version SavedWithVersion
- {
- get
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- return _savedWith ?? (_savedWith = PdnInfo.GetVersion());
- }
- }
-
- [field: NonSerialized]
- public event EventHandler DirtyChanged;
-
- private void OnDirtyChanged()
- {
- if (DirtyChanged != null)
- {
- DirtyChanged(this, EventArgs.Empty);
- }
- }
-
- /// <summary>
- /// Keeps track of whether the document has changed at all since it was last opened
- /// or saved. This is something that is not reset to true by any method in the Document
- /// class, but is set to false anytime anything is changed.
- /// This way we can prompt the user to save a changed document when they go to quit.
- /// </summary>
- public bool Dirty
- {
- get
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- return _dirty;
- }
-
- set
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- if (_dirty == value) return;
- _dirty = value;
- OnDirtyChanged();
- }
- }
-
- /// <summary>
- /// Exposes a collection for access to the layers, and for manipulation of
- /// the way the document contains the layers (add/remove/move).
- /// </summary>
- public LayerList Layers
- {
- get
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- return _layers;
- }
- }
-
- /// <summary>
- /// Width of the document, in pixels. All contained layers must be this wide as well.
- /// </summary>
- public int Width
- {
- get
- {
- return _width;
- }
- }
-
- /// <summary>
- /// Height of the document, in pixels. All contained layers must be this tall as well.
- /// </summary>
- public int Height
- {
- get
- {
- return _height;
- }
- }
-
- /// <summary>
- /// The size of the document, in pixels. This is a convenience property that wraps up
- /// the Width and Height properties in one Size structure.
- /// </summary>
- public Size Size
- {
- get
- {
- return new Size(Width, Height);
- }
- }
-
- public Rectangle Bounds
- {
- get
- {
- return new Rectangle(0, 0, Width, Height);
- }
- }
-
- public Metadata Metadata
- {
- get { return _metadata ?? (_metadata = new Metadata(_userMetaData)); }
- }
-
- public void ReplaceMetaDataFrom(Document other)
- {
- Metadata.ReplaceWithDataFrom(other.Metadata);
- }
-
- public void ClearMetaData()
- {
- Metadata.Clear();
- }
-
- /* [Obsolete("don't use this property; implementors should expose type-safe properties instead", false)]
- // Note, we can not remove this property because then the compiler complains that 'tag' is unused.
- public object Tag
- {
- get
- {
- return _tag; COMMENTED OUT AS IT IS OBSOLETE
- }
-
- set
- {
- _tag = value;
- }
- }*/
-
- /// <summary>
- /// Clears a portion of a surface to transparent.
- /// </summary>
- /// <param name="surface">The surface to partially clear</param>
- /// <param name="roi">The rectangle to clear</param>
- private static unsafe void ClearBackground(Surface surface, Rectangle roi)
- {
- roi.Intersect(surface.Bounds);
-
- for (int y = roi.Top; y < roi.Bottom; y++)
- {
- ColorBgra *ptr = surface.GetPointAddressUnchecked(roi.Left, y);
- Memory.SetToZero(ptr, (ulong)roi.Width * ColorBgra.SizeOf);
- }
- }
-
- /// <summary>
- /// Clears a portion of a surface to transparent.
- /// </summary>
- /// <param name="surface">The surface to partially clear</param>
- /// <param name="rois">The array of Rectangles designating the areas to clear</param>
- /// <param name="startIndex">The start index within the rois array to clear</param>
- /// <param name="length">The number of Rectangles in the rois array (staring with startIndex) to clear</param>
- private static void ClearBackground(Surface surface, IList<Rectangle> rois, int startIndex, int length)
- {
- for (int i = startIndex; i < startIndex + length; i++)
- {
- ClearBackground(surface, rois[i]);
- }
- }
-
- public void Render(RenderArgs args)
- {
- Render(args, args.Surface.Bounds);
- }
-
- public void Render(RenderArgs args, Rectangle roi)
- {
- Render(args, roi, false);
- }
-
- public void Render(RenderArgs args, bool clearBackground)
- {
- Render(args, args.Surface.Bounds, clearBackground);
- }
-
- /// <summary>
- /// Renders a requested region of the document. Will clear the background of the input
- /// before rendering if requested.
- /// </summary>
- /// <param name="args">Contains information used to control where rendering occurs.</param>
- /// <param name="roi">The rectangular region to render.</param>
- /// <param name="clearBackground">If true, 'args' will be cleared to zero before rendering.</param>
- public void Render(RenderArgs args, Rectangle roi, bool clearBackground)
- {
- int startIndex;
-
- if (clearBackground)
- {
- var layer0 = _layers[0] as BitmapLayer; //Change to BitmapLayer if compile error
-
- // Special case: if the first layer is a visible BitmapLayer with full opacity using
- // the default blend op, we can just copy the pixels straight over
- if (layer0 != null &&
- layer0.Visible &&
- layer0.Opacity == 255 &&
- layer0.BlendOp.GetType() == UserBlendOps.GetDefaultBlendOp())
- {
- args.Surface.CopySurface(layer0.Surface);
- startIndex = 1;
- }
- else
- {
- ClearBackground(args.Surface, roi);
- startIndex = 0;
- }
- }
- else
- {
- startIndex = 0;
- }
-
- for (int i = startIndex; i < _layers.Count; ++i)
- {
- var layer = (Layer)_layers[i];
-
- if (layer.Visible)
- {
- layer.Render(args, roi);
- }
- }
- }
-
- public void Render(RenderArgs args, Rectangle[] roi, bool clearBackground)
- {
- Render(args, roi, 0, roi.Length, clearBackground);
- }
-
- public void Render(RenderArgs args, Rectangle[] roi, int startIndex, int length, bool clearBackground)
- {
- int startLayerIndex;
-
- if (clearBackground)
- {
- var layer0 = _layers[0] as BitmapLayer;//Change to BitmapLayer if var fails
-
- // Special case: if the first layer is a visible BitmapLayer with full opacity using
- // the default blend op, we can just copy the pixels straight over
- if (layer0 != null &&
- layer0.Visible &&
- layer0.Opacity == 255 &&
- layer0.BlendOp.GetType() == UserBlendOps.GetDefaultBlendOp())
- {
- args.Surface.CopySurface(layer0.Surface, roi, startIndex, length);
- startLayerIndex = 1;
- }
- else
- {
- ClearBackground(args.Surface, roi, startIndex, length);
- startLayerIndex = 0;
- }
- }
- else
- {
- startLayerIndex = 0;
- }
-
- for (int i = startLayerIndex; i < _layers.Count; ++i)
- {
- var layer = (Layer)_layers[i];
-
- if (layer.Visible)
- {
- layer.RenderUnchecked(args, roi, startIndex, length);
- }
- }
- }
-
- private sealed class UpdateScansContext
- {
- private readonly Document _document;
- private readonly RenderArgs _dst;
- private readonly Rectangle[] _scans;
- private readonly int _startIndex;
- private readonly int _length;
-
- public void UpdateScans(object context)
- {
- _document.Render(_dst, _scans, _startIndex, _length, true);
- }
-
- public UpdateScansContext(Document document, RenderArgs dst, Rectangle[] scans, int startIndex, int length)
- {
- _document = document;
- _dst = dst;
- _scans = scans;
- _startIndex = startIndex;
- _length = length;
- }
- }
-
- /// <summary>
- /// Renders only the portions of the document that have changed (been Invalidated) since
- /// the last call to this function.
- /// </summary>
- /// <param name="args">Contains information used to control where rendering occurs.</param>
- /// <param name="dst"></param>
- /// <returns>true if any rendering was done (the update list was non-empty), false otherwise</returns>
- public bool Update(RenderArgs dst)
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- Rectangle[] updateRects;
- int updateRectsLength;
- _updateRegion.GetArrayReadOnly(out updateRects, out updateRectsLength);
-
- if (updateRectsLength == 0)
- {
- return false;
- }
-
- PdnRegion region = Utility.RectanglesToRegion(updateRects, 0, updateRectsLength);
- Rectangle[] rectsOriginal = region.GetRegionScansReadOnlyInt();
- Rectangle[] rectsToUse;
-
- // Special case where we're drawing 1 big rectangle: split it up!
- // This case happens quite frequently, but we don't want to spend a lot of
- // time analyzing any other case that is more complicated.
- if (rectsOriginal.Length == 1 && rectsOriginal[0].Height > 1)
- {
- var rectsNew = new Rectangle[Processor.LogicalCpuCount]; //Change to Rectangle[] if var fails
- Utility.SplitRectangle(rectsOriginal[0], rectsNew);
- rectsToUse = rectsNew;
- }
- else
- {
- rectsToUse = rectsOriginal;
- }
-
- int cpuCount = Processor.LogicalCpuCount;
- for (int i = 0; i < cpuCount; ++i)
- {
- int start = (i * rectsToUse.Length) / cpuCount;
- int end = ((i + 1) * rectsToUse.Length) / cpuCount;
-
- var usc = new UpdateScansContext(this, dst, rectsToUse, start, end - start);
-
- if (i == cpuCount - 1)
- {
- // Reuse this thread for the last job -- no sense creating a new thread.
- usc.UpdateScans(usc);
- }
- else
- {
- _threadPool.QueueUserWorkItem(usc.UpdateScans, usc);
- }
- }
-
- _threadPool.Drain();
- Validate();
- return true;
- }
-
- /// <summary>
- /// Constructs a blank document (zero layers) of the given width and height.
- /// </summary>
- /// <param name="width"></param>
- /// <param name="height"></param>
- public Document(int width, int height)
- {
- _width = width;
- _height = height;
- Dirty = true;
- _updateRegion = new Vector<Rectangle>();
- _layers = new LayerList(this);
- SetupEvents();
- _userMetaData = new NameValueCollection();
- Invalidate();
- }
-
- public Document(Size size)
- : this(size.Width, size.Height)
- {
- }
-
- /// <summary>
- /// Sets up event handling for contained objects.
- /// </summary>
- private void SetupEvents()
- {
- _layers.Changed += LayerListChangedHandler;
- _layers.Changing += LayerListChangingHandler;
- _layerInvalidatedDelegate = new InvalidateEventHandler(LayerInvalidatedHandler);
-
- foreach (Layer layer in _layers)
- {
- layer.Invalidated += _layerInvalidatedDelegate;
- }
- }
-
- /// <summary>
- /// Called after deserialization occurs so that certain things that are non-serializable
- /// can be set up.
- /// </summary>
- /// <param name="sender"></param>
- public void OnDeserialization(object sender)
- {
- _updateRegion = new Vector<Rectangle>();
- _updateRegion.Add(Bounds);
- _threadPool = new Threading.ThreadPool();
- SetupEvents();
- Dirty = true;
- }
-
- [field: NonSerialized]
- public event InvalidateEventHandler Invalidated;
-
- /// <summary>
- /// Raises the Invalidated event.
- /// </summary>
- /// <param name="e"></param>
- private void OnInvalidated(InvalidateEventArgs e)
- {
- if (Invalidated != null)
- {
- Invalidated(this, e);
- }
- }
-
- /// <summary>
- /// Handles the Changing event that is raised from the contained LayerList.
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void LayerListChangingHandler(object sender, EventArgs e)
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("Document");
- }
-
- foreach (Layer layer in Layers)
- {
- layer.Invalidated -= _layerInvalidatedDelegate;
- }
- }
-
- /// <summary>
- /// Handles the Changed event that is raised from the contained LayerList.
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void LayerListChangedHandler(object sender, EventArgs e)
- {
- foreach (Layer layer in Layers)
- {
- layer.Invalidated += _layerInvalidatedDelegate;
- }
-
- Invalidate();
- }
-
- /// <summary>
- /// Handles the Invalidated event that is raised from any contained Layer.
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void LayerInvalidatedHandler(object sender, InvalidateEventArgs e)
- {
- Invalidate(e.InvalidRect);
- }
-
- /// <summary>
- /// Causes the whole document to be invalidated, forcing a full rerender on
- /// the next call to Update.
- /// </summary>
- public void Invalidate()
- {
- Dirty = true;
- var rect = new Rectangle(0, 0, Width, Height);
- _updateRegion.Clear();
- _updateRegion.Add(rect);
- OnInvalidated(new InvalidateEventArgs(rect));
- }
-
- /// <summary>
- /// Invalidates a portion of the document. The given region is then tagged
- /// for rerendering during the next call to Update.
- /// </summary>
- /// <param name="roi">The region of interest to be invalidated.</param>
- public void Invalidate(PdnRegion roi)
- {
- Dirty = true;
-
- foreach (Rectangle rect in roi.GetRegionScansReadOnlyInt())
- {
- rect.Intersect(Bounds);
- _updateRegion.Add(rect);
-
- if (rect.IsEmpty) continue;
- var iea = new InvalidateEventArgs(rect);
- OnInvalidated(iea);
- }
- }
-
- public void Invalidate(RectangleF[] roi)
- {
- foreach (RectangleF rectF in roi)
- {
- Invalidate(Rectangle.Truncate(rectF));
- }
- }
-
- public void Invalidate(RectangleF roi)
- {
- Invalidate(Rectangle.Truncate(roi));
- }
-
- public void Invalidate(Rectangle[] roi)
- {
- foreach (Rectangle rect in roi)
- {
- Invalidate(rect);
- }
- }
-
- /// <summary>
- /// Invalidates a portion of the document. The given region is then tagged
- /// for rerendering during the next call to Update.
- /// </summary>
- /// <param name="roi">The region of interest to be invalidated.</param>
- public void Invalidate(Rectangle roi)
- {
- Dirty = true;
- Rectangle rect = Rectangle.Intersect(roi, Bounds);
- _updateRegion.Add(rect);
- OnInvalidated(new InvalidateEventArgs(rect));
- }
-
- /// <summary>
- /// Clears the document's update region. This is called at the end of the
- /// Update method.
- /// </summary>
- private void Validate()
- {
- _updateRegion.Clear();
- }
-
- /// <summary>
- /// Creates a document that consists of one BitmapLayer.
- /// </summary>
- /// <param name="image">The Image to make a copy of that will be the first layer ("Background") in the document.</param>
- public static Document FromImage(Image image)
- {
- if (image == null)
- {
- throw new ArgumentNullException("image");
- }
-
- var document = new Document(image.Width, image.Height);
- BitmapLayer layer = Layer.CreateBackgroundLayer(image.Width, image.Height);
- layer.Surface.Clear(ColorBgra.FromBgra(0, 0, 0, 0));
-
- var asBitmap = image as Bitmap;
-
- // Copy pixels
- if (asBitmap != null && asBitmap.PixelFormat == PixelFormat.Format32bppArgb)
- {
- unsafe
- {
- BitmapData bData = asBitmap.LockBits(new Rectangle(0, 0, asBitmap.Width, asBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
-
- try
- {
- for (int y = 0; y < bData.Height; ++y)
- {
- var srcPtr = (uint*)((byte*)bData.Scan0.ToPointer() + (y * bData.Stride));
- ColorBgra* dstPtr = layer.Surface.GetRowAddress(y);
-
- for (int x = 0; x < bData.Width; ++x)
- {
- dstPtr->Bgra = *srcPtr;
- ++srcPtr;
- ++dstPtr;
- }
- }
- }
-
- finally
- {
- asBitmap.UnlockBits(bData);
- bData = null;
- }
- }
- }
- else if (asBitmap != null && asBitmap.PixelFormat == PixelFormat.Format24bppRgb)
- {
- unsafe
- {
- BitmapData bData = asBitmap.LockBits(new Rectangle(0, 0, asBitmap.Width, asBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
-
- try
- {
- for (int y = 0; y < bData.Height; ++y)
- {
- byte* srcPtr = (byte*)bData.Scan0.ToPointer() + (y * bData.Stride);
- ColorBgra* dstPtr = layer.Surface.GetRowAddress(y);
-
- for (int x = 0; x < bData.Width; ++x)
- {
- byte b = *srcPtr;
- byte g = *(srcPtr + 1);
- byte r = *(srcPtr + 2);
- const byte a = 255;
-
- *dstPtr = ColorBgra.FromBgra(b, g, r, a);
-
- srcPtr += 3;
- ++dstPtr;
- }
- }
- }
-
- finally
- {
- asBitmap.UnlockBits(bData);
- bData = null;
- }
- }
- }
- else
- {
- using (var args = new RenderArgs(layer.Surface))
- {
- args.Graphics.CompositingMode = CompositingMode.SourceCopy;
- args.Graphics.SmoothingMode = SmoothingMode.None;
- args.Graphics.DrawImage(image, args.Bounds, args.Bounds, Graphic…
Large files files are truncated, but you can click here to view the full file