PageRenderTime 64ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Data/Layer.cs

https://bitbucket.org/tcz001/openpdn
C# | 660 lines | 442 code | 87 blank | 131 comment | 48 complexity | 4dbbfc6cdebf9df7ba0dbe4a183e5bff MD5 | raw file
Possible License(s): Unlicense
  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 System;
  10. using System.Collections;
  11. using System.Collections.Generic;
  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.Runtime.Serialization;
  18. using System.Threading;
  19. using System.Windows.Forms;
  20. namespace PaintDotNet
  21. {
  22. /// <summary>
  23. /// A layer's properties are immutable. That is, you can modify the surface
  24. /// of a layer all you want, but to change its dimensions requires creating
  25. /// a new layer.
  26. /// </summary>
  27. [Serializable]
  28. public abstract class Layer
  29. : ICloneable,
  30. IDisposable,
  31. IThumbnailProvider
  32. {
  33. private int width;
  34. private int height;
  35. /// <summary>
  36. /// The background layer is generally opaque although it doesn't *have* to be. For
  37. /// example, the Canvas Size action distinguishes between background and non-background
  38. /// layers such that it fills the background layer with opaque and the non-background
  39. /// layers with transparency.
  40. /// The value of this property should not be used to disallow the user from performing
  41. /// an action.
  42. /// </summary>
  43. public bool IsBackground
  44. {
  45. get
  46. {
  47. return properties.isBackground;
  48. }
  49. set
  50. {
  51. bool oldValue = properties.isBackground;
  52. if (oldValue != value)
  53. {
  54. OnPropertyChanging(LayerProperties.IsBackgroundName);
  55. properties.isBackground = value;
  56. OnPropertyChanged(LayerProperties.IsBackgroundName);
  57. }
  58. }
  59. }
  60. /// <summary>
  61. /// If this value is non-0, then the PropertyChanged event will be
  62. /// suppressed. This is in place so that the Layer Properties dialog
  63. /// can tweak the properties without them filling up the undo stack.
  64. /// </summary>
  65. [NonSerialized]
  66. private int suppressPropertyChanges;
  67. /// <summary>
  68. /// Encapsulates the mutable properties of the Layer class.
  69. /// </summary>
  70. [Serializable]
  71. internal sealed class LayerProperties
  72. : ICloneable,
  73. ISerializable
  74. {
  75. public string name;
  76. public NameValueCollection userMetaData;
  77. public bool visible;
  78. public bool isBackground;
  79. public byte opacity;
  80. private const string nameTag = "name";
  81. private const string userMetaDataTag = "userMetaData";
  82. private const string visibleTag = "visible";
  83. private const string isBackgroundTag = "isBackground";
  84. private const string opacityTag = "opacity";
  85. public static string IsBackgroundName
  86. {
  87. get
  88. {
  89. return PdnResources.GetString("Layer.Properties.IsBackground.Name");
  90. }
  91. }
  92. public static string NameName
  93. {
  94. get
  95. {
  96. return PdnResources.GetString("Layer.Properties.Name.Name");
  97. }
  98. }
  99. public static string VisibleName
  100. {
  101. get
  102. {
  103. return PdnResources.GetString("Layer.Properties.Visible.Name");
  104. }
  105. }
  106. public static string OpacityName
  107. {
  108. get
  109. {
  110. return PdnResources.GetString("Layer.Properties.Opacity.Name");
  111. }
  112. }
  113. public LayerProperties(string name, NameValueCollection userMetaData, bool visible, bool isBackground, byte opacity)
  114. {
  115. this.name = name;
  116. this.userMetaData = new NameValueCollection(userMetaData);
  117. this.visible = visible;
  118. this.isBackground = isBackground;
  119. this.opacity = opacity;
  120. }
  121. public LayerProperties(LayerProperties copyMe)
  122. {
  123. this.name = copyMe.name;
  124. this.userMetaData = new NameValueCollection(copyMe.userMetaData);
  125. this.visible = copyMe.visible;
  126. this.isBackground = copyMe.isBackground;
  127. this.opacity = copyMe.opacity;
  128. }
  129. public object Clone()
  130. {
  131. return new LayerProperties(this);
  132. }
  133. public LayerProperties(SerializationInfo info, StreamingContext context)
  134. {
  135. this.name = info.GetString(nameTag);
  136. this.userMetaData = (NameValueCollection)info.GetValue(userMetaDataTag, typeof(NameValueCollection));
  137. this.visible = info.GetBoolean(visibleTag);
  138. this.isBackground = info.GetBoolean(isBackgroundTag);
  139. // This property was added with v2.1. So as to allow loading old .PDN files,
  140. // this is an optional item.
  141. // (Historical note: this property was actually moved from the BitmapLayer
  142. // properties to the base class because it was found to be a rather important
  143. // property for rendering regardless of layer "type")
  144. try
  145. {
  146. this.opacity = info.GetByte(opacityTag);
  147. }
  148. catch (SerializationException)
  149. {
  150. this.opacity = 255;
  151. }
  152. }
  153. public void GetObjectData(SerializationInfo info, StreamingContext context)
  154. {
  155. info.AddValue(nameTag, name);
  156. info.AddValue(userMetaDataTag, userMetaData);
  157. info.AddValue(visibleTag, visible);
  158. info.AddValue(isBackgroundTag, isBackground);
  159. info.AddValue(opacityTag, opacity);
  160. }
  161. }
  162. private LayerProperties properties;
  163. public byte Opacity
  164. {
  165. get
  166. {
  167. if (disposed)
  168. {
  169. throw new ObjectDisposedException("Layer");
  170. }
  171. return properties.opacity;
  172. }
  173. set
  174. {
  175. if (disposed)
  176. {
  177. throw new ObjectDisposedException("Layer");
  178. }
  179. if (properties.opacity != value)
  180. {
  181. OnPropertyChanging(LayerProperties.OpacityName);
  182. properties.opacity = value;
  183. OnPropertyChanged(LayerProperties.OpacityName);
  184. Invalidate();
  185. }
  186. }
  187. }
  188. /// <summary>
  189. /// Allows you to save the mutable properties of the layer so you can restore them later
  190. /// (esp. important for undo!). Mutable properties include the layer's name, whether it's
  191. /// visible, and the metadata. This list might expand later.
  192. /// </summary>
  193. /// <returns>
  194. /// An object that can be used later in a call to LoadProperties.
  195. /// </returns>
  196. /// <remarks>
  197. /// It is important that derived classes call this in the correct fashion so as to 'chain'
  198. /// the properties list together. The following is the correct pattern:
  199. ///
  200. /// public override object SaveProperties()
  201. /// {
  202. /// object baseProperties = base.SaveProperties();
  203. /// return new List(properties.Clone(), new List(baseProperties, null));
  204. /// }
  205. /// </remarks>
  206. public virtual object SaveProperties()
  207. {
  208. return properties.Clone();
  209. }
  210. public void LoadProperties(object oldState)
  211. {
  212. LoadProperties(oldState, false);
  213. }
  214. public virtual void LoadProperties(object oldState, bool suppressEvents)
  215. {
  216. LayerProperties lp = (LayerProperties)oldState;
  217. List<string> changed = new List<String>();
  218. if (!suppressEvents)
  219. {
  220. if (lp.name != properties.name)
  221. {
  222. changed.Add(LayerProperties.NameName);
  223. }
  224. if (lp.isBackground != properties.isBackground)
  225. {
  226. changed.Add(LayerProperties.IsBackgroundName);
  227. }
  228. if (lp.visible != properties.visible)
  229. {
  230. changed.Add(LayerProperties.VisibleName);
  231. }
  232. if (lp.opacity != properties.opacity)
  233. {
  234. changed.Add(LayerProperties.OpacityName);
  235. }
  236. }
  237. foreach (string propertyName in changed)
  238. {
  239. OnPropertyChanging(propertyName);
  240. }
  241. properties = (LayerProperties)((LayerProperties)oldState).Clone();
  242. Invalidate();
  243. foreach (string propertyName in changed)
  244. {
  245. OnPropertyChanged(propertyName);
  246. }
  247. }
  248. public int Width
  249. {
  250. get
  251. {
  252. return width;
  253. }
  254. }
  255. public int Height
  256. {
  257. get
  258. {
  259. return height;
  260. }
  261. }
  262. public Size Size
  263. {
  264. get
  265. {
  266. return new Size(Width, Height);
  267. }
  268. }
  269. public Rectangle Bounds
  270. {
  271. get
  272. {
  273. return new Rectangle(new Point(0, 0), Size);
  274. }
  275. }
  276. public void PushSuppressPropertyChanged()
  277. {
  278. Interlocked.Increment(ref suppressPropertyChanges);
  279. }
  280. public void PopSuppressPropertyChanged()
  281. {
  282. if (0 > Interlocked.Decrement(ref suppressPropertyChanges))
  283. {
  284. throw new InvalidProgramException("suppressPreviewChanged is less than zero");
  285. }
  286. }
  287. [field: NonSerialized]
  288. public event EventHandler PreviewChanged;
  289. protected virtual void OnPreviewChanged()
  290. {
  291. if (PreviewChanged != null)
  292. {
  293. PreviewChanged(this, EventArgs.Empty);
  294. }
  295. }
  296. /// <summary>
  297. /// This event is raised before a property is changed. Note that the name given
  298. /// in the PropertyEventArgs is for descriptive (UI) purposes only and serves no
  299. /// programmatic purpose. When this event is raised you should not make any
  300. /// assumptions about which property was changed based on this description.
  301. /// </summary>
  302. [field: NonSerialized]
  303. public event PropertyEventHandler PropertyChanging;
  304. protected virtual void OnPropertyChanging(string propertyName)
  305. {
  306. if (this.suppressPropertyChanges == 0)
  307. {
  308. if (PropertyChanging != null)
  309. {
  310. PropertyChanging(this, new PropertyEventArgs(propertyName));
  311. }
  312. }
  313. }
  314. /// <summary>
  315. /// This event is raised after a property is changed. Note that the name given
  316. /// in the PropertyEventArgs is for descriptive (UI) purposes only and serves no
  317. /// programmatic purpose. When this event is raised you should not make any
  318. /// assumptions about which property was changed based on this description.
  319. /// </summary>
  320. [field: NonSerialized]
  321. public event PropertyEventHandler PropertyChanged;
  322. protected virtual void OnPropertyChanged(string propertyName)
  323. {
  324. if (this.suppressPropertyChanges == 0)
  325. {
  326. if (PropertyChanged != null)
  327. {
  328. PropertyChanged(this, new PropertyEventArgs(propertyName));
  329. }
  330. }
  331. }
  332. /// <summary>
  333. /// You can call this to raise the PropertyChanged event. Note that is will
  334. /// raise the event with an empty string for the property name description.
  335. /// Thus it is useful only for syncing up UI elements that require notification
  336. /// of events but that otherwise don't really track it.
  337. /// </summary>
  338. public void PerformPropertyChanged()
  339. {
  340. OnPropertyChanged(string.Empty);
  341. }
  342. /// <summary>
  343. /// A user-definable name.
  344. /// </summary>
  345. public string Name
  346. {
  347. get
  348. {
  349. return properties.name;
  350. }
  351. set
  352. {
  353. if (properties.name != value)
  354. {
  355. OnPropertyChanging(LayerProperties.NameName);
  356. properties.name = value;
  357. OnPropertyChanged(LayerProperties.NameName);
  358. }
  359. }
  360. }
  361. [NonSerialized]
  362. private Metadata metadata;
  363. public Metadata Metadata
  364. {
  365. get
  366. {
  367. if (metadata == null)
  368. {
  369. metadata = new Metadata(properties.userMetaData);
  370. }
  371. return metadata;
  372. }
  373. }
  374. /// <summary>
  375. /// Determines whether the layer is part of a document's composition. If this
  376. /// property is false, the composition engine will ignore this layer.
  377. /// </summary>
  378. public bool Visible
  379. {
  380. get
  381. {
  382. return properties.visible;
  383. }
  384. set
  385. {
  386. bool oldValue = properties.visible;
  387. if (oldValue != value)
  388. {
  389. OnPropertyChanging(LayerProperties.VisibleName);
  390. properties.visible = value;
  391. OnPropertyChanged(LayerProperties.VisibleName);
  392. Invalidate();
  393. }
  394. }
  395. }
  396. /// <summary>
  397. /// Determines whether a rectangle is fully in bounds or not. This is determined by checking
  398. /// to make sure the left, top, right, and bottom edges are within bounds.
  399. /// </summary>
  400. /// <param name="roi"></param>
  401. /// <returns></returns>
  402. private bool IsInBounds(Rectangle roi)
  403. {
  404. if (roi.Left < 0 || roi.Top < 0 || roi.Left >= Width || roi.Top >= Height ||
  405. roi.Right > Width || roi.Bottom > Height)
  406. {
  407. return false;
  408. }
  409. return true;
  410. }
  411. /// <summary>
  412. /// Implements IThumbnailProvider.RenderThumbnail().
  413. /// </summary>
  414. public abstract Surface RenderThumbnail(int maxEdgeLength);
  415. /// <summary>
  416. /// Causes the layer to render a given rectangle of interest (roi) to the given destination surface.
  417. /// </summary>
  418. /// <param name="args">Contains information about which objects to use for rendering</param>
  419. /// <param name="roi">The rectangular region to be rendered.</param>
  420. public void Render(RenderArgs args, Rectangle roi)
  421. {
  422. // the bitmap we're rendering to must match the size of the layer we're rendering from
  423. if (args.Surface.Width != Width || args.Surface.Height != Height)
  424. {
  425. throw new ArgumentException();
  426. }
  427. // the region of interest can not be out of bounds!
  428. if (!IsInBounds(roi))
  429. {
  430. throw new ArgumentOutOfRangeException("roi");
  431. }
  432. RenderImpl(args, roi);
  433. }
  434. /// <summary>
  435. /// Causes the layer to render a given region of interest (roi) to the given destination surface.
  436. /// </summary>
  437. /// <param name="args">Contains information about which objects to use for rendering</param>
  438. /// <param name="roi">The region to be rendered.</param>
  439. public void Render(RenderArgs args, PdnRegion roi)
  440. {
  441. Rectangle roiBounds = roi.GetBoundsInt();
  442. if (!IsInBounds(roiBounds))
  443. {
  444. throw new ArgumentOutOfRangeException("roi");
  445. }
  446. Rectangle[] rects = roi.GetRegionScansReadOnlyInt();
  447. RenderImpl(args, rects);
  448. }
  449. public void RenderUnchecked(RenderArgs args, Rectangle[] roi, int startIndex, int length)
  450. {
  451. RenderImpl(args, roi, startIndex, length);
  452. }
  453. /// <summary>
  454. /// Override this method to provide your layer's rendering capabilities.
  455. /// </summary>
  456. /// <param name="args">Contains information about which objects to use for rendering</param>
  457. /// <param name="roi">The rectangular region to be rendered.</param>
  458. protected abstract void RenderImpl(RenderArgs args, Rectangle roi);
  459. protected void RenderImpl(RenderArgs args, Rectangle[] roi)
  460. {
  461. RenderImpl(args, roi, 0, roi.Length);
  462. }
  463. protected virtual void RenderImpl(RenderArgs args, Rectangle[] roi, int startIndex, int length)
  464. {
  465. for (int i = startIndex; i < startIndex + length; ++i)
  466. {
  467. RenderImpl(args, roi[i]);
  468. }
  469. }
  470. [field: NonSerialized]
  471. public event InvalidateEventHandler Invalidated;
  472. protected virtual void OnInvalidated(InvalidateEventArgs e)
  473. {
  474. if (Invalidated != null)
  475. {
  476. Invalidated(this, e);
  477. }
  478. }
  479. /// <summary>
  480. /// Causes the entire layer surface to be invalidated.
  481. /// </summary>
  482. public void Invalidate()
  483. {
  484. Rectangle rect = new Rectangle(0, 0, Width, Height);
  485. OnInvalidated(new InvalidateEventArgs(rect));
  486. }
  487. /// <summary>
  488. /// Causes a portion of the layer surface to be invalidated.
  489. /// </summary>
  490. /// <param name="roi">The region of interest to be invalidated.</param>
  491. public void Invalidate(PdnRegion roi)
  492. {
  493. foreach (Rectangle rect in roi.GetRegionScansReadOnlyInt())
  494. {
  495. Invalidate(rect);
  496. }
  497. }
  498. /// <summary>
  499. /// Causes a portion of the layer surface to be invalidated.
  500. /// </summary>
  501. /// <param name="roi">The region of interest to be invalidated.</param>
  502. public void Invalidate(RectangleF[] roi)
  503. {
  504. foreach (RectangleF rectF in roi)
  505. {
  506. Invalidate(Rectangle.Truncate(rectF));
  507. }
  508. }
  509. /// <summary>
  510. /// Causes a portion of the layer surface to be invalidated.
  511. /// </summary>
  512. /// <param name="roi">The rectangle of interest to be invalidated.</param>
  513. public void Invalidate(Rectangle roi)
  514. {
  515. Rectangle rect = Rectangle.Intersect(roi, this.Bounds);
  516. // TODO: this is horrible for performance w.r.t. complex invalidation regions. Lots of heap pollution.
  517. // fix that!
  518. OnInvalidated(new InvalidateEventArgs(rect));
  519. }
  520. public Layer(int width, int height)
  521. {
  522. this.width = width;
  523. this.height = height;
  524. this.properties = new LayerProperties(null, new NameValueCollection(), true, false, 255);
  525. }
  526. protected Layer(Layer copyMe)
  527. {
  528. this.width = copyMe.width;
  529. this.height = copyMe.height;
  530. this.properties = (LayerProperties)copyMe.properties.Clone();
  531. }
  532. // TODO: add "name" parameter, keep this for legacy and fill it in with "Background"
  533. // goal is to put complete burden of loc on the client
  534. public static BitmapLayer CreateBackgroundLayer(int width, int height)
  535. {
  536. // set colors to 0xffffffff
  537. // note: we use alpha of 255 here so that "invert colors" works as expected
  538. // that is, for just 1 layer we invert the initial white->black
  539. // but on subsequent layers we invert transparent white -> transparent black, which shows up as white for the most part
  540. BitmapLayer layer = new BitmapLayer(width, height, ColorBgra.White);
  541. layer.Name = PdnResources.GetString("Layer.Background.Name");
  542. // tag it as a background layer
  543. layer.properties.isBackground = true;
  544. return layer;
  545. }
  546. /// <summary>
  547. /// This allows a layer to provide a dialog for configuring
  548. /// the layer's properties.
  549. /// </summary>
  550. public abstract PdnBaseForm CreateConfigDialog();
  551. public abstract object Clone();
  552. ~Layer()
  553. {
  554. Dispose(false);
  555. }
  556. public void Dispose()
  557. {
  558. Dispose(true);
  559. GC.SuppressFinalize(this);
  560. }
  561. private bool disposed = false;
  562. protected virtual void Dispose(bool disposing)
  563. {
  564. if (!disposed)
  565. {
  566. disposed = true;
  567. if (disposing)
  568. {
  569. }
  570. }
  571. }
  572. }
  573. }