PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Data/Document.cs

https://bitbucket.org/tcz001/openpdn
C# | 1816 lines | 1293 code | 250 blank | 273 comment | 222 complexity | 4ddf0e318d62373d5a92c4c3ddd7867c MD5 | raw file
Possible License(s): Unlicense

Large files files are truncated, but you can click here to view the full file

  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using PaintDotNet.SystemLayer;
  10. using System;
  11. using System.Collections;
  12. using System.Collections.Specialized;
  13. using System.ComponentModel;
  14. using System.Drawing;
  15. using System.Drawing.Drawing2D;
  16. using System.Drawing.Imaging;
  17. using System.Globalization;
  18. using System.IO;
  19. using System.IO.Compression;
  20. using System.Reflection;
  21. using System.Runtime.Remoting.Messaging;
  22. using System.Runtime.Serialization;
  23. using System.Runtime.Serialization.Formatters.Binary;
  24. using System.Text;
  25. using System.Threading;
  26. using System.Windows.Forms;
  27. using System.Xml;
  28. namespace PaintDotNet
  29. {
  30. [Serializable]
  31. public sealed class Document
  32. : IDeserializationCallback,
  33. IDisposable,
  34. ICloneable
  35. {
  36. private LayerList layers;
  37. private int width;
  38. private int height;
  39. private NameValueCollection userMetaData;
  40. [NonSerialized]
  41. private PaintDotNet.Threading.ThreadPool threadPool = new PaintDotNet.Threading.ThreadPool();
  42. [NonSerialized]
  43. private InvalidateEventHandler layerInvalidatedDelegate;
  44. // TODO: the document class should not manage its own update region, its owner should
  45. [NonSerialized]
  46. private Vector<Rectangle> updateRegion;
  47. [NonSerialized]
  48. private bool dirty;
  49. private Version savedWith;
  50. [NonSerialized]
  51. private Metadata metadata = null;
  52. [NonSerialized]
  53. private XmlDocument headerXml;
  54. private const string headerXmlSkeleton = "<pdnImage><custom></custom></pdnImage>";
  55. private XmlDocument HeaderXml
  56. {
  57. get
  58. {
  59. if (this.disposed)
  60. {
  61. throw new ObjectDisposedException("Document");
  62. }
  63. if (this.headerXml == null)
  64. {
  65. this.headerXml = new XmlDocument();
  66. this.headerXml.LoadXml(headerXmlSkeleton);
  67. }
  68. return this.headerXml;
  69. }
  70. }
  71. public string Header
  72. {
  73. get
  74. {
  75. if (this.disposed)
  76. {
  77. throw new ObjectDisposedException("Document");
  78. }
  79. return this.HeaderXml.OuterXml;
  80. }
  81. }
  82. public string CustomHeaders
  83. {
  84. get
  85. {
  86. if (this.disposed)
  87. {
  88. throw new ObjectDisposedException("Document");
  89. }
  90. return this.HeaderXml.SelectSingleNode("/pdnImage/custom").InnerXml;
  91. }
  92. set
  93. {
  94. if (this.disposed)
  95. {
  96. throw new ObjectDisposedException("Document");
  97. }
  98. this.HeaderXml.SelectSingleNode("/pdnImage/custom").InnerXml = value;
  99. Dirty = true;
  100. }
  101. }
  102. /// <summary>
  103. /// Gets or sets the units that are used for measuring the document's physical (printed) size.
  104. /// </summary>
  105. /// <remarks>
  106. /// If this property is set to MeasurementUnit.Pixel, then Dpu will be reset to 1.
  107. /// If this property has not been set in the image's metadata, its default value
  108. /// will be MeasurementUnit.Inch.
  109. /// If the EXIF data for the image is invalid (such as "ResolutionUnit = 0" or something),
  110. /// then the default DpuUnit will be returned.
  111. /// </remarks>
  112. public MeasurementUnit DpuUnit
  113. {
  114. get
  115. {
  116. PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.ResolutionUnit);
  117. if (pis.Length == 0)
  118. {
  119. this.DpuUnit = DefaultDpuUnit;
  120. return DefaultDpuUnit;
  121. }
  122. else
  123. {
  124. try
  125. {
  126. ushort unit = Exif.DecodeShortValue(pis[0]);
  127. // Guard against bad data in the EXIF store
  128. switch ((MeasurementUnit)unit)
  129. {
  130. case MeasurementUnit.Centimeter:
  131. case MeasurementUnit.Inch:
  132. case MeasurementUnit.Pixel:
  133. return (MeasurementUnit)unit;
  134. default:
  135. this.Metadata.RemoveExifValues(ExifTagID.ResolutionUnit);
  136. return this.DpuUnit; // recursive call
  137. }
  138. }
  139. catch (Exception)
  140. {
  141. this.Metadata.RemoveExifValues(ExifTagID.ResolutionUnit);
  142. return this.DpuUnit; // recursive call
  143. }
  144. }
  145. }
  146. set
  147. {
  148. PropertyItem pi = Exif.CreateShort(ExifTagID.ResolutionUnit, (ushort)value);
  149. this.Metadata.ReplaceExifValues(ExifTagID.ResolutionUnit, new PropertyItem[1] { pi });
  150. if (value == MeasurementUnit.Pixel)
  151. {
  152. this.DpuX = 1.0;
  153. this.DpuY = 1.0;
  154. }
  155. Dirty = true;
  156. }
  157. }
  158. public static MeasurementUnit DefaultDpuUnit
  159. {
  160. get
  161. {
  162. return MeasurementUnit.Inch;
  163. }
  164. }
  165. #if false
  166. [Obsolete("Use DefaultDpuUnit property instead.")]
  167. public static MeasurementUnit GetDefaultDpuUnit()
  168. {
  169. return DefaultDpuUnit;
  170. }
  171. #endif
  172. private const double defaultDpi = 96.0;
  173. public static double DefaultDpi
  174. {
  175. get
  176. {
  177. return defaultDpi;
  178. }
  179. }
  180. public const double CmPerInch = 2.54;
  181. private const double defaultDpcm = defaultDpi / CmPerInch;
  182. public static double DefaultDpcm
  183. {
  184. get
  185. {
  186. return defaultDpcm;
  187. }
  188. }
  189. public const double MinimumDpu = 0.01;
  190. public const double MaximumDpu = 32767.0;
  191. public static double InchesToCentimeters(double inches)
  192. {
  193. return inches * CmPerInch;
  194. }
  195. public static double CentimetersToInches(double centimeters)
  196. {
  197. return centimeters / CmPerInch;
  198. }
  199. public static double DotsPerInchToDotsPerCm(double dpi)
  200. {
  201. return dpi / CmPerInch;
  202. }
  203. public static double DotsPerCmToDotsPerInch(double dpcm)
  204. {
  205. return dpcm * CmPerInch;
  206. }
  207. public static double GetDefaultDpu(MeasurementUnit units)
  208. {
  209. double dpu;
  210. switch (units)
  211. {
  212. case MeasurementUnit.Inch:
  213. dpu = defaultDpi;
  214. break;
  215. case MeasurementUnit.Centimeter:
  216. dpu = defaultDpcm;
  217. break;
  218. case MeasurementUnit.Pixel:
  219. dpu = 1.0;
  220. break;
  221. default:
  222. throw new InvalidEnumArgumentException("DpuUnit", (int)units, typeof(MeasurementUnit));
  223. }
  224. return dpu;
  225. }
  226. /// <summary>
  227. /// Ensures that the document's DpuX, DpuY, and DpuUnits properties are set.
  228. /// If they are not already set, they are initialized to their default values (96, 96 , inches).
  229. /// </summary>
  230. private void InitializeDpu()
  231. {
  232. this.DpuUnit = this.DpuUnit;
  233. this.DpuX = this.DpuX;
  234. this.DpuY = this.DpuY;
  235. }
  236. private byte[] GetDoubleAsRationalExifData(double value)
  237. {
  238. uint numerator;
  239. uint denominator;
  240. if (Math.IEEERemainder(value, 1.0) == 0)
  241. {
  242. numerator = (uint)value;
  243. denominator = 1;
  244. }
  245. else
  246. {
  247. double s = value * 1000.0;
  248. numerator = (uint)Math.Floor(s);
  249. denominator = 1000;
  250. }
  251. return Exif.EncodeRationalValue(numerator, denominator);
  252. }
  253. /// <summary>
  254. /// Gets or sets the Document's dots-per-unit scale in the X direction.
  255. /// </summary>
  256. /// <remarks>
  257. /// If DpuUnit is equal to MeasurementUnit.Pixel, then this property may not be set
  258. /// to any value other than 1.0. Setting DpuUnit to MeasurementUnit.Pixel will reset
  259. /// this property to 1.0. This property may only be set to a value greater than 0.
  260. /// One dot is always equal to one pixel. This property will not return a value less
  261. /// than MinimumDpu, nor a value larger than MaximumDpu.
  262. /// </remarks>
  263. public double DpuX
  264. {
  265. get
  266. {
  267. PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.XResolution);
  268. if (pis.Length == 0)
  269. {
  270. double defaultDpu = GetDefaultDpu(this.DpuUnit);
  271. this.DpuX = defaultDpu;
  272. return defaultDpu;
  273. }
  274. else
  275. {
  276. try
  277. {
  278. uint numerator;
  279. uint denominator;
  280. Exif.DecodeRationalValue(pis[0], out numerator, out denominator);
  281. if (denominator == 0)
  282. {
  283. throw new DivideByZeroException(); // will be caught by the below catch{}
  284. }
  285. else
  286. {
  287. return Math.Min(MaximumDpu, Math.Max(MinimumDpu, (double)numerator / (double)denominator));
  288. }
  289. }
  290. catch
  291. {
  292. this.Metadata.RemoveExifValues(ExifTagID.XResolution);
  293. return this.DpuX; // recursive call;
  294. }
  295. }
  296. }
  297. set
  298. {
  299. if (value <= 0.0)
  300. {
  301. throw new ArgumentOutOfRangeException("value", value, "must be > 0.0");
  302. }
  303. if (this.DpuUnit == MeasurementUnit.Pixel && value != 1.0)
  304. {
  305. throw new ArgumentOutOfRangeException("value", value, "if DpuUnit == Pixel, then value must equal 1.0");
  306. }
  307. byte[] data = GetDoubleAsRationalExifData(value);
  308. PropertyItem pi = Exif.CreatePropertyItem(ExifTagID.XResolution, ExifTagType.Rational, data);
  309. this.Metadata.ReplaceExifValues(ExifTagID.XResolution, new PropertyItem[1] { pi });
  310. Dirty = true;
  311. }
  312. }
  313. /// <summary>
  314. /// Gets or sets the Document's dots-per-unit scale in the Y direction.
  315. /// </summary>
  316. /// <remarks>
  317. /// If DpuUnit is equal to MeasurementUnit.Pixel, then this property may not be set
  318. /// to any value other than 1.0. Setting DpuUnit to MeasurementUnit.Pixel will reset
  319. /// this property to 1.0. This property may only be set to a value greater than 0.
  320. /// One dot is always equal to one pixel. This property will not return a value less
  321. /// than MinimumDpu, nor a value larger than MaximumDpu.
  322. /// </remarks>
  323. public double DpuY
  324. {
  325. get
  326. {
  327. PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.YResolution);
  328. if (pis.Length == 0)
  329. {
  330. // If there's no DpuY setting, default to the DpuX setting
  331. double dpu = this.DpuX;
  332. this.DpuY = dpu;
  333. return dpu;
  334. }
  335. else
  336. {
  337. try
  338. {
  339. uint numerator;
  340. uint denominator;
  341. Exif.DecodeRationalValue(pis[0], out numerator, out denominator);
  342. if (denominator == 0)
  343. {
  344. throw new DivideByZeroException(); // will be caught by the below catch{}
  345. }
  346. else
  347. {
  348. return Math.Min(MaximumDpu, Math.Max(MinimumDpu, (double)numerator / (double)denominator));
  349. }
  350. }
  351. catch
  352. {
  353. this.Metadata.RemoveExifValues(ExifTagID.YResolution);
  354. return this.DpuY; // recursive call;
  355. }
  356. }
  357. }
  358. set
  359. {
  360. if (value <= 0.0)
  361. {
  362. throw new ArgumentOutOfRangeException("value", value, "must be > 0.0");
  363. }
  364. if (this.DpuUnit == MeasurementUnit.Pixel && value != 1.0)
  365. {
  366. throw new ArgumentOutOfRangeException("value", value, "if DpuUnit == Pixel, then value must equal 1.0");
  367. }
  368. byte[] data = GetDoubleAsRationalExifData(value);
  369. PropertyItem pi = Exif.CreatePropertyItem(ExifTagID.YResolution, ExifTagType.Rational, data);
  370. this.Metadata.ReplaceExifValues(ExifTagID.YResolution, new PropertyItem[1] { pi });
  371. Dirty = true;
  372. }
  373. }
  374. /// <summary>
  375. /// Gets the Document's measured physical width based on the DpuUnit and DpuX properties.
  376. /// </summary>
  377. public double PhysicalWidth
  378. {
  379. get
  380. {
  381. return (double)this.Width / (double)this.DpuX;
  382. }
  383. }
  384. /// <summary>
  385. /// Gets the Document's measured physical height based on the DpuUnit and DpuY properties.
  386. /// </summary>
  387. public double PhysicalHeight
  388. {
  389. get
  390. {
  391. return (double)this.Height / (double)this.DpuY;
  392. }
  393. }
  394. //
  395. // Conversion Matrix:
  396. //
  397. // GetPhysical[X|Y](x, unit), where dpu = this.dpuX or dpuY
  398. //
  399. // dpu | px | in | cm |
  400. // unit | | | |
  401. // -------------+------+------+------------+
  402. // px | x | x | x |
  403. // -------------+------+------+------------+
  404. // in | x / | x / | x / |
  405. // | 96 | dpuX | (dpuX*2.54)|
  406. // -------------+------+------+------------+
  407. // cm | x / |x*2.54| x / dpuX |
  408. // | 37.8| /dpuX| |
  409. // -------------+------+------+------------+
  410. public static double PixelToPhysical(double pixel, MeasurementUnit resultUnit, MeasurementUnit dpuUnit, double dpu)
  411. {
  412. double result;
  413. if (resultUnit == MeasurementUnit.Pixel)
  414. {
  415. result = pixel;
  416. }
  417. else
  418. {
  419. if (resultUnit == dpuUnit)
  420. {
  421. result = pixel / dpu;
  422. }
  423. else if (dpuUnit == MeasurementUnit.Pixel)
  424. {
  425. double defaultDpu = GetDefaultDpu(dpuUnit);
  426. result = pixel / defaultDpu;
  427. }
  428. else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch)
  429. {
  430. result = pixel / (CmPerInch * dpu);
  431. }
  432. else // if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter)
  433. {
  434. result = (pixel * CmPerInch) / dpu;
  435. }
  436. }
  437. return result;
  438. }
  439. public double PixelToPhysicalX(double pixel, MeasurementUnit resultUnit)
  440. {
  441. double result;
  442. if (resultUnit == MeasurementUnit.Pixel)
  443. {
  444. result = pixel;
  445. }
  446. else
  447. {
  448. MeasurementUnit dpuUnit = this.DpuUnit;
  449. if (resultUnit == dpuUnit)
  450. {
  451. result = pixel / this.DpuX;
  452. }
  453. else if (dpuUnit == MeasurementUnit.Pixel)
  454. {
  455. double defaultDpuX = GetDefaultDpu(dpuUnit);
  456. result = pixel / defaultDpuX;
  457. }
  458. else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch)
  459. {
  460. result = pixel / (CmPerInch * this.DpuX);
  461. }
  462. else //if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter)
  463. {
  464. result = (pixel * CmPerInch) / this.DpuX;
  465. }
  466. }
  467. return result;
  468. }
  469. public double PixelToPhysicalY(double pixel, MeasurementUnit resultUnit)
  470. {
  471. double result;
  472. if (resultUnit == MeasurementUnit.Pixel)
  473. {
  474. result = pixel;
  475. }
  476. else
  477. {
  478. MeasurementUnit dpuUnit = this.DpuUnit;
  479. if (resultUnit == dpuUnit)
  480. {
  481. result = pixel / this.DpuY;
  482. }
  483. else if (dpuUnit == MeasurementUnit.Pixel)
  484. {
  485. double defaultDpuY = GetDefaultDpu(dpuUnit);
  486. result = pixel / defaultDpuY;
  487. }
  488. else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch)
  489. {
  490. result = pixel / (CmPerInch * this.DpuY);
  491. }
  492. else //if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter)
  493. {
  494. result = (pixel * CmPerInch) / this.DpuY;
  495. }
  496. }
  497. return result;
  498. }
  499. private static bool IsValidMeasurementUnit(MeasurementUnit unit)
  500. {
  501. switch (unit)
  502. {
  503. case MeasurementUnit.Pixel:
  504. case MeasurementUnit.Centimeter:
  505. case MeasurementUnit.Inch:
  506. return true;
  507. default:
  508. return false;
  509. }
  510. }
  511. public static double ConvertMeasurement(
  512. double sourceLength,
  513. MeasurementUnit sourceUnits,
  514. MeasurementUnit basisDpuUnits,
  515. double basisDpu,
  516. MeasurementUnit resultDpuUnits)
  517. {
  518. // Validation
  519. if (!IsValidMeasurementUnit(sourceUnits))
  520. {
  521. throw new InvalidEnumArgumentException("sourceUnits", (int)sourceUnits, typeof(MeasurementUnit));
  522. }
  523. if (!IsValidMeasurementUnit(basisDpuUnits))
  524. {
  525. throw new InvalidEnumArgumentException("basisDpuUnits", (int)basisDpuUnits, typeof(MeasurementUnit));
  526. }
  527. if (!IsValidMeasurementUnit(resultDpuUnits))
  528. {
  529. throw new InvalidEnumArgumentException("resultDpuUnits", (int)resultDpuUnits, typeof(MeasurementUnit));
  530. }
  531. if (basisDpuUnits == MeasurementUnit.Pixel && basisDpu != 1.0)
  532. {
  533. throw new ArgumentOutOfRangeException("basisDpuUnits, basisDpu", "if basisDpuUnits is Pixel, then basisDpu must equal 1.0");
  534. }
  535. // Case 1. No conversion is necessary if they want the same units out.
  536. if (sourceUnits == resultDpuUnits)
  537. {
  538. return sourceLength;
  539. }
  540. // Case 2. Simple inches -> centimeters
  541. if (sourceUnits == MeasurementUnit.Inch && resultDpuUnits == MeasurementUnit.Centimeter)
  542. {
  543. return InchesToCentimeters(sourceLength);
  544. }
  545. // Case 3. Simple centimeters -> inches.
  546. if (sourceUnits == MeasurementUnit.Centimeter && resultDpuUnits == MeasurementUnit.Inch)
  547. {
  548. return CentimetersToInches(sourceLength);
  549. }
  550. // At this point we know we are converting from non-pixels to pixels, or from pixels
  551. // to non-pixels.
  552. // Cases 4 through 8 cover conversion from non-pixels to pixels.
  553. // Cases 9 through 11 cover conversion from pixels to non-pixels.
  554. // Case 4. Conversion from pixels to inches/centimeters when basis is in pixels too.
  555. // This means we must use the default DPU for the desired result measurement.
  556. // No need to compare lengthUnits != resultDpuUnits, since we already know this to
  557. // be true from case 1.
  558. if (sourceUnits == MeasurementUnit.Pixel && basisDpuUnits == MeasurementUnit.Pixel)
  559. {
  560. double dpu = GetDefaultDpu(resultDpuUnits);
  561. double lengthInOrCm = sourceLength / dpu;
  562. return lengthInOrCm;
  563. }
  564. // Case 5. Conversion from inches/centimeters to pixels when basis is in pixels too.
  565. // This means we must use the default DPU for the given input measurement.
  566. if (sourceUnits != MeasurementUnit.Pixel && basisDpuUnits == MeasurementUnit.Pixel)
  567. {
  568. double dpu = GetDefaultDpu(sourceUnits);
  569. double resultPx = sourceLength * dpu;
  570. return resultPx;
  571. }
  572. // Case 6. Conversion from inches/centimeters to pixels, when basis is in same units as input.
  573. if (sourceUnits == basisDpuUnits && resultDpuUnits == MeasurementUnit.Pixel)
  574. {
  575. double resultPx = sourceLength * basisDpu;
  576. return resultPx;
  577. }
  578. // Case 7. Conversion from inches to pixels, when basis is in centimeters.
  579. if (sourceUnits == MeasurementUnit.Inch && basisDpuUnits == MeasurementUnit.Centimeter)
  580. {
  581. double dpi = DotsPerCmToDotsPerInch(basisDpu);
  582. double resultPx = sourceLength * dpi;
  583. return resultPx;
  584. }
  585. // Case 8. Conversion from centimeters to pixels, when basis is in inches.
  586. if (sourceUnits == MeasurementUnit.Centimeter && basisDpuUnits == MeasurementUnit.Inch)
  587. {
  588. double dpcm = DotsPerInchToDotsPerCm(basisDpu);
  589. double resultPx = sourceLength * dpcm;
  590. return resultPx;
  591. }
  592. // Case 9. Converting from pixels to inches/centimeters, when the basis and result
  593. // units are the same.
  594. if (basisDpuUnits == resultDpuUnits)
  595. {
  596. double resultInOrCm = sourceLength / basisDpu;
  597. return resultInOrCm;
  598. }
  599. // Case 10. Converting from pixels to centimeters, when the basis is in inches.
  600. if (resultDpuUnits == MeasurementUnit.Centimeter && basisDpuUnits == MeasurementUnit.Inch)
  601. {
  602. double dpcm = DotsPerInchToDotsPerCm(basisDpu);
  603. double resultCm = sourceLength / dpcm;
  604. return resultCm;
  605. }
  606. // Case 11. Converting from pixels to inches, when the basis is in centimeters.
  607. if (resultDpuUnits == MeasurementUnit.Inch && basisDpuUnits == MeasurementUnit.Centimeter)
  608. {
  609. double dpi = DotsPerCmToDotsPerInch(basisDpu);
  610. double resultIn = sourceLength / dpi;
  611. return resultIn;
  612. }
  613. // Should not be possible to get here, but must appease the compiler.
  614. throw new InvalidOperationException();
  615. }
  616. public double PixelAreaToPhysicalArea(double area, MeasurementUnit resultUnit)
  617. {
  618. double xScale = PixelToPhysicalX(1.0, resultUnit);
  619. double yScale = PixelToPhysicalY(1.0, resultUnit);
  620. return area * xScale * yScale;
  621. }
  622. private static string GetUnitsAbbreviation(MeasurementUnit units)
  623. {
  624. string result;
  625. switch (units)
  626. {
  627. case MeasurementUnit.Pixel:
  628. result = string.Empty;
  629. break;
  630. case MeasurementUnit.Centimeter:
  631. result = PdnResources.GetString("MeasurementUnit.Centimeter.Abbreviation");
  632. break;
  633. case MeasurementUnit.Inch:
  634. result = PdnResources.GetString("MeasurementUnit.Inch.Abbreviation");
  635. break;
  636. default:
  637. throw new InvalidEnumArgumentException("MeasurementUnit was invalid");
  638. }
  639. return result;
  640. }
  641. public void CoordinatesToStrings(MeasurementUnit units, int x, int y, out string xString, out string yString, out string unitsString)
  642. {
  643. string unitsAbbreviation = GetUnitsAbbreviation(units);
  644. unitsString = GetUnitsAbbreviation(units);
  645. if (units == MeasurementUnit.Pixel)
  646. {
  647. xString = x.ToString();
  648. yString = y.ToString();
  649. }
  650. else
  651. {
  652. double physicalX = PixelToPhysicalX(x, units);
  653. xString = physicalX.ToString("F2");
  654. double physicalY = PixelToPhysicalY(y, units);
  655. yString = physicalY.ToString("F2");
  656. }
  657. }
  658. /// <summary>
  659. /// This is provided for future use.
  660. /// If you want to add new stuff that must be serialized, create a new class,
  661. /// then point 'tag' to a new instance of this class that is initialized
  662. /// during construction. Make sure the new class has a 'tag' variable as well.
  663. /// We effectively set up a 'linked list' where new versions of the code
  664. /// can open old versions of the document, as .NET serialization is fickle in
  665. /// certain areas. You might also add a new property to simplify using
  666. /// this stuff...
  667. /// public DocumentVersion2Data DocV2Data { get { return (DocumentVersion2Data)tag; } }
  668. /// </summary>
  669. // In practice, this has never been used, and .NET 2.0+ has better facilities for adding
  670. // new data to a serialization schema. Therefore, marking as obsolete.
  671. [Obsolete]
  672. private object tag = null;
  673. /// <summary>
  674. /// Reports the version of Paint.NET that this file was saved with.
  675. /// This is reset when SaveToStream is used. This can be used to
  676. /// determine file format compatibility if necessary.
  677. /// </summary>
  678. public Version SavedWithVersion
  679. {
  680. get
  681. {
  682. if (disposed)
  683. {
  684. throw new ObjectDisposedException("Document");
  685. }
  686. if (savedWith == null)
  687. {
  688. savedWith = PdnInfo.GetVersion();
  689. }
  690. return savedWith;
  691. }
  692. }
  693. [field: NonSerialized]
  694. public event EventHandler DirtyChanged;
  695. private void OnDirtyChanged()
  696. {
  697. if (DirtyChanged != null)
  698. {
  699. DirtyChanged(this, EventArgs.Empty);
  700. }
  701. }
  702. /// <summary>
  703. /// Keeps track of whether the document has changed at all since it was last opened
  704. /// or saved. This is something that is not reset to true by any method in the Document
  705. /// class, but is set to false anytime anything is changed.
  706. /// This way we can prompt the user to save a changed document when they go to quit.
  707. /// </summary>
  708. public bool Dirty
  709. {
  710. get
  711. {
  712. if (this.disposed)
  713. {
  714. throw new ObjectDisposedException("Document");
  715. }
  716. return this.dirty;
  717. }
  718. set
  719. {
  720. if (this.disposed)
  721. {
  722. throw new ObjectDisposedException("Document");
  723. }
  724. if (this.dirty != value)
  725. {
  726. this.dirty = value;
  727. OnDirtyChanged();
  728. }
  729. }
  730. }
  731. /// <summary>
  732. /// Exposes a collection for access to the layers, and for manipulation of
  733. /// the way the document contains the layers (add/remove/move).
  734. /// </summary>
  735. public LayerList Layers
  736. {
  737. get
  738. {
  739. if (disposed)
  740. {
  741. throw new ObjectDisposedException("Document");
  742. }
  743. return layers;
  744. }
  745. }
  746. /// <summary>
  747. /// Width of the document, in pixels. All contained layers must be this wide as well.
  748. /// </summary>
  749. public int Width
  750. {
  751. get
  752. {
  753. return width;
  754. }
  755. }
  756. /// <summary>
  757. /// Height of the document, in pixels. All contained layers must be this tall as well.
  758. /// </summary>
  759. public int Height
  760. {
  761. get
  762. {
  763. return height;
  764. }
  765. }
  766. /// <summary>
  767. /// The size of the document, in pixels. This is a convenience property that wraps up
  768. /// the Width and Height properties in one Size structure.
  769. /// </summary>
  770. public Size Size
  771. {
  772. get
  773. {
  774. return new Size(Width, Height);
  775. }
  776. }
  777. public Rectangle Bounds
  778. {
  779. get
  780. {
  781. return new Rectangle(0, 0, Width, Height);
  782. }
  783. }
  784. public Metadata Metadata
  785. {
  786. get
  787. {
  788. if (metadata == null)
  789. {
  790. metadata = new Metadata(userMetaData);
  791. }
  792. return metadata;
  793. }
  794. }
  795. public void ReplaceMetaDataFrom(Document other)
  796. {
  797. this.Metadata.ReplaceWithDataFrom(other.Metadata);
  798. }
  799. public void ClearMetaData()
  800. {
  801. this.Metadata.Clear();
  802. }
  803. [Obsolete("don't use this property; implementors should expose type-safe properties instead", false)]
  804. // Note, we can not remove this property because then the compiler complains that 'tag' is unused.
  805. public object Tag
  806. {
  807. get
  808. {
  809. return this.tag;
  810. }
  811. set
  812. {
  813. this.tag = value;
  814. }
  815. }
  816. /// <summary>
  817. /// Clears a portion of a surface to transparent.
  818. /// </summary>
  819. /// <param name="surface">The surface to partially clear</param>
  820. /// <param name="roi">The rectangle to clear</param>
  821. private unsafe void ClearBackground(Surface surface, Rectangle roi)
  822. {
  823. roi.Intersect(surface.Bounds);
  824. for (int y = roi.Top; y < roi.Bottom; y++)
  825. {
  826. ColorBgra *ptr = surface.GetPointAddressUnchecked(roi.Left, y);
  827. Memory.SetToZero(ptr, (ulong)roi.Width * ColorBgra.SizeOf);
  828. }
  829. }
  830. /// <summary>
  831. /// Clears a portion of a surface to transparent.
  832. /// </summary>
  833. /// <param name="surface">The surface to partially clear</param>
  834. /// <param name="rois">The array of Rectangles designating the areas to clear</param>
  835. /// <param name="startIndex">The start index within the rois array to clear</param>
  836. /// <param name="length">The number of Rectangles in the rois array (staring with startIndex) to clear</param>
  837. private void ClearBackground(Surface surface, Rectangle[] rois, int startIndex, int length)
  838. {
  839. for (int i = startIndex; i < startIndex + length; i++)
  840. {
  841. ClearBackground(surface, rois[i]);
  842. }
  843. }
  844. public void Render(RenderArgs args)
  845. {
  846. Render(args, args.Surface.Bounds);
  847. }
  848. public void Render(RenderArgs args, Rectangle roi)
  849. {
  850. Render(args, roi, false);
  851. }
  852. public void Render(RenderArgs args, bool clearBackground)
  853. {
  854. Render(args, args.Surface.Bounds, clearBackground);
  855. }
  856. /// <summary>
  857. /// Renders a requested region of the document. Will clear the background of the input
  858. /// before rendering if requested.
  859. /// </summary>
  860. /// <param name="args">Contains information used to control where rendering occurs.</param>
  861. /// <param name="roi">The rectangular region to render.</param>
  862. /// <param name="clearBackground">If true, 'args' will be cleared to zero before rendering.</param>
  863. public void Render(RenderArgs args, Rectangle roi, bool clearBackground)
  864. {
  865. int startIndex;
  866. if (clearBackground)
  867. {
  868. BitmapLayer layer0;
  869. layer0 = this.layers[0] as BitmapLayer;
  870. // Special case: if the first layer is a visible BitmapLayer with full opacity using
  871. // the default blend op, we can just copy the pixels straight over
  872. if (layer0 != null &&
  873. layer0.Visible &&
  874. layer0.Opacity == 255 &&
  875. layer0.BlendOp.GetType() == UserBlendOps.GetDefaultBlendOp())
  876. {
  877. args.Surface.CopySurface(layer0.Surface);
  878. startIndex = 1;
  879. }
  880. else
  881. {
  882. ClearBackground(args.Surface, roi);
  883. startIndex = 0;
  884. }
  885. }
  886. else
  887. {
  888. startIndex = 0;
  889. }
  890. for (int i = startIndex; i < this.layers.Count; ++i)
  891. {
  892. Layer layer = (Layer)this.layers[i];
  893. if (layer.Visible)
  894. {
  895. layer.Render(args, roi);
  896. }
  897. }
  898. }
  899. public void Render(RenderArgs args, Rectangle[] roi, bool clearBackground)
  900. {
  901. this.Render(args, roi, 0, roi.Length, clearBackground);
  902. }
  903. public void Render(RenderArgs args, Rectangle[] roi, int startIndex, int length, bool clearBackground)
  904. {
  905. int startLayerIndex;
  906. if (clearBackground)
  907. {
  908. BitmapLayer layer0;
  909. layer0 = this.layers[0] as BitmapLayer;
  910. // Special case: if the first layer is a visible BitmapLayer with full opacity using
  911. // the default blend op, we can just copy the pixels straight over
  912. if (layer0 != null &&
  913. layer0.Visible &&
  914. layer0.Opacity == 255 &&
  915. layer0.BlendOp.GetType() == UserBlendOps.GetDefaultBlendOp())
  916. {
  917. args.Surface.CopySurface(layer0.Surface, roi, startIndex, length);
  918. startLayerIndex = 1;
  919. }
  920. else
  921. {
  922. ClearBackground(args.Surface, roi, startIndex, length);
  923. startLayerIndex = 0;
  924. }
  925. }
  926. else
  927. {
  928. startLayerIndex = 0;
  929. }
  930. for (int i = startLayerIndex; i < this.layers.Count; ++i)
  931. {
  932. Layer layer = (Layer)this.layers[i];
  933. if (layer.Visible)
  934. {
  935. layer.RenderUnchecked(args, roi, startIndex, length);
  936. }
  937. }
  938. }
  939. private sealed class UpdateScansContext
  940. {
  941. private Document document;
  942. private RenderArgs dst;
  943. private Rectangle[] scans;
  944. private int startIndex;
  945. private int length;
  946. public void UpdateScans(object context)
  947. {
  948. document.Render(dst, scans, startIndex, length, true);
  949. }
  950. public UpdateScansContext(Document document, RenderArgs dst, Rectangle[] scans, int startIndex, int length)
  951. {
  952. this.document = document;
  953. this.dst = dst;
  954. this.scans = scans;
  955. this.startIndex = startIndex;
  956. this.length = length;
  957. }
  958. }
  959. /// <summary>
  960. /// Renders only the portions of the document that have changed (been Invalidated) since
  961. /// the last call to this function.
  962. /// </summary>
  963. /// <param name="args">Contains information used to control where rendering occurs.</param>
  964. /// <returns>true if any rendering was done (the update list was non-empty), false otherwise</returns>
  965. public bool Update(RenderArgs dst)
  966. {
  967. if (disposed)
  968. {
  969. throw new ObjectDisposedException("Document");
  970. }
  971. Rectangle[] updateRects;
  972. int updateRectsLength;
  973. updateRegion.GetArrayReadOnly(out updateRects, out updateRectsLength);
  974. if (updateRectsLength == 0)
  975. {
  976. return false;
  977. }
  978. PdnRegion region = Utility.RectanglesToRegion(updateRects, 0, updateRectsLength);
  979. Rectangle[] rectsOriginal = region.GetRegionScansReadOnlyInt();
  980. Rectangle[] rectsToUse;
  981. // Special case where we're drawing 1 big rectangle: split it up!
  982. // This case happens quite frequently, but we don't want to spend a lot of
  983. // time analyzing any other case that is more complicated.
  984. if (rectsOriginal.Length == 1 && rectsOriginal[0].Height > 1)
  985. {
  986. Rectangle[] rectsNew = new Rectangle[Processor.LogicalCpuCount];
  987. Utility.SplitRectangle(rectsOriginal[0], rectsNew);
  988. rectsToUse = rectsNew;
  989. }
  990. else
  991. {
  992. rectsToUse = rectsOriginal;
  993. }
  994. int cpuCount = Processor.LogicalCpuCount;
  995. for (int i = 0; i < cpuCount; ++i)
  996. {
  997. int start = (i * rectsToUse.Length) / cpuCount;
  998. int end = ((i + 1) * rectsToUse.Length) / cpuCount;
  999. UpdateScansContext usc = new UpdateScansContext(this, dst, rectsToUse, start, end - start);
  1000. if (i == cpuCount - 1)
  1001. {
  1002. // Reuse this thread for the last job -- no sense creating a new thread.
  1003. usc.UpdateScans(usc);
  1004. }
  1005. else
  1006. {
  1007. threadPool.QueueUserWorkItem(new WaitCallback(usc.UpdateScans), usc);
  1008. }
  1009. }
  1010. this.threadPool.Drain();
  1011. Validate();
  1012. return true;
  1013. }
  1014. /// <summary>
  1015. /// Constructs a blank document (zero layers) of the given width and height.
  1016. /// </summary>
  1017. /// <param name="width"></param>
  1018. /// <param name="height"></param>
  1019. public Document(int width, int height)
  1020. {
  1021. this.width = width;
  1022. this.height = height;
  1023. this.Dirty = true;
  1024. this.updateRegion = new Vector<Rectangle>();
  1025. layers = new LayerList(this);
  1026. SetupEvents();
  1027. userMetaData = new NameValueCollection();
  1028. Invalidate();
  1029. }
  1030. public Document(Size size)
  1031. : this(size.Width, size.Height)
  1032. {
  1033. }
  1034. /// <summary>
  1035. /// Sets up event handling for contained objects.
  1036. /// </summary>
  1037. private void SetupEvents()
  1038. {
  1039. layers.Changed += new EventHandler(LayerListChangedHandler);
  1040. layers.Changing += new EventHandler(LayerListChangingHandler);
  1041. layerInvalidatedDelegate = new InvalidateEventHandler(LayerInvalidatedHandler);
  1042. foreach (Layer layer in layers)
  1043. {
  1044. layer.Invalidated += layerInvalidatedDelegate;
  1045. }
  1046. }
  1047. /// <summary>
  1048. /// Called after deserialization occurs so that certain things that are non-serializable
  1049. /// can be set up.
  1050. /// </summary>
  1051. /// <param name="sender"></param>
  1052. public void OnDeserialization(object sender)
  1053. {
  1054. this.updateRegion = new Vector<Rectangle>();
  1055. this.updateRegion.Add(this.Bounds);
  1056. this.threadPool = new PaintDotNet.Threading.ThreadPool();
  1057. SetupEvents();
  1058. Dirty = true;
  1059. }
  1060. [field: NonSerialized]
  1061. public event InvalidateEventHandler Invalidated;
  1062. /// <summary>
  1063. /// Raises the Invalidated event.
  1064. /// </summary>
  1065. /// <param name="e"></param>
  1066. private void OnInvalidated(InvalidateEventArgs e)
  1067. {
  1068. if (Invalidated != null)
  1069. {
  1070. Invalidated(this, e);
  1071. }
  1072. }
  1073. /// <summary>
  1074. /// Handles the Changing event that is raised from the contained LayerList.
  1075. /// </summary>
  1076. /// <param name="sender"></param>
  1077. /// <param name="e"></param>
  1078. private void LayerListChangingHandler(object sender, EventArgs e)
  1079. {
  1080. if (disposed)
  1081. {
  1082. throw new ObjectDisposedException("Document");
  1083. }
  1084. foreach (Layer layer in Layers)
  1085. {
  1086. layer.Invalidated -= layerInvalidatedDelegate;
  1087. }
  1088. }
  1089. /// <summary>
  1090. /// Handles the Changed event that is raised from the contained LayerList.
  1091. /// </summary>
  1092. /// <param name="sender"></param>
  1093. /// <param name="e"></param>
  1094. private void LayerListChangedHandler(object sender, EventArgs e)
  1095. {
  1096. foreach (Layer layer in Layers)
  1097. {
  1098. layer.Invalidated += layerInvalidatedDelegate;
  1099. }
  1100. Invalidate();
  1101. }
  1102. /// <summary>
  1103. /// Handles the Invalidated event that is raised from any contained Layer.
  1104. /// </summary>
  1105. /// <param name="sender"></param>
  1106. /// <param name="e"></param>
  1107. private void LayerInvalidatedHandler(object sender, InvalidateEventArgs e)
  1108. {
  1109. Invalidate(e.InvalidRect);
  1110. }
  1111. /// <summary>
  1112. /// Causes the whole document to be invalidated, forcing a full rerender on
  1113. /// the next call to Update.
  1114. /// </summary>
  1115. public void Invalidate()
  1116. {
  1117. Dirty = true;
  1118. Rectangle rect = new Rectangle(0, 0, Width, Height);
  1119. updateRegion.Clear();
  1120. updateRegion.Add(rect);
  1121. OnInvalidated(new InvalidateEventArgs(rect));
  1122. }
  1123. /// <summary>
  1124. /// Invalidates a portion of the document. The given region is then tagged
  1125. /// for rerendering during the next call to Update.
  1126. /// </summary>
  1127. /// <param name="roi">The region of interest to be invalidated.</param>
  1128. public void Invalidate(PdnRegion roi)
  1129. {
  1130. Dirty = true;
  1131. foreach (Rectangle rect in roi.GetRegionScansReadOnlyInt())
  1132. {
  1133. rect.Intersect(this.Bounds);
  1134. updateRegion.Add(rect);
  1135. if (!rect.IsEmpty)
  1136. {
  1137. InvalidateEventArgs iea = new InvalidateEventArgs(rect);
  1138. OnInvalidated(iea);
  1139. }
  1140. }
  1141. }
  1142. public void Invalidate(RectangleF[] roi)
  1143. {
  1144. foreach (RectangleF rectF in roi)
  1145. {
  1146. Invalidate(Rectangle.Truncate(rectF));
  1147. }
  1148. }
  1149. public void Invalidate(RectangleF roi)
  1150. {
  1151. Invalidate(Rectangle.Truncate(roi));
  1152. }
  1153. public void Invalidate(Rectangle[] roi)
  1154. {
  1155. foreach (Rectangle rect in roi)
  1156. {
  1157. Invalidate(rect);
  1158. }
  1159. }
  1160. /// <summary>
  1161. /// Invalidates a portion of the document. The given region is then tagged
  1162. /// for rerendering during the next call to Update.
  1163. /// </summary>
  1164. /// <param name="roi">The region of interest to be invalidated.</param>
  1165. public void Invalidate(Rectangle roi)
  1166. {
  1167. Dirty = true;
  1168. Rectangle rect = Rectangle.Intersect(roi, this.Bounds);
  1169. updateRegion.Add(rect);
  1170. OnInvalidated(new InvalidateEventArgs(rect));
  1171. }
  1172. /// <summary>
  1173. /// Clears the document's update region. This is called at the end of the
  1174. /// Update method.
  1175. /// </summary>
  1176. private void Validate()
  1177. {
  1178. updateRegion.Clear();
  1179. }
  1180. /// <summary>
  1181. /// Creates a document that consists of one BitmapLayer.
  1182. /// </summary>
  1183. /// <param name="image">The Image to make a copy of that will be the first layer ("Background") in the document.</param>
  1184. public static Document FromImage(Image image)
  1185. {
  1186. if (image == null)
  1187. {
  1188. throw new ArgumentNullException("image");
  1189. }
  1190. Document document = new Document(image.Width, image.Height);
  1191. BitmapLayer layer = Layer.CreateBackgroundLayer(image.Width, image.Height);
  1192. layer.Surface.Clear(ColorBgra.FromBgra(0, 0, 0, 0));
  1193. Bitmap asBitmap = image as Bitmap;
  1194. // Copy pixels
  1195. if (asBitmap != null && asBitmap.PixelFormat == PixelFormat.Format32bppArgb)
  1196. {
  1197. unsafe
  1198. {
  1199. BitmapData bData = asBitmap.LockBits(new Rectangle(0, 0, asBitmap.Width, asBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
  1200. try
  1201. {
  1202. for (int y = 0; y < bData.Height; ++y)
  1203. {
  1204. uint* srcPtr = (uint*)((byte*)bData.Scan0.ToPointer() + (y * bData.Stride));
  1205. ColorBgra* dstPtr = layer.Surface.GetRowAddress(y);
  1206. for (int x = 0; x < bData.Width; ++x)
  1207. {
  1208. dstPtr->Bgra = *srcPtr;
  1209. ++srcPtr;
  1210. ++dstPtr;
  1211. }
  1212. }
  1213. }
  1214. finally
  1215. {
  1216. asBitmap.UnlockBits(bData);
  1217. bData = null;
  1218. }
  1219. }
  1220. }
  1221. else if (asBitmap != null && asBitmap.PixelFormat == PixelFormat.Format24bppRgb)
  1222. {
  1223. unsafe
  1224. {
  1225. BitmapData bData = asBitmap.LockBits(new Rectangle(0, 0, asBitmap.Width, asBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
  1226. try
  1227. {
  1228. for (int y = 0; y < bData.Height; ++y)
  1229. {
  1230. byte* srcPtr = (byte*)bData.Scan0.ToPointer() + (y * bData.Stride);

Large files files are truncated, but you can click here to view the full file