StitchSurface.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. using PaintDotNet.SystemLayer;
  2. using System;
  3. using System.Drawing;
  4. using System.Drawing.Imaging;
  5. namespace PaintDotNet
  6. {
  7. /// <summary>
  8. /// This is our StitchSurface type. We allocate our own blocks of memory for this,
  9. /// and provide ways to create a GDI+ Bitmap object that aliases our StitchSurface.
  10. /// That way we can do everything fast, in memory and have complete control,
  11. /// and still have the ability to use GDI+ for drawing and rendering where
  12. /// appropriate.
  13. /// </summary>
  14. //[Serializable]
  15. public sealed class StitchSurface : IDisposable
  16. {
  17. /// <summary>
  18. /// 持有的mat
  19. /// </summary>
  20. private OpenCvSharp.Mat mat;
  21. /// <summary>
  22. /// 持有mat的绘制区域
  23. /// </summary>
  24. public Rectangle stitchBounds;
  25. /// <summary>
  26. /// 持有mat的绘制区域外背景颜色
  27. /// </summary>
  28. public Color stitchBackColor;
  29. /// <summary>
  30. /// 图像内存块
  31. /// </summary>
  32. public MemoryBlock scan0;
  33. /// <summary>
  34. /// 宽
  35. /// </summary>
  36. private int width;
  37. /// <summary>
  38. /// 高
  39. /// </summary>
  40. private int height;
  41. /// <summary>
  42. /// 扫描宽度
  43. /// </summary>
  44. private int stride;
  45. /// <summary>
  46. /// 释放标记
  47. /// </summary>
  48. private bool disposed = false;
  49. /// <summary>
  50. /// 存储打开时图片的格式
  51. /// </summary>
  52. private PixelFormat pixelFormat = PixelFormat.Format24bppRgb;
  53. public PixelFormat PixelFormat
  54. {
  55. get
  56. {
  57. return this.pixelFormat;
  58. }
  59. set
  60. {
  61. this.pixelFormat = value;
  62. }
  63. }
  64. public bool IsDisposed
  65. {
  66. get
  67. {
  68. return this.disposed;
  69. }
  70. }
  71. /// <summary>
  72. /// 持有mat的有效区域
  73. /// </summary>
  74. public OpenCvSharp.Mat StitchMat
  75. {
  76. get
  77. {
  78. if (this.mat == null || this.stitchBounds.Width == 0 || this.stitchBounds.Height == 0) return null;
  79. unsafe
  80. {
  81. return new OpenCvSharp.Mat(stitchBounds.Height, stitchBounds.Width, OpenCvSharp.MatType.CV_8UC3, new IntPtr((void*)((byte*)scan0.VoidStar + GetPointByteOffsetUnchecked(stitchBounds.X, stitchBounds.Y, this.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 3))), stride);
  82. }
  83. }
  84. }
  85. /// <summary>
  86. /// Gets the width, in pixels, of this StitchSurface.
  87. /// </summary>
  88. /// <remarks>
  89. /// This property will never throw an ObjectDisposedException.
  90. /// </remarks>
  91. public int StitchWidth
  92. {
  93. get
  94. {
  95. return Math.Min(this.width, this.stitchBounds.Width + 2 * this.stitchBounds.X);
  96. }
  97. }
  98. /// <summary>
  99. /// Gets the height, in pixels, of this StitchSurface.
  100. /// </summary>
  101. /// <remarks>
  102. /// This property will never throw an ObjectDisposedException.
  103. /// </remarks>
  104. public int StitchHeight
  105. {
  106. get
  107. {
  108. return Math.Min(this.height, this.stitchBounds.Height + 2 * this.stitchBounds.Y);
  109. }
  110. }
  111. /// <summary>
  112. /// Gets the width, in pixels, of this StitchSurface.
  113. /// </summary>
  114. /// <remarks>
  115. /// This property will never throw an ObjectDisposedException.
  116. /// </remarks>
  117. public int Width
  118. {
  119. get
  120. {
  121. return this.width;
  122. }
  123. }
  124. /// <summary>
  125. /// Gets the height, in pixels, of this StitchSurface.
  126. /// </summary>
  127. /// <remarks>
  128. /// This property will never throw an ObjectDisposedException.
  129. /// </remarks>
  130. public int Height
  131. {
  132. get
  133. {
  134. return this.height;
  135. }
  136. }
  137. /// <summary>
  138. /// Gets the stride, in bytes, for this StitchSurface.
  139. /// </summary>
  140. /// <remarks>
  141. /// Stride is defined as the number of bytes between the beginning of a row and
  142. /// the beginning of the next row. Thus, in loose C notation: stride = (byte *)&this[0, 1] - (byte *)&this[0, 0].
  143. /// Stride will always be equal to <b>or greater than</b> Width * ColorBgrB.SizeOf.
  144. /// This property will never throw an ObjectDisposedException.
  145. /// </remarks>
  146. public int Stride
  147. {
  148. set
  149. {
  150. this.stride = value;
  151. }
  152. }
  153. /// <summary>
  154. /// Gets the size, in pixels, of this StitchSurface.
  155. /// </summary>
  156. /// <remarks>
  157. /// This is a convenience function that creates a new Size instance based
  158. /// on the values of the Width and Height properties.
  159. /// This property will never throw an ObjectDisposedException.
  160. /// </remarks>
  161. public Size Size
  162. {
  163. get
  164. {
  165. return new Size(this.width, this.height);
  166. }
  167. }
  168. /// <summary>
  169. /// Gets the bounds of this StitchSurface, in pixels.
  170. /// </summary>
  171. /// <remarks>
  172. /// This is a convenience function that returns Rectangle(0, 0, Width, Height).
  173. /// This property will never throw an ObjectDisposedException.
  174. /// </remarks>
  175. public Rectangle Bounds
  176. {
  177. get
  178. {
  179. return new Rectangle(0, 0, width, height);
  180. }
  181. }
  182. /// <summary>
  183. /// Creates a new instance of the StitchSurface class.
  184. /// </summary>
  185. /// <param name="size">The size, in pixels, of the new StitchSurface.</param>
  186. public StitchSurface(Size size, int SizeOf = 4, OpenCvSharp.Mat mat = null)
  187. {
  188. this.mat = mat;
  189. int width = size.Width;
  190. int height = size.Height;
  191. int stride = 0;
  192. long bytes;
  193. try
  194. {
  195. stride = checked(width * SizeOf);
  196. bytes = (long)height * (long)stride;
  197. }
  198. catch (OverflowException ex)
  199. {
  200. throw new OutOfMemoryException("Dimensions are too large - not enough memory, width=" + width.ToString() + ", height=" + height.ToString(), ex);
  201. }
  202. MemoryBlock scan0 = new MemoryBlock(width, height, SizeOf);
  203. this.width = width;
  204. this.height = height;
  205. this.stride = stride;
  206. this.scan0 = scan0;
  207. }
  208. /// <summary>
  209. /// Creates a new instance of the StitchSurface class that reuses a block of memory that was previously allocated.
  210. /// </summary>
  211. /// <param name="width">The width, in pixels, for the StitchSurface.</param>
  212. /// <param name="height">The height, in pixels, for the StitchSurface.</param>
  213. /// <param name="stride">The stride, in bytes, for the StitchSurface.</param>
  214. /// <param name="scan0">The MemoryBlock to use. The beginning of this buffer defines the upper left (0, 0) pixel of the StitchSurface.</param>
  215. private StitchSurface(int width, int height, int stride, MemoryBlock scan0)
  216. {
  217. this.width = width;
  218. this.height = height;
  219. this.stride = stride;
  220. this.scan0 = scan0;
  221. }
  222. ~StitchSurface()
  223. {
  224. Dispose(false);
  225. }
  226. /// <summary>
  227. /// Creates a StitchSurface that aliases a portion of this StitchSurface.
  228. /// </summary>
  229. /// <param name="bounds">The portion of this StitchSurface that will be aliased.</param>
  230. /// <remarks>The upper left corner of the new StitchSurface will correspond to the
  231. /// upper left corner of this rectangle in the original StitchSurface.</remarks>
  232. /// <returns>A StitchSurface that aliases the requested portion of this StitchSurface.</returns>
  233. public StitchSurface CreateWindow(Rectangle bounds)
  234. {
  235. return CreateWindow(bounds.X, bounds.Y, bounds.Width, bounds.Height);
  236. }
  237. public StitchSurface CreateWindow(int x, int y, int windowWidth, int windowHeight)
  238. {
  239. if (disposed)
  240. {
  241. throw new ObjectDisposedException("StitchSurface");
  242. }
  243. if (windowHeight == 0)
  244. {
  245. throw new ArgumentOutOfRangeException("windowHeight", "must be greater than zero");
  246. }
  247. Rectangle original = this.Bounds;
  248. Rectangle sub = new Rectangle(x, y, windowWidth, windowHeight);
  249. Rectangle clipped = Rectangle.Intersect(original, sub);
  250. if (clipped != sub)
  251. {
  252. throw new ArgumentOutOfRangeException("bounds", new Rectangle(x, y, windowWidth, windowHeight),
  253. "bounds parameters must be a subset of this StitchSurface's bounds");
  254. }
  255. long offset = ((long)stride * (long)y) + ((long)(this.pixelFormat == PixelFormat.Format24bppRgb ? 3 : 4) * (long)x);
  256. long length = ((windowHeight - 1) * (long)stride) + (long)windowWidth * (long)(this.pixelFormat == PixelFormat.Format24bppRgb ? 3 : 4);
  257. MemoryBlock block = new MemoryBlock(this.scan0, offset, length);
  258. return new StitchSurface(windowWidth, windowHeight, this.stride, block);
  259. }
  260. /// <summary>
  261. /// Gets the offset, in bytes, of the requested row from the start of the StitchSurface.
  262. /// </summary>
  263. /// <param name="y">The row.</param>
  264. /// <returns>The number of bytes between (0,0) and (0,y)</returns>
  265. /// <remarks>
  266. /// This method does not do any bounds checking and is potentially unsafe to use,
  267. /// but faster than GetRowByteOffset().
  268. /// </remarks>
  269. public unsafe long GetRowByteOffsetUnchecked(int y)
  270. {
  271. #if DEBUG
  272. if (y < 0 || y >= this.height)
  273. {
  274. Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")");
  275. }
  276. #endif
  277. return (long)y * (long)stride;
  278. }
  279. /// <summary>
  280. /// Gets a pointer to the beginning of the requested row in the StitchSurface.
  281. /// </summary>
  282. /// <param name="y">The row</param>
  283. /// <returns>A pointer that references (0,y) in this StitchSurface.</returns>
  284. /// <remarks>
  285. /// This method does not do any bounds checking and is potentially unsafe to use,
  286. /// but faster than GetRowAddress().
  287. /// </remarks>
  288. public unsafe ColorBgrB* GetRowAddressUnchecked(int y)
  289. {
  290. #if DEBUG
  291. if (y < 0 || y >= this.height)
  292. {
  293. Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")");
  294. }
  295. #endif
  296. return (ColorBgrB*)(((byte*)scan0.VoidStar) + GetRowByteOffsetUnchecked(y));
  297. }
  298. /// <summary>
  299. /// Gets the number of bytes from the beginning of a row to the requested column.
  300. /// </summary>
  301. /// <param name="x">The column.</param>
  302. /// <returns>
  303. /// The number of bytes between (0,n) and (x,n) where n is in the range [0, Height).
  304. /// </returns>
  305. /// <remarks>
  306. /// This method does not do any bounds checking and is potentially unsafe to use,
  307. /// but faster than GetColumnByteOffset().
  308. /// </remarks>
  309. public long GetColumnByteOffsetUnchecked(int x, int SizeOf)
  310. {
  311. #if DEBUG
  312. if (x < 0 || x >= this.width)
  313. {
  314. Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")");
  315. }
  316. #endif
  317. return (long)x * (long)SizeOf;
  318. }
  319. /// <summary>
  320. /// Gets the number of bytes from the beginning of the StitchSurface's buffer to
  321. /// the requested point.
  322. /// </summary>
  323. /// <param name="x">The x offset.</param>
  324. /// <param name="y">The y offset.</param>
  325. /// <returns>
  326. /// The number of bytes between (0,0) and (x,y).
  327. /// </returns>
  328. /// <remarks>
  329. /// This method does not do any bounds checking and is potentially unsafe to use,
  330. /// but faster than GetPointByteOffset().
  331. /// </remarks>
  332. public long GetPointByteOffsetUnchecked(int x, int y, int SizeOf)
  333. {
  334. #if DEBUG
  335. if (x < 0 || x >= this.width)
  336. {
  337. Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")");
  338. }
  339. if (y < 0 || y >= this.height)
  340. {
  341. Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")");
  342. }
  343. #endif
  344. return GetRowByteOffsetUnchecked(y) + GetColumnByteOffsetUnchecked(x, SizeOf);
  345. }
  346. /// <summary>
  347. /// Gets the address in memory of the requested point.
  348. /// </summary>
  349. /// <param name="x">The x offset.</param>
  350. /// <param name="y">The y offset.</param>
  351. /// <returns>A pointer to the requested point in the StitchSurface.</returns>
  352. /// <remarks>
  353. /// This method does not do any bounds checking and is potentially unsafe to use,
  354. /// but faster than GetPointAddress().
  355. /// </remarks>
  356. public unsafe ColorBgrB* GetPointAddressUnchecked(int x, int y)
  357. {
  358. #if DEBUG
  359. if (x < 0 || x >= this.width)
  360. {
  361. Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")");
  362. }
  363. if (y < 0 || y >= this.height)
  364. {
  365. Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")");
  366. }
  367. #endif
  368. return unchecked(x + (ColorBgrB*)(((byte*)scan0.VoidStar) + (y * stride)));
  369. }
  370. /// <summary>
  371. /// Determines if the requested row offset is within bounds.
  372. /// </summary>
  373. /// <param name="y">The row.</param>
  374. /// <returns>true if y &gt;= 0 and y &lt; height, otherwise false</returns>
  375. public bool IsRowVisible(int y)
  376. {
  377. return y >= 0 && y < Height;
  378. }
  379. /// <summary>
  380. /// Determines if the requested column offset is within bounds.
  381. /// </summary>
  382. /// <param name="x">The column.</param>
  383. /// <returns>true if x &gt;= 0 and x &lt; width, otherwise false.</returns>
  384. public bool IsColumnVisible(int x)
  385. {
  386. return x >= 0 && x < Width;
  387. }
  388. /// <summary>
  389. /// Helper function. Same as calling CreateAliasedBounds(Bounds).
  390. /// </summary>
  391. /// <returns>A GDI+ Bitmap that aliases the entire StitchSurface.</returns>
  392. public Bitmap CreateAliasedBitmap()
  393. {
  394. return CreateAliasedBitmap(this.Bounds);
  395. }
  396. /// <summary>
  397. /// Helper function. Same as calling CreateAliasedBounds(bounds, true).
  398. /// </summary>
  399. /// <returns>A GDI+ Bitmap that aliases the entire StitchSurface.</returns>
  400. public Bitmap CreateAliasedBitmap(Rectangle bounds)
  401. {
  402. return CreateAliasedBitmap(bounds, true);
  403. }
  404. /// <summary>
  405. /// Creates a GDI+ Bitmap object that aliases the same memory that this StitchSurface does.
  406. /// Then you can use GDI+ to draw on to this StitchSurface.
  407. /// Note: Since the Bitmap does not hold a reference to this StitchSurface object, nor to
  408. /// the MemoryBlock that it contains, you must hold a reference to the StitchSurface object
  409. /// for as long as you wish to use the aliased Bitmap. Otherwise the memory may be
  410. /// freed and the Bitmap will look corrupt or cause other errors. You may use the
  411. /// RenderArgs class to help manage this lifetime instead.
  412. /// </summary>
  413. /// <param name="bounds">The rectangle of interest within this StitchSurface that you wish to alias.</param>
  414. /// <param name="alpha">If true, the returned bitmap will use PixelFormat.Format32bppArgb.
  415. /// If false, the returned bitmap will use PixelFormat.Format32bppRgb.</param>
  416. /// <returns>A GDI+ Bitmap that aliases the requested portion of the StitchSurface.</returns>
  417. /// <exception cref="ArgumentOutOfRangeException"><b>bounds</b> was not entirely within the boundaries of the StitchSurface</exception>
  418. /// <exception cref="ObjectDisposedException">This StitchSurface instance is already disposed.</exception>
  419. public Bitmap CreateAliasedBitmap(Rectangle bounds, bool alpha = true)
  420. {
  421. if (disposed)
  422. {
  423. throw new ObjectDisposedException("StitchSurface");
  424. }
  425. if (bounds.IsEmpty)
  426. {
  427. throw new ArgumentOutOfRangeException();
  428. }
  429. Rectangle clipped = Rectangle.Intersect(this.Bounds, bounds);
  430. if (clipped != bounds)
  431. {
  432. throw new ArgumentOutOfRangeException();
  433. }
  434. unsafe
  435. {
  436. OpenCvSharp.Mat temp = new OpenCvSharp.Mat(bounds.Height, bounds.Width,OpenCvSharp.MatType.CV_8UC3, new IntPtr((void*)((byte*)scan0.VoidStar + GetPointByteOffsetUnchecked(bounds.X, bounds.Y, this.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 3))), stride);
  437. Bitmap bitmap1;
  438. if (temp.Rows < 3000)
  439. bitmap1 = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(temp);
  440. else
  441. bitmap1 = new Bitmap(temp.Width, temp.Height, (int)temp.Step(), PixelFormat.Format24bppRgb, temp.Data);
  442. return bitmap1;// new Bitmap(temp.Width, temp.Height, (int)temp.Step(), PixelFormat.Format24bppRgb, temp.Data);
  443. //return new Bitmap(bounds.Width, bounds.Height, (stride / 4) * 4, /*alpha ? PixelFormat.Format32bppArgb :*/ this.PixelFormat/*PixelFormat.Format24bppRgb*/,
  444. // new IntPtr((void*)((byte*)scan0.VoidStar + GetPointByteOffsetUnchecked(bounds.X, bounds.Y, this.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 3))));
  445. }
  446. }
  447. /// <summary>
  448. /// Releases all resources held by this StitchSurface object.
  449. /// </summary>
  450. public void Dispose()
  451. {
  452. Dispose(true);
  453. GC.SuppressFinalize(this);
  454. }
  455. private void Dispose(bool disposing)
  456. {
  457. if (!disposed)
  458. {
  459. disposed = true;
  460. if (disposing)
  461. {
  462. scan0.Dispose();
  463. scan0 = null;
  464. if (mat != null)
  465. {
  466. mat.Dispose();
  467. mat = null;
  468. }
  469. }
  470. }
  471. }
  472. }
  473. }