using System; using System.Collections.Specialized; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.Serialization; using System.Windows.Forms; namespace PaintDotNet { [Serializable] public sealed class StitchDocument : IDeserializationCallback, IDisposable { public StitchSurface surface; private int width; private int height; private NameValueCollection userMetaData; [NonSerialized] private Vector updateRegion; [NonSerialized] private bool dirty; [NonSerialized] private Metadata metadata = null; /// /// 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; /// /// 1英寸 = 2.54厘米 /// public static double CmPerInch = 2.54; /// /// 默认值 = dpi / 单位 /// public double defaultDpcm; public const double MinimumDpu = 0.01; public const double MaximumDpu = 32767.0; 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; } 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; } } /// /// 像素换算到物理长度 /// /// /// /// 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; } [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(); } } } /// /// 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; } } /// /// Constructs a blank document (zero layers) of the given width and height. /// /// /// public StitchDocument(int width, int height) { this.width = width; this.height = height; this.Dirty = true; this.updateRegion = new Vector(); userMetaData = new NameValueCollection(); Invalidate(); } /// /// 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); Dirty = true; } [field: NonSerialized] public event InvalidateEventHandler Invalidated; /// /// Raises the Invalidated event. /// /// private void OnInvalidated(InvalidateEventArgs e) { if (Invalidated != null) { Invalidated(this, e); } } /// /// 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)); } public unsafe static StitchDocument FromMat(OpenCvSharp.Mat mat) { if (mat == null) throw new ArgumentNullException("image"); //Console.WriteLine("size:" + new ColorBgra()); //Console.WriteLine("size2:" + sizeof(ColorBgrB)); StitchDocument document = new StitchDocument(mat.Width, mat.Height); if (mat.Channels() == 4) { OpenCvSharp.Cv2.CvtColor(mat, mat, OpenCvSharp.ColorConversionCodes.BGRA2BGR); } StitchSurface surface = new StitchSurface(new Size(mat.Width, mat.Height), mat.Channels(), mat); //surface.Clear(ColorBgrB.FromColor(Color.Red)); surface.PixelFormat = mat.Channels() == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format32bppArgb; surface.scan0.VoidStar = (void*)mat.Data; surface.Stride = (int)mat.Step(); document.surface = surface; document.Invalidate(); return document; } ~StitchDocument() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private bool disposed = false; private void Dispose(bool disposing) { if (!disposed) disposed = true; } } }