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.InteropServices; 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 { public Surface surface; private int width; private int height; private NameValueCollection userMetaData; [NonSerialized] private Threading.ThreadPool threadPool = new Threading.ThreadPool(); /*[NonSerialized] private InvalidateEventHandler layerInvalidatedDelegate;*/ [NonSerialized] private Vector updateRegion; [NonSerialized] private bool dirty; private Version savedWith; [NonSerialized] private Metadata metadata = null; [NonSerialized] private XmlDocument headerXml; private const string headerXmlSkeleton = ""; private XmlDocument HeaderXml { get { if (this.disposed) { throw new ObjectDisposedException("Document"); } if (this.headerXml == null) { this.headerXml = new XmlDocument(); this.headerXml.LoadXml(headerXmlSkeleton); } return this.headerXml; } } /// /// Gets or sets the units that are used for measuring the document's physical (printed) size. /// /// /// 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. /// public MeasurementUnit DpuUnit { get { PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.ResolutionUnit); if (pis.Length == 0) { this.DpuUnit = DefaultDpuUnit; return DefaultDpuUnit; } else { try { ushort unit = Exif.DecodeShortValue(pis[0]); // Guard against bad data in the EXIF store switch ((MeasurementUnit)unit) { case MeasurementUnit.Pixel: case MeasurementUnit.Inch: case MeasurementUnit.Mil: case MeasurementUnit.Centimeter: case MeasurementUnit.Millimeter: case MeasurementUnit.Micron: case MeasurementUnit.Nano: return (MeasurementUnit)unit; default: this.Metadata.RemoveExifValues(ExifTagID.ResolutionUnit); return this.DpuUnit; // recursive call } } catch (Exception) { this.Metadata.RemoveExifValues(ExifTagID.ResolutionUnit); return this.DpuUnit; // recursive call } } } set { PropertyItem pi = Exif.CreateShort(ExifTagID.ResolutionUnit, (ushort)value); this.Metadata.ReplaceExifValues(ExifTagID.ResolutionUnit, new PropertyItem[1] { pi }); if (value == MeasurementUnit.Pixel) { this.DpuX = 1.0; this.DpuY = 1.0; } Dirty = true; } } public static MeasurementUnit DefaultDpuUnit { get { return MeasurementUnit.Inch; } } public static double defaultDpi = 96; public static double DefaultDpi { get { return defaultDpi; } } /// /// 1英寸 = 2.54厘米 /// public static double CmPerInch = 2.54; /// /// 默认值 = dpi / 单位 /// public double defaultDpcm; public double DefaultDpcm { get { return defaultDpi / CmPerInch; } } public const double MinimumDpu = 0.01; public const double MaximumDpu = 32767.0; public static double InchesToCentimeters(double inches) { return inches * CmPerInch; } public static double MilsToCentimeters(double mils) { return mils * CmPerInch * 0.001; } public static double CentimetersToInches(double centimeters) { return centimeters / CmPerInch; } public static double CentimetersToMils(double centimeters) { return 1000 * centimeters / CmPerInch; } public static double DotsPerInchToDotsPerCm(double dpi) { return dpi / CmPerInch; } public static double DotsPerMilToDotsPerCm(double dpi) { return 0.001 * dpi / CmPerInch; } public static double DotsPerCmToDotsPerInch(double dpcm) { return dpcm * CmPerInch; } public static double DotsPerCmToDotsPerMil(double dpcm) { return dpcm * CmPerInch * 0.001; } public static double GetDefaultDpu(MeasurementUnit units) { double dpu; switch (units) { case MeasurementUnit.Inch: dpu = defaultDpi; break; case MeasurementUnit.Mil: dpu = defaultDpi / 1000; break; case MeasurementUnit.Centimeter: dpu = defaultDpi / CmPerInch; break; case MeasurementUnit.Millimeter: dpu = defaultDpi / CmPerInch / 10; break; case MeasurementUnit.Micron: dpu = defaultDpi / CmPerInch / 10000; break; case MeasurementUnit.Nano: dpu = defaultDpi / CmPerInch / 10000000; break; case MeasurementUnit.Pixel: dpu = 1.0; break; default: throw new InvalidEnumArgumentException("DpuUnit", (int)units, typeof(MeasurementUnit)); } return dpu; } /// /// 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). /// private void InitializeDpu() { this.DpuUnit = this.DpuUnit; this.DpuX = this.DpuX; this.DpuY = this.DpuY; } private 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); } /// /// Gets or sets the Document's dots-per-unit scale in the X direction. /// /// /// 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. /// public double DpuX { get { PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.XResolution); if (pis.Length == 0) { double defaultDpu = GetDefaultDpu(this.DpuUnit); this.DpuX = defaultDpu; return defaultDpu; } else { 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{} } else { return Math.Min(MaximumDpu, Math.Max(MinimumDpu, (double)numerator / (double)denominator)); } } catch { this.Metadata.RemoveExifValues(ExifTagID.XResolution); return this.DpuX; // recursive call; } } } set { if (value <= 0.0) { throw new ArgumentOutOfRangeException("value", value, "must be > 0.0"); } if (this.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); this.Metadata.ReplaceExifValues(ExifTagID.XResolution, new PropertyItem[1] { pi }); Dirty = true; } } /// /// Gets or sets the Document's dots-per-unit scale in the Y direction. /// /// /// 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. /// public double DpuY { get { PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.YResolution); if (pis.Length == 0) { // If there's no DpuY setting, default to the DpuX setting double dpu = this.DpuX; this.DpuY = dpu; return dpu; } else { 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{} } else { return Math.Min(MaximumDpu, Math.Max(MinimumDpu, (double)numerator / (double)denominator)); } } catch { this.Metadata.RemoveExifValues(ExifTagID.YResolution); return this.DpuY; // recursive call; } } } set { if (value <= 0.0) { throw new ArgumentOutOfRangeException("value", value, "must be > 0.0"); } if (this.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); this.Metadata.ReplaceExifValues(ExifTagID.YResolution, new PropertyItem[1] { pi }); Dirty = true; } } /// /// Gets the Document's measured physical width based on the DpuUnit and DpuX properties. /// public double PhysicalWidth { get { return (double)this.Width / (double)this.DpuX; } } /// /// Gets the Document's measured physical height based on the DpuUnit and DpuY properties. /// public double PhysicalHeight { get { return (double)this.Height / (double)this.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 = this.DpuUnit; if (resultUnit == dpuUnit) { result = pixel / this.DpuX; } else if (dpuUnit == MeasurementUnit.Pixel) { double defaultDpuX = GetDefaultDpu(dpuUnit); result = pixel / defaultDpuX; } else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch) { result = pixel / this.DpuX; // (CmPerInch * this.DpuX); } else if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Millimeter) { double defaultDpuY = GetDefaultDpu(MeasurementUnit.Millimeter); result = pixel / defaultDpuY; } else if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Micron) { double defaultDpuY = GetDefaultDpu(MeasurementUnit.Micron); result = pixel / defaultDpuY; } else if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Nano) { double defaultDpuY = GetDefaultDpu(MeasurementUnit.Nano); result = pixel / defaultDpuY; } else { result = pixel / this.DpuX; //(pixel * CmPerInch) / this.DpuX; } } return result; } /// /// 像素换算到物理长度 /// /// /// /// public double PixelToPhysicalY(double pixel, MeasurementUnit resultUnit) { double result; if (resultUnit == MeasurementUnit.Pixel) { result = pixel; } else { MeasurementUnit dpuUnit = this.DpuUnit; if (resultUnit == dpuUnit) { double defaultDpuY = GetDefaultDpu(dpuUnit); result = pixel / defaultDpuY; //result = pixel / this.DpuY; } else if (dpuUnit == MeasurementUnit.Pixel) { double defaultDpuY = GetDefaultDpu(dpuUnit); result = pixel / defaultDpuY; } else if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Mil) { double defaultDpuY = GetDefaultDpu(MeasurementUnit.Mil); result = pixel / defaultDpuY; // pixel / (CmPerInch * this.DpuY); } else if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter) { double defaultDpuY = GetDefaultDpu(MeasurementUnit.Centimeter); result = pixel / defaultDpuY; // pixel / (CmPerInch * this.DpuY); } else if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Millimeter) { double defaultDpuY = GetDefaultDpu(MeasurementUnit.Millimeter); result = pixel / defaultDpuY; } else if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Micron) { double defaultDpuY = GetDefaultDpu(MeasurementUnit.Micron); result = pixel / defaultDpuY; } else if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Nano) { double defaultDpuY = GetDefaultDpu(MeasurementUnit.Nano); result = pixel / defaultDpuY; } else { result = pixel / this.DpuX;//(pixel * CmPerInch) / this.DpuY; } } return result; } private static bool IsValidMeasurementUnit(MeasurementUnit unit) { switch (unit) { case MeasurementUnit.Pixel: case MeasurementUnit.Inch: case MeasurementUnit.Centimeter: case MeasurementUnit.Millimeter: case MeasurementUnit.Micron: case MeasurementUnit.Nano: 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 2. Simple mils -> centimeters if (sourceUnits == MeasurementUnit.Mil && resultDpuUnits == MeasurementUnit.Centimeter) { return MilsToCentimeters(sourceLength); } // Case 3. Simple centimeters -> inches. if (sourceUnits == MeasurementUnit.Centimeter && resultDpuUnits == MeasurementUnit.Inch) { return CentimetersToInches(sourceLength); } // Case 3. Simple centimeters -> inches. if (sourceUnits == MeasurementUnit.Centimeter && resultDpuUnits == MeasurementUnit.Mil) { return CentimetersToMils(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 7. Conversion from inches to pixels, when basis is in centimeters. if (sourceUnits == MeasurementUnit.Mil && basisDpuUnits == MeasurementUnit.Centimeter) { double dpi = DotsPerCmToDotsPerMil(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 8. Conversion from centimeters to pixels, when basis is in inches. if (sourceUnits == MeasurementUnit.Centimeter && basisDpuUnits == MeasurementUnit.Mil) { double dpcm = DotsPerMilToDotsPerCm(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.Inch: result = PdnResources.GetString("MeasurementUnit.Inch.Abbreviation"); break; case MeasurementUnit.Mil: result = PdnResources.GetString("MeasurementUnit.Mil.Abbreviation"); break; case MeasurementUnit.Centimeter: result = PdnResources.GetString("MeasurementUnit.Centimeter.Abbreviation"); break; case MeasurementUnit.Millimeter: result = PdnResources.GetString("MeasurementUnit.Centimeter.Abbreviation"); break; case MeasurementUnit.Micron: result = PdnResources.GetString("MeasurementUnit.Centimeter.Abbreviation"); break; case MeasurementUnit.Nano: result = PdnResources.GetString("MeasurementUnit.Centimeter.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"); } } public Version SavedWithVersion { get { if (disposed) { throw new ObjectDisposedException("Document"); } if (savedWith == null) { savedWith = PdnInfo.GetVersion(); } return savedWith; } } [field: NonSerialized] public event EventHandler DirtyChanged; private void OnDirtyChanged() { if (DirtyChanged != null) { DirtyChanged(this, EventArgs.Empty); } } /// /// 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. /// public bool Dirty { get { if (this.disposed) { throw new ObjectDisposedException("Document"); } return this.dirty; } set { if (this.disposed) { throw new ObjectDisposedException("Document"); } if (this.dirty != value) { this.dirty = value; OnDirtyChanged(); } } } /// /// Exposes a collection for access to the layers, and for manipulation of /// the way the document contains the layers (add/remove/move). /// /*public LayerList Layers { get { return null; if (disposed) { throw new ObjectDisposedException("Document"); } return layers; } }*/ /// /// Width of the document, in pixels. All contained layers must be this wide as well. /// public int Width { get { return width; } } /// /// Height of the document, in pixels. All contained layers must be this tall as well. /// public int Height { get { return height; } } /// /// The size of the document, in pixels. This is a convenience property that wraps up /// the Width and Height properties in one Size structure. /// public Size Size { get { return new Size(Width, Height); } } public Rectangle Bounds { get { return new Rectangle(0, 0, Width, Height); } } public Metadata Metadata { get { if (metadata == null) { metadata = new Metadata(userMetaData); } return metadata; } } public void ReplaceMetaDataFrom(Document other) { this.Metadata.ReplaceWithDataFrom(other.Metadata); } public void ClearMetaData() { this.Metadata.Clear(); } /// /// Clears a portion of a surface to transparent. /// /// The surface to partially clear /// The rectangle to clear private 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); } } /// /// Clears a portion of a surface to transparent. /// /// The surface to partially clear /// The array of Rectangles designating the areas to clear /// The start index within the rois array to clear /// The number of Rectangles in the rois array (staring with startIndex) to clear private void ClearBackground(Surface surface, Rectangle[] rois, int startIndex, int length) { for (int i = startIndex; i < startIndex + length; i++) { ClearBackground(surface, rois[i]); } } public void Render(RenderArgs args, bool clearBackground) { Render(args, args.Surface.Bounds, clearBackground); } /// /// Renders a requested region of the document. Will clear the background of the input /// before rendering if requested. /// /// Contains information used to control where rendering occurs. /// The rectangular region to render. /// If true, 'args' will be cleared to zero before rendering. public void Render(RenderArgs args, Rectangle roi, bool clearBackground) { /*int startIndex; if (clearBackground) { BitmapLayer layer0; layer0 = this.layers[0] as BitmapLayer; // 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 < this.layers.Count; ++i) { Layer layer = (Layer)this.layers[i]; if (layer.Visible) { layer.Render(args, roi); } }*/ } public void Render(RenderArgs args, Rectangle[] roi, int startIndex, int length, bool clearBackground) { /*int startLayerIndex; if (clearBackground) { BitmapLayer layer0; layer0 = this.layers[0] as BitmapLayer; // 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 < this.layers.Count; ++i) { Layer layer = (Layer)this.layers[i]; if (layer.Visible) { layer.RenderUnchecked(args, roi, startIndex, length); } }*/ } private sealed class UpdateScansContext { private Document document; private RenderArgs dst; private Rectangle[] scans; private int startIndex; private 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) { this.document = document; this.dst = dst; this.scans = scans; this.startIndex = startIndex; this.length = length; } } /// /// Constructs a blank document (zero layers) of the given width and height. /// /// /// public Document(int width, int height) { this.width = width; this.height = height; this.Dirty = true; this.updateRegion = new Vector(); //layers = new LayerList(this); //SetupEvents(); userMetaData = new NameValueCollection(); Invalidate(); } /// /// Sets up event handling for contained objects. /// /*private void SetupEvents() { layers.Changed += new EventHandler(LayerListChangedHandler); layers.Changing += new EventHandler(LayerListChangingHandler); layerInvalidatedDelegate = new InvalidateEventHandler(LayerInvalidatedHandler); foreach (Layer layer in layers) { layer.Invalidated += layerInvalidatedDelegate; } }*/ /// /// Called after deserialization occurs so that certain things that are non-serializable /// can be set up. /// /// public void OnDeserialization(object sender) { this.updateRegion = new Vector(); this.updateRegion.Add(this.Bounds); this.threadPool = new PaintDotNet.Threading.ThreadPool(); //SetupEvents(); Dirty = true; } [field: NonSerialized] public event InvalidateEventHandler Invalidated; /// /// Raises the Invalidated event. /// /// private void OnInvalidated(InvalidateEventArgs e) { if (Invalidated != null) { Invalidated(this, e); } } /*/// /// Handles the Changing event that is raised from the contained LayerList. /// /// /// private void LayerListChangingHandler(object sender, EventArgs e) { if (disposed) { throw new ObjectDisposedException("Document"); } foreach (Layer layer in Layers) { layer.Invalidated -= layerInvalidatedDelegate; } }*/ /*/// /// Handles the Changed event that is raised from the contained LayerList. /// /// /// private void LayerListChangedHandler(object sender, EventArgs e) { foreach (Layer layer in Layers) { layer.Invalidated += layerInvalidatedDelegate; } Invalidate(); }*/ /*/// /// Handles the Invalidated event that is raised from any contained Layer. /// /// /// private void LayerInvalidatedHandler(object sender, InvalidateEventArgs e) { Invalidate(e.InvalidRect); }*/ /// /// Causes the whole document to be invalidated, forcing a full rerender on /// the next call to Update. /// public void Invalidate() { Dirty = true; Rectangle rect = new Rectangle(0, 0, Width, Height); updateRegion.Clear(); updateRegion.Add(rect); OnInvalidated(new InvalidateEventArgs(rect)); } /*/// /// Invalidates a portion of the document. The given region is then tagged /// for rerendering during the next call to Update. /// /// The region of interest to be invalidated. public void Invalidate(Rectangle roi) { Dirty = true; Rectangle rect = Rectangle.Intersect(roi, this.Bounds); updateRegion.Add(rect); OnInvalidated(new InvalidateEventArgs(rect)); }*/ /*/// /// Clears the document's update region. This is called at the end of the /// Update method. /// private void Validate() { updateRegion.Clear(); }*/ /// /// Creates a document that consists of one BitmapLayer. /// /// The Image to make a copy of that will be the first layer ("Background") in the document. public static Document FromImage(Image image) { if (image == null) { throw new ArgumentNullException("image"); } Document document = new Document(image.Width, image.Height); Surface surface = new Surface(new Size(image.Width, image.Height)); surface.Clear(ColorBgra.FromBgra(0, 0, 0, 0)); surface.PixelFormat = image.PixelFormat; //BitmapLayer layer = Layer.CreateBackgroundLayer(image.Width, image.Height); //layer.Surface.Clear(ColorBgra.FromBgra(0, 0, 0, 0)); //layer.Surface.PixelFormat = image.PixelFormat; Bitmap 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) { uint* srcPtr = (uint*)((byte*)bData.Scan0.ToPointer() + (y * bData.Stride)); ColorBgra* dstPtr = 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 = surface.GetRowAddress(y); for (int x = 0; x < bData.Width; ++x) { byte b = *srcPtr; byte g = *(srcPtr + 1); byte r = *(srcPtr + 2); byte a = 255; *dstPtr = ColorBgra.FromBgra(b, g, r, a); srcPtr += 3; ++dstPtr; } } } finally { asBitmap.UnlockBits(bData); bData = null; } } } else { using (RenderArgs args = new RenderArgs(surface)) { args.Graphics.CompositingMode = CompositingMode.SourceCopy; args.Graphics.SmoothingMode = SmoothingMode.None; args.Graphics.DrawImage(image, args.Bounds, args.Bounds, GraphicsUnit.Pixel); } } // Console.Write(string.Format("Copy pixels end:{0};", DateTime.Now.Millisecond % 10000)); // Transfer metadata // Sometimes GDI+ does not honor the resolution tags that we // put in manually via the EXIF properties. document.DpuUnit = MeasurementUnit.Inch; document.DpuX = image.HorizontalResolution; document.DpuY = image.VerticalResolution; PropertyItem[] pis; try { pis = image.PropertyItems; } catch (Exception ex) { Tracing.Ping("Exception while retreiving image's PropertyItems: " + ex.ToString()); pis = null; } if (pis != null) { for (int i = 0; i < pis.Length; ++i) { document.Metadata.AddExifValues(new PropertyItem[] { pis[i] }); } } //document.Layers.Add(layer); document.surface = surface; document.Invalidate(); return document; } public static Document FromMat1(OpenCvSharp.Mat mat) { if (mat == null) { throw new ArgumentNullException("image"); } Image image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat); Bitmap asBitmap = image as Bitmap; Document document = new Document(mat.Width, mat.Height); Surface surface = new Surface(new Size(mat.Width, mat.Height)); surface.Clear(ColorBgra.FromBgra(0, 0, 0, 0)); surface.PixelFormat = asBitmap.PixelFormat; //BitmapLayer layer = Layer.CreateBackgroundLayer(mat.Width, mat.Height); //layer.Surface.Clear(ColorBgra.FromBgra(0, 0, 0, 0)); // 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) { uint* srcPtr = (uint*)((byte*)bData.Scan0.ToPointer() + (y * bData.Stride)); ColorBgra* dstPtr = 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 = surface.GetRowAddress(y); for (int x = 0; x < bData.Width; ++x) { byte b = *srcPtr; byte g = *(srcPtr + 1); byte r = *(srcPtr + 2); byte a = 255; *dstPtr = ColorBgra.FromBgra(b, g, r, a); srcPtr += 3; ++dstPtr; } } } finally { asBitmap.UnlockBits(bData); bData = null; } } } else { using (RenderArgs args = new RenderArgs(surface)) { args.Graphics.CompositingMode = CompositingMode.SourceCopy; args.Graphics.SmoothingMode = SmoothingMode.None; args.Graphics.DrawImage(image, args.Bounds, args.Bounds, GraphicsUnit.Pixel); } } // Transfer metadata // Sometimes GDI+ does not honor the resolution tags that we // put in manually via the EXIF properties. document.DpuUnit = MeasurementUnit.Inch; document.DpuX = image.HorizontalResolution; document.DpuY = image.VerticalResolution; PropertyItem[] pis; try { pis = image.PropertyItems; } catch (Exception ex) { Tracing.Ping("Exception while retreiving image's PropertyItems: " + ex.ToString()); pis = null; } if (pis != null) { for (int i = 0; i < pis.Length; ++i) { document.Metadata.AddExifValues(new PropertyItem[] { pis[i] }); } } document.surface = surface; //document.Layers.Add(layer); document.Invalidate(); return document; } public static Document FromByteArr(byte[] arr, int w, int h) { Document document = new Document(w, h); Surface surface = new Surface(new Size(w, h), PixelFormat.Format24bppRgb); Marshal.Copy(arr, 0, surface.Scan0.Pointer, arr.Length); document.surface = surface; document.Invalidate(); return document; } public unsafe static Document FromImageMat(OpenCvSharp.Mat mat) {//用于调试FromImage,完成后可以去掉该封装 return Document.FromMat(mat); } public unsafe static Document FromMat(OpenCvSharp.Mat mat) { if (mat == null) { throw new ArgumentNullException("image"); } if (mat.Channels() == 3) { OpenCvSharp.Cv2.CvtColor(mat, mat, OpenCvSharp.ColorConversionCodes.BGR2BGRA); } else if (mat.Channels() == 1) { OpenCvSharp.Cv2.CvtColor(mat, mat, OpenCvSharp.ColorConversionCodes.GRAY2BGRA); } Document document = new Document(mat.Width, mat.Height); Surface surface = new Surface(new Size(mat.Width, mat.Height)); surface.mat = mat; surface.scan0.VoidStar = (void*)mat.Data; surface.Stride = (int)mat.Step(); document.surface = surface; //document.Layers.Add(layer); document.Invalidate(); return document; /*if (mat.Channels() == 3) { OpenCvSharp.Cv2.CvtColor(mat, mat, OpenCvSharp.ColorConversionCodes.BGR2BGRA); } else if (mat.Channels() == 1) { OpenCvSharp.Cv2.CvtColor(mat, mat, OpenCvSharp.ColorConversionCodes.GRAY2BGRA); } Document document = new Document(mat.Width, mat.Height); Surface surface = new Surface(new Size(mat.Width, mat.Height)); byte[] arr = new byte[mat.Total() * mat.ElemSize()]; Marshal.Copy(mat.Data, arr, 0, arr.Length); Marshal.Copy(arr, 0, surface.Scan0.Pointer, arr.Length); document.surface = surface; document.Invalidate();*/ } public unsafe static Document FromImageMatForHistogram(OpenCvSharp.Mat mat, Surface oldSurface) { return Document.FromMatForHistogram(mat, oldSurface); } public unsafe static Document FromMatForHistogram(OpenCvSharp.Mat mat, Surface oldSurface) { if (mat == null) { throw new ArgumentNullException("image"); } if (mat.Channels() == 3) { OpenCvSharp.Cv2.CvtColor(mat, mat, OpenCvSharp.ColorConversionCodes.BGR2BGRA); } else if (mat.Channels() == 1) { OpenCvSharp.Cv2.CvtColor(mat, mat, OpenCvSharp.ColorConversionCodes.GRAY2BGRA); } Document document = new Document(mat.Width, mat.Height); Surface surface = new Surface(new Size(mat.Width, mat.Height)); surface.mat = mat; surface.BackUpMat = oldSurface.BackUpMat; surface.scan0.VoidStar = (void*)mat.Data; surface.Stride = (int)mat.Step(); document.surface = surface; document.Invalidate(); return document; } public static byte[] MagicBytes { get { return Encoding.UTF8.GetBytes("PDN3"); } } /// /// Deserializes a Document from a stream. /// /// The stream to deserialize from. This stream must be seekable. /// The Document that was stored in stream. /// /// This is the only supported way to deserialize a Document instance from disk. /// public static Document FromStream(Stream stream) { long oldPosition = stream.Position; bool pdn21Format = true; // Version 2.1+ file format: // Starts with bytes as defined by MagicBytes // Next three bytes are 24-bit unsigned int 'N' (first byte is low-word, second byte is middle-word, third byte is high word) // The next N bytes are a string, this is the document header (it is XML, UTF-8 encoded) // Important: 'N' indicates a byte count, not a character count. 'N' bytes may result in less than 'N' characters, // depending on how the characters decode as per UTF8 // If the next 2 bytes are 0x00, 0x01: This signifies that non-compressed .NET serialized data follows. // If the next 2 bytes are 0x1f, 0x8b: This signifies the start of the gzip compressed .NET serialized data // // Version 2.0 and previous file format: // Starts with 0x1f, 0x8b: this signifies the start of the gzip compressed .NET serialized data. // Read in the 'magic' bytes for (int i = 0; i < MagicBytes.Length; ++i) { int theByte = stream.ReadByte(); if (theByte == -1) { throw new EndOfStreamException(); } if (theByte != MagicBytes[i]) { pdn21Format = false; break; } } // Read in the header if we found the 'magic' bytes identifying a PDN 2.1 file XmlDocument headerXml = null; if (pdn21Format) { int low = stream.ReadByte(); if (low == -1) { throw new EndOfStreamException(); } int mid = stream.ReadByte(); if (mid == -1) { throw new EndOfStreamException(); } int high = stream.ReadByte(); if (high == -1) { throw new EndOfStreamException(); } int byteCount = low + (mid << 8) + (high << 16); byte[] bytes = new byte[byteCount]; int bytesRead = Utility.ReadFromStream(stream, bytes, 0, byteCount); if (bytesRead != byteCount) { throw new EndOfStreamException("expected " + byteCount + " bytes, but only got " + bytesRead); } string xml = Encoding.UTF8.GetString(bytes); headerXml = new XmlDocument(); headerXml.LoadXml(xml); } else { stream.Position = oldPosition; // rewind and try as v2.0-or-earlier file } // Start reading the data section of the file. Determine if it's gzip or regular long oldPosition2 = stream.Position; int first = stream.ReadByte(); if (first == -1) { throw new EndOfStreamException(); } int second = stream.ReadByte(); if (second == -1) { throw new EndOfStreamException(); } Document document; object docObject; BinaryFormatter formatter = new BinaryFormatter(); SerializationFallbackBinder sfb = new SerializationFallbackBinder(); sfb.AddAssembly(Assembly.GetExecutingAssembly()); // first try PaintDotNet.Data.dll sfb.AddAssembly(typeof(Utility).Assembly); // second, try PaintDotNet.Core.dll sfb.AddAssembly(typeof(SystemLayer.Memory).Assembly); // third, try PaintDotNet.SystemLayer.dll formatter.Binder = sfb; if (first == 0 && second == 1) { DeferredFormatter deferred = new DeferredFormatter(); formatter.Context = new StreamingContext(formatter.Context.State, deferred); docObject = formatter.UnsafeDeserialize(stream, null); deferred.FinishDeserialization(stream); } else if (first == 0x1f && second == 0x8b) { stream.Position = oldPosition2; // rewind to the start of 0x1f, 0x8b GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress, true); docObject = formatter.UnsafeDeserialize(gZipStream, null); } else { throw new FormatException("file is not a valid document"); } document = (Document)docObject; document.Dirty = true; document.headerXml = headerXml; document.Invalidate(); return document; } /// /// Saves the Document to the given Stream with only the default headers and no /// IO completion callback. /// /// The Stream to serialize the Document to. public void SaveToStream(Stream stream) { SaveToStream(stream, null); } /// /// Saves the Document to the given Stream with the default and given headers, and /// using the given IO completion callback. /// /// The Stream to serialize the Document to. /// /// This can be used to keep track of the number of uncompressed bytes that are written. The /// values reported through the IOEventArgs.Count+Offset will vary from 1 to approximately /// Layers.Count*Width*Height*sizeof(ColorBgra). The final number will actually be higher /// because of hierarchical overhead, so make sure to cap any progress reports to 100%. This /// callback will be wired to the IOFinished event of a SiphonStream. Events may be raised /// from any thread. May be null. /// public void SaveToStream(Stream stream, IOEventHandler callback) { InitializeDpu(); PrepareHeader(); string headerText = this.HeaderXml.OuterXml; // Write the header byte[] magicBytes = Document.MagicBytes; stream.Write(magicBytes, 0, magicBytes.Length); byte[] headerBytes = Encoding.UTF8.GetBytes(headerText); stream.WriteByte((byte)(headerBytes.Length & 0xff)); stream.WriteByte((byte)((headerBytes.Length & 0xff00) >> 8)); stream.WriteByte((byte)((headerBytes.Length & 0xff0000) >> 16)); stream.Write(headerBytes, 0, headerBytes.Length); stream.Flush(); // Copy version info this.savedWith = PdnInfo.GetVersion(); // Write 0x00, 0x01 to indicate normal .NET serialized data stream.WriteByte(0x00); stream.WriteByte(0x01); // Write the remainder of the file (gzip compressed) SiphonStream siphonStream = new SiphonStream(stream); BinaryFormatter formatter = new BinaryFormatter(); DeferredFormatter deferred = new DeferredFormatter(true, null); SaveProgressRelay relay = new SaveProgressRelay(deferred, callback); formatter.Context = new StreamingContext(formatter.Context.State, deferred); formatter.Serialize(siphonStream, this); deferred.FinishSerialization(siphonStream); stream.Flush(); } private class SaveProgressRelay { private DeferredFormatter formatter; private IOEventHandler ioCallback; private long lastReportedBytes; public SaveProgressRelay(DeferredFormatter formatter, IOEventHandler ioCallback) { this.formatter = formatter; this.ioCallback = ioCallback; this.formatter.ReportedBytesChanged += new EventHandler(Formatter_ReportedBytesChanged); } private void Formatter_ReportedBytesChanged(object sender, EventArgs e) { long reportedBytes = formatter.ReportedBytes; bool raiseEvent; long length = 0; lock (this) { raiseEvent = (reportedBytes > lastReportedBytes); if (raiseEvent) { length = reportedBytes - this.lastReportedBytes; this.lastReportedBytes = reportedBytes; } } if (raiseEvent && ioCallback != null) { ioCallback(this, new IOEventArgs(IOOperationType.Write, reportedBytes - length, (int)length)); } } } private void PrepareHeader() { XmlDocument xd = this.HeaderXml; XmlElement pdnImage = (XmlElement)xd.SelectSingleNode("/pdnImage"); pdnImage.SetAttribute("width", this.Width.ToString()); pdnImage.SetAttribute("height", this.Height.ToString()); pdnImage.SetAttribute("layers", "1");//this.Layers.Count.ToString() pdnImage.SetAttribute("savedWithVersion", this.SavedWithVersion.ToString(4)); } ~Document() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private bool disposed = false; private void Dispose(bool disposing) { if (!disposed) { if (disposing) { /*foreach (Layer layer in layers) { layer.Dispose(); }*/ } disposed = true; } } public Document Clone() { // I cheat. MemoryStream stream = new MemoryStream(); SaveToStream(stream); stream.Seek(0, SeekOrigin.Begin); return (Document)Document.FromStream(stream); } object ICloneable.Clone() { return Clone(); } } }